mirror of
https://github.com/flarum/framework.git
synced 2024-12-03 07:33:36 +08:00
chore: drop old package
This commit is contained in:
parent
fdf7b9a082
commit
3493dc80d0
|
@ -150,7 +150,6 @@
|
|||
"pusher/pusher-php-server": "^7.2",
|
||||
"s9e/text-formatter": "^2.13",
|
||||
"staudenmeir/eloquent-eager-limit": "^1.8.2",
|
||||
"sycho/json-api": "^0.5.0",
|
||||
"sycho/sourcemap": "^2.0.0",
|
||||
"symfony/config": "^6.3",
|
||||
"symfony/console": "^6.3",
|
||||
|
|
|
@ -45,13 +45,13 @@ return [
|
|||
->fields(PostResourceFields::class)
|
||||
->endpoint(
|
||||
[Endpoint\Index::class, Endpoint\Show::class, Endpoint\Create::class, Endpoint\Update::class],
|
||||
function (Endpoint\Index|Endpoint\Show|Endpoint\Create|Endpoint\Update $endpoint): Endpoint\Endpoint {
|
||||
function (Endpoint\Index|Endpoint\Show|Endpoint\Create|Endpoint\Update $endpoint): Endpoint\EndpointInterface {
|
||||
return $endpoint->addDefaultInclude(['likes']);
|
||||
}
|
||||
),
|
||||
|
||||
(new Extend\ApiResource(Resource\DiscussionResource::class))
|
||||
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint): Endpoint\Endpoint {
|
||||
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint): Endpoint\EndpointInterface {
|
||||
return $endpoint->addDefaultInclude(['posts.likes']);
|
||||
}),
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ return [
|
|||
|
||||
(new Extend\ApiResource(Resource\PostResource::class))
|
||||
->fields(PostResourceFields::class)
|
||||
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\Endpoint {
|
||||
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\EndpointInterface {
|
||||
return $endpoint->addDefaultInclude(['mentionedBy', 'mentionedBy.user', 'mentionedBy.discussion']);
|
||||
})
|
||||
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint): Endpoint\Index {
|
||||
|
@ -131,7 +131,7 @@ return [
|
|||
}),
|
||||
|
||||
(new Extend\ApiResource(Resource\PostResource::class))
|
||||
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\Endpoint {
|
||||
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\EndpointInterface {
|
||||
return $endpoint->eagerLoad(['mentionsTags']);
|
||||
}),
|
||||
]),
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace Flarum\Statistics\Api\Controller;
|
|||
use Carbon\Carbon;
|
||||
use DateTime;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Http\Exception\InvalidParameterException;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Post\RegisteredTypesScope;
|
||||
|
@ -24,7 +25,6 @@ use Laminas\Diactoros\Response\JsonResponse;
|
|||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tobscure\JsonApi\Exception\InvalidParameterException;
|
||||
|
||||
class ShowStatisticsData implements RequestHandlerInterface
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ class TagResource extends AbstractDatabaseResource
|
|||
}
|
||||
}
|
||||
|
||||
public function find(string $id, \Tobyz\JsonApiServer\Context $context): ?object
|
||||
public function find(string $id, Context $context): ?object
|
||||
{
|
||||
$actor = $context->getActor();
|
||||
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
"psr/http-server-middleware": "^1.0.2",
|
||||
"s9e/text-formatter": "^2.13",
|
||||
"staudenmeir/eloquent-eager-limit": "^1.8.2",
|
||||
"sycho/json-api": "^0.5.0",
|
||||
"sycho/sourcemap": "^2.0.0",
|
||||
"symfony/config": "^6.3",
|
||||
"symfony/console": "^6.3",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
namespace Flarum\Api;
|
||||
|
||||
use Flarum\Api\Controller\AbstractSerializeController;
|
||||
use Flarum\Api\Endpoint\EndpointInterface;
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Flarum\Api\Serializer\BasicDiscussionSerializer;
|
||||
use Flarum\Api\Serializer\NotificationSerializer;
|
||||
|
@ -24,6 +25,7 @@ use Flarum\Http\UrlGenerator;
|
|||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
use ReflectionClass;
|
||||
use Tobyz\JsonApiServer\Endpoint\Endpoint;
|
||||
|
||||
class ApiServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
|
@ -42,6 +44,8 @@ class ApiServiceProvider extends AbstractServiceProvider
|
|||
Resource\DiscussionResource::class,
|
||||
Resource\NotificationResource::class,
|
||||
Resource\AccessTokenResource::class,
|
||||
Resource\MailSettingResource::class,
|
||||
Resource\ExtensionReadmeResource::class,
|
||||
];
|
||||
});
|
||||
|
||||
|
@ -155,8 +159,7 @@ class ApiServiceProvider extends AbstractServiceProvider
|
|||
|
||||
public function boot(Container $container): void
|
||||
{
|
||||
AbstractSerializeController::setContainer($container);
|
||||
AbstractSerializer::setContainer($container);
|
||||
//
|
||||
}
|
||||
|
||||
protected function populateRoutes(RouteCollection $routes, Container $container): void
|
||||
|
@ -186,14 +189,16 @@ class ApiServiceProvider extends AbstractServiceProvider
|
|||
* None of the injected dependencies should be directly used within
|
||||
* the `endpoints` method. Encourage using callbacks.
|
||||
*
|
||||
* @var \Flarum\Api\Endpoint\Endpoint[] $endpoints
|
||||
* @var array<Endpoint&EndpointInterface> $endpoints
|
||||
*/
|
||||
$endpoints = $resource->resolveEndpoints(true);
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$route = $endpoint->route();
|
||||
$method = $endpoint->method;
|
||||
$path = rtrim("/$type$endpoint->path", '/');
|
||||
$name = "$type.$endpoint->name";
|
||||
|
||||
$routes->addRoute($route->method, rtrim("/$type$route->path", '/'), "$type.$route->name", $factory->toApiResource($resource::class, $endpoint::class));
|
||||
$routes->addRoute($method, $path, $name, $factory->toApiResource($resource::class, $endpoint->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ use Tobyz\JsonApiServer\Resource\Resource;
|
|||
class Context extends BaseContext
|
||||
{
|
||||
protected ?SearchResults $search = null;
|
||||
protected int|string|null $modelId = null;
|
||||
|
||||
/**
|
||||
* Data passed internally when reusing resource endpoint logic.
|
||||
|
@ -26,13 +25,6 @@ class Context extends BaseContext
|
|||
*/
|
||||
protected array $parameters = [];
|
||||
|
||||
public function withModelId(int|string|null $id): static
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->modelId = $id;
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withSearchResults(SearchResults $search): static
|
||||
{
|
||||
$new = clone $this;
|
||||
|
@ -47,11 +39,6 @@ class Context extends BaseContext
|
|||
return $new;
|
||||
}
|
||||
|
||||
public function getModelId(): int|string|null
|
||||
{
|
||||
return $this->modelId;
|
||||
}
|
||||
|
||||
public function getSearchResults(): ?SearchResults
|
||||
{
|
||||
return $this->search;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
abstract class AbstractCreateController extends AbstractShowController
|
||||
{
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return parent::handle($request)->withStatus(201);
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Collection;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Tobscure\JsonApi\ElementInterface;
|
||||
use Tobscure\JsonApi\SerializerInterface;
|
||||
|
||||
abstract class AbstractListController extends AbstractSerializeController
|
||||
{
|
||||
protected function createElement(mixed $data, SerializerInterface $serializer): ElementInterface
|
||||
{
|
||||
return new Collection($data, $serializer);
|
||||
}
|
||||
|
||||
abstract protected function data(ServerRequestInterface $request, Document $document): iterable;
|
||||
|
||||
protected function addPaginationData(Document $document, ServerRequestInterface $request, string $url, ?int $total): void
|
||||
{
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$url,
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$total,
|
||||
);
|
||||
|
||||
$document->setMeta([
|
||||
'total' => $total,
|
||||
'perPage' => $limit,
|
||||
'page' => $offset / $limit + 1,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,432 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\JsonApiResponse;
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Tobscure\JsonApi\ElementInterface;
|
||||
use Tobscure\JsonApi\Parameters;
|
||||
use Tobscure\JsonApi\SerializerInterface;
|
||||
|
||||
abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* The name of the serializer class to output results with.
|
||||
*
|
||||
* @var class-string<AbstractSerializer>|null
|
||||
*/
|
||||
public ?string $serializer;
|
||||
|
||||
/**
|
||||
* The relationships that are included by default.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public array $include = [];
|
||||
|
||||
/**
|
||||
* The relationships that are available to be included.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public array $optionalInclude = [];
|
||||
|
||||
/**
|
||||
* The maximum number of records that can be requested.
|
||||
*/
|
||||
public int $maxLimit = 50;
|
||||
|
||||
/**
|
||||
* The number of records included by default.
|
||||
*/
|
||||
public int $limit = 20;
|
||||
|
||||
/**
|
||||
* The fields that are available to be sorted by.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public array $sortFields = [];
|
||||
|
||||
/**
|
||||
* The default sort field and order to use.
|
||||
*
|
||||
* @var array<string, string>|null
|
||||
*/
|
||||
public ?array $sort = null;
|
||||
|
||||
protected static Container $container;
|
||||
|
||||
/**
|
||||
* @var array<class-string<self>, callable[]>
|
||||
*/
|
||||
protected static array $beforeDataCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var array<class-string<self>, callable[]>
|
||||
*/
|
||||
protected static array $beforeSerializationCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var string[][]
|
||||
*/
|
||||
protected static array $loadRelations = [];
|
||||
|
||||
/**
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected static array $loadRelationCallables = [];
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$document = new Document;
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$beforeDataCallbacks[$class])) {
|
||||
foreach (static::$beforeDataCallbacks[$class] as $callback) {
|
||||
$callback($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->data($request, $document);
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$beforeSerializationCallbacks[$class])) {
|
||||
foreach (static::$beforeSerializationCallbacks[$class] as $callback) {
|
||||
$callback($this, $data, $request, $document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->serializer)) {
|
||||
throw new InvalidArgumentException('Serializer required for controller: '.static::class);
|
||||
}
|
||||
|
||||
$serializer = static::$container->make($this->serializer);
|
||||
$serializer->setRequest($request);
|
||||
|
||||
$element = $this->createElement($data, $serializer)
|
||||
->with($this->extractInclude($request))
|
||||
->fields($this->extractFields($request));
|
||||
|
||||
$document->setData($element);
|
||||
|
||||
return new JsonApiResponse($document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to be serialized and assigned to the response document.
|
||||
*/
|
||||
abstract protected function data(ServerRequestInterface $request, Document $document): mixed;
|
||||
|
||||
/**
|
||||
* Create a PHP JSON-API Element for output in the document.
|
||||
*/
|
||||
abstract protected function createElement(mixed $data, SerializerInterface $serializer): ElementInterface;
|
||||
|
||||
/**
|
||||
* Returns the relations to load added by extenders.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getRelationsToLoad(Collection $models): array
|
||||
{
|
||||
$addedRelations = [];
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$loadRelations[$class])) {
|
||||
$addedRelations = array_merge($addedRelations, static::$loadRelations[$class]);
|
||||
}
|
||||
}
|
||||
|
||||
return $addedRelations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relation callables to load added by extenders.
|
||||
*
|
||||
* @return array<string, callable>
|
||||
*/
|
||||
protected function getRelationCallablesToLoad(Collection $models): array
|
||||
{
|
||||
$addedRelationCallables = [];
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$loadRelationCallables[$class])) {
|
||||
$addedRelationCallables = array_merge($addedRelationCallables, static::$loadRelationCallables[$class]);
|
||||
}
|
||||
}
|
||||
|
||||
return $addedRelationCallables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager loads the required relationships.
|
||||
*/
|
||||
protected function loadRelations(Collection $models, array $relations, ServerRequestInterface $request = null): void
|
||||
{
|
||||
$addedRelations = $this->getRelationsToLoad($models);
|
||||
$addedRelationCallables = $this->getRelationCallablesToLoad($models);
|
||||
|
||||
foreach ($addedRelationCallables as $name => $relation) {
|
||||
$addedRelations[] = $name;
|
||||
}
|
||||
|
||||
if (! empty($addedRelations)) {
|
||||
usort($addedRelations, function ($a, $b) {
|
||||
return substr_count($a, '.') - substr_count($b, '.');
|
||||
});
|
||||
|
||||
foreach ($addedRelations as $relation) {
|
||||
if (str_contains($relation, '.')) {
|
||||
$parentRelation = Str::beforeLast($relation, '.');
|
||||
|
||||
if (! in_array($parentRelation, $relations, true)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$relations[] = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($relations)) {
|
||||
$relations = array_unique($relations);
|
||||
}
|
||||
|
||||
$callableRelations = [];
|
||||
$nonCallableRelations = [];
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
if (isset($addedRelationCallables[$relation])) {
|
||||
$load = $addedRelationCallables[$relation];
|
||||
|
||||
$callableRelations[$relation] = function ($query) use ($load, $request, $relations) {
|
||||
$load($query, $request, $relations);
|
||||
};
|
||||
} else {
|
||||
$nonCallableRelations[] = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($callableRelations)) {
|
||||
$models->loadMissing($callableRelations);
|
||||
}
|
||||
|
||||
if (! empty($nonCallableRelations)) {
|
||||
$models->loadMissing($nonCallableRelations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Tobscure\JsonApi\Exception\InvalidParameterException
|
||||
*/
|
||||
protected function extractInclude(ServerRequestInterface $request): array
|
||||
{
|
||||
$available = array_merge($this->include, $this->optionalInclude);
|
||||
|
||||
return $this->buildParameters($request)->getInclude($available) ?: $this->include;
|
||||
}
|
||||
|
||||
protected function extractFields(ServerRequestInterface $request): array
|
||||
{
|
||||
return $this->buildParameters($request)->getFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Tobscure\JsonApi\Exception\InvalidParameterException
|
||||
*/
|
||||
protected function extractSort(ServerRequestInterface $request): ?array
|
||||
{
|
||||
return $this->buildParameters($request)->getSort($this->sortFields) ?: $this->sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Tobscure\JsonApi\Exception\InvalidParameterException
|
||||
*/
|
||||
protected function extractOffset(ServerRequestInterface $request): int
|
||||
{
|
||||
return (int) $this->buildParameters($request)->getOffset($this->extractLimit($request)) ?: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Tobscure\JsonApi\Exception\InvalidParameterException
|
||||
*/
|
||||
protected function extractLimit(ServerRequestInterface $request): int
|
||||
{
|
||||
return (int) $this->buildParameters($request)->getLimit($this->maxLimit) ?: $this->limit;
|
||||
}
|
||||
|
||||
protected function extractFilter(ServerRequestInterface $request): array
|
||||
{
|
||||
return $this->buildParameters($request)->getFilter() ?: [];
|
||||
}
|
||||
|
||||
protected function buildParameters(ServerRequestInterface $request): Parameters
|
||||
{
|
||||
return new Parameters($request->getQueryParams());
|
||||
}
|
||||
|
||||
protected function sortIsDefault(ServerRequestInterface $request): bool
|
||||
{
|
||||
return ! Arr::get($request->getQueryParams(), 'sort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the serializer that will serialize data for the endpoint.
|
||||
*/
|
||||
public function setSerializer(string $serializer): void
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the given relationship by default.
|
||||
*/
|
||||
public function addInclude(array|string $name): void
|
||||
{
|
||||
$this->include = array_merge($this->include, (array) $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't include the given relationship by default.
|
||||
*/
|
||||
public function removeInclude(array|string $name): void
|
||||
{
|
||||
$this->include = array_diff($this->include, (array) $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the given relationship available for inclusion.
|
||||
*/
|
||||
public function addOptionalInclude(array|string $name): void
|
||||
{
|
||||
$this->optionalInclude = array_merge($this->optionalInclude, (array) $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't allow the given relationship to be included.
|
||||
*/
|
||||
public function removeOptionalInclude(array|string $name): void
|
||||
{
|
||||
$this->optionalInclude = array_diff($this->optionalInclude, (array) $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default number of results.
|
||||
*/
|
||||
public function setLimit(int $limit): void
|
||||
{
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of results.
|
||||
*/
|
||||
public function setMaxLimit(int $max): void
|
||||
{
|
||||
$this->maxLimit = $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow sorting results by the given field.
|
||||
*/
|
||||
public function addSortField(array|string $field): void
|
||||
{
|
||||
$this->sortFields = array_merge($this->sortFields, (array) $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow sorting results by the given field.
|
||||
*/
|
||||
public function removeSortField(array|string $field): void
|
||||
{
|
||||
$this->sortFields = array_diff($this->sortFields, (array) $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default sort order for the results.
|
||||
*/
|
||||
public function setSort(array $sort): void
|
||||
{
|
||||
$this->sort = $sort;
|
||||
}
|
||||
|
||||
public static function getContainer(): Container
|
||||
{
|
||||
return static::$container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function setContainer(Container $container): void
|
||||
{
|
||||
static::$container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function addDataPreparationCallback(string $controllerClass, callable $callback): void
|
||||
{
|
||||
if (! isset(static::$beforeDataCallbacks[$controllerClass])) {
|
||||
static::$beforeDataCallbacks[$controllerClass] = [];
|
||||
}
|
||||
|
||||
static::$beforeDataCallbacks[$controllerClass][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function addSerializationPreparationCallback(string $controllerClass, callable $callback): void
|
||||
{
|
||||
if (! isset(static::$beforeSerializationCallbacks[$controllerClass])) {
|
||||
static::$beforeSerializationCallbacks[$controllerClass] = [];
|
||||
}
|
||||
|
||||
static::$beforeSerializationCallbacks[$controllerClass][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function setLoadRelations(string $controllerClass, array $relations): void
|
||||
{
|
||||
if (! isset(static::$loadRelations[$controllerClass])) {
|
||||
static::$loadRelations[$controllerClass] = [];
|
||||
}
|
||||
|
||||
static::$loadRelations[$controllerClass] = array_merge(static::$loadRelations[$controllerClass], $relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function setLoadRelationCallables(string $controllerClass, array $relations): void
|
||||
{
|
||||
if (! isset(static::$loadRelationCallables[$controllerClass])) {
|
||||
static::$loadRelationCallables[$controllerClass] = [];
|
||||
}
|
||||
|
||||
static::$loadRelationCallables[$controllerClass] = array_merge(static::$loadRelationCallables[$controllerClass], $relations);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Tobscure\JsonApi\Resource;
|
||||
use Tobscure\JsonApi\SerializerInterface;
|
||||
|
||||
abstract class AbstractShowController extends AbstractSerializeController
|
||||
{
|
||||
protected function createElement(mixed $data, SerializerInterface $serializer): \Tobscure\JsonApi\ElementInterface
|
||||
{
|
||||
return new Resource($data, $serializer);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\DeleteAvatar;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class DeleteAvatarController extends AbstractShowController
|
||||
{
|
||||
public ?string $serializer = UserSerializer::class;
|
||||
|
||||
public function __construct(
|
||||
protected Dispatcher $bus
|
||||
) {
|
||||
}
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document): mixed
|
||||
{
|
||||
return $this->bus->dispatch(
|
||||
new DeleteAvatar(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\ExtensionReadmeSerializer;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class ShowExtensionReadmeController extends AbstractShowController
|
||||
{
|
||||
public ?string $serializer = ExtensionReadmeSerializer::class;
|
||||
|
||||
public function __construct(
|
||||
protected ExtensionManager $extensions
|
||||
) {
|
||||
}
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document): ?Extension
|
||||
{
|
||||
$extensionName = Arr::get($request->getQueryParams(), 'name');
|
||||
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
return $this->extensions->getExtension($extensionName);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Endpoint\Show;
|
||||
use Flarum\Api\JsonApi;
|
||||
use Flarum\Api\Resource\ForumResource;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
@ -26,7 +25,7 @@ class ShowForumController implements RequestHandlerInterface
|
|||
{
|
||||
return $this->api
|
||||
->forResource(ForumResource::class)
|
||||
->forEndpoint(Show::class)
|
||||
->forEndpoint('show')
|
||||
->handle($request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,36 +9,23 @@
|
|||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\MailSettingsSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Validation\Factory;
|
||||
use Flarum\Api\JsonApi;
|
||||
use Flarum\Api\Resource\MailSettingResource;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ShowMailSettingsController extends AbstractShowController
|
||||
class ShowMailSettingsController implements RequestHandlerInterface
|
||||
{
|
||||
public ?string $serializer = MailSettingsSerializer::class;
|
||||
public function __construct(
|
||||
protected JsonApi $api
|
||||
) {}
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document): array
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
$drivers = array_map(function ($driver) {
|
||||
return self::$container->make($driver);
|
||||
}, self::$container->make('mail.supported_drivers'));
|
||||
|
||||
$settings = self::$container->make(SettingsRepositoryInterface::class);
|
||||
$configured = self::$container->make('flarum.mail.configured_driver');
|
||||
$actual = self::$container->make('mail.driver');
|
||||
$validator = self::$container->make(Factory::class);
|
||||
|
||||
$errors = $configured->validate($settings, $validator);
|
||||
|
||||
return [
|
||||
'drivers' => $drivers,
|
||||
'sending' => $actual->canSend(),
|
||||
'errors' => $errors,
|
||||
];
|
||||
return $this->api
|
||||
->forResource(MailSettingResource::class)
|
||||
->forEndpoint('show')
|
||||
->handle($request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\Command\UploadAvatar;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class UploadAvatarController extends AbstractShowController
|
||||
{
|
||||
public ?string $serializer = UserSerializer::class;
|
||||
|
||||
public function __construct(
|
||||
protected Dispatcher $bus
|
||||
) {
|
||||
}
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document): User
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$file = Arr::get($request->getUploadedFiles(), 'avatar');
|
||||
|
||||
return $this->bus->dispatch(
|
||||
new UploadAvatar($id, $file, $actor)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Api\JsonApi;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
|
@ -16,9 +17,9 @@ use Illuminate\Contracts\Filesystem\Filesystem;
|
|||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Interfaces\EncodedImageInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
abstract class UploadImageController extends ShowForumController
|
||||
{
|
||||
|
@ -28,13 +29,16 @@ abstract class UploadImageController extends ShowForumController
|
|||
protected string $filenamePrefix = '';
|
||||
|
||||
public function __construct(
|
||||
JsonApi $api,
|
||||
protected SettingsRepositoryInterface $settings,
|
||||
Factory $filesystemFactory
|
||||
) {
|
||||
parent::__construct($api);
|
||||
|
||||
$this->uploadDir = $filesystemFactory->disk('flarum-assets');
|
||||
}
|
||||
|
||||
public function data(ServerRequestInterface $request, Document $document): array
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
|
@ -52,7 +56,7 @@ abstract class UploadImageController extends ShowForumController
|
|||
|
||||
$this->settings->set($this->filePathSettingKey, $uploadName);
|
||||
|
||||
return parent::data($request, $document);
|
||||
return parent::handle($request);
|
||||
}
|
||||
|
||||
abstract protected function makeImage(UploadedFileInterface $file): EncodedImageInterface;
|
||||
|
|
|
@ -20,6 +20,8 @@ trait HasAuthorization
|
|||
*/
|
||||
protected null|string|Closure $ability = null;
|
||||
|
||||
protected bool $admin = false;
|
||||
|
||||
public function authenticated(bool|Closure $condition = true): self
|
||||
{
|
||||
$this->authenticated = $condition;
|
||||
|
@ -34,6 +36,13 @@ trait HasAuthorization
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function admin(bool $admin = true): self
|
||||
{
|
||||
$this->admin = $admin;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAuthenticated(Context $context): bool
|
||||
{
|
||||
if (is_bool($this->authenticated)) {
|
||||
|
@ -68,6 +77,10 @@ trait HasAuthorization
|
|||
$actor->assertRegistered();
|
||||
}
|
||||
|
||||
if ($this->admin) {
|
||||
$actor->assertAdmin();
|
||||
}
|
||||
|
||||
if ($ability = $this->getAuthorized($context)) {
|
||||
$actor->assertCan($ability, $context->model);
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Flarum\Api\Endpoint\Concerns;
|
||||
|
||||
trait HasCustomRoute
|
||||
{
|
||||
protected string $path;
|
||||
|
||||
public function path(string $path): self
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint\Concerns;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobyz\JsonApiServer\Laravel\EloquentResource;
|
||||
|
||||
/**
|
||||
* This is directed at eager loading relationships apart from the request includes.
|
||||
|
@ -66,8 +67,14 @@ trait HasEagerLoading
|
|||
/**
|
||||
* Eager loads the required relationships.
|
||||
*/
|
||||
protected function loadRelations(Collection $models, ServerRequestInterface $request, array $included = []): void
|
||||
protected function loadRelations(Collection $models, Context $context, array $included = []): void
|
||||
{
|
||||
if (! $context->collection instanceof EloquentResource) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = $context->request;
|
||||
|
||||
$included = $this->stringInclude($included);
|
||||
$models = $models->filter(fn ($model) => $model instanceof Model);
|
||||
|
||||
|
|
|
@ -2,81 +2,27 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
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 Illuminate\Database\Eloquent\Collection;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use RuntimeException;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
use Tobyz\JsonApiServer\Endpoint\Create as BaseCreate;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use Tobyz\JsonApiServer\Resource\Creatable;
|
||||
use function Tobyz\JsonApiServer\json_api_response;
|
||||
|
||||
class Create extends BaseCreate implements Endpoint
|
||||
class Create extends BaseCreate implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use HasCustomRoute;
|
||||
use HasCustomHooks;
|
||||
|
||||
public function handle(Context $context): ?ResponseInterface
|
||||
public function setUp(): void
|
||||
{
|
||||
$model = $this->execute($context);
|
||||
parent::setUp();
|
||||
|
||||
return json_api_response($document = $this->showResource($context, $model))
|
||||
->withStatus(201)
|
||||
->withHeader('Location', $document['data']['links']['self']);
|
||||
}
|
||||
$this->after(function (Context $context, object $model) {
|
||||
$this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context));
|
||||
|
||||
public function execute(Context $context): object
|
||||
{
|
||||
$collection = $context->collection;
|
||||
|
||||
if (!$collection instanceof Creatable) {
|
||||
throw new RuntimeException(
|
||||
sprintf('%s must implement %s', get_class($collection), Creatable::class),
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->isVisible($context)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
$this->callBeforeHook($context);
|
||||
|
||||
$data = $this->parseData($context);
|
||||
|
||||
$context = $context
|
||||
->withResource($resource = $context->resource($data['type']))
|
||||
->withModel($model = $collection->newModel($context));
|
||||
|
||||
$this->assertFieldsValid($context, $data);
|
||||
$this->fillDefaultValues($context, $data);
|
||||
$this->deserializeValues($context, $data);
|
||||
$this->assertDataValid($context, $data);
|
||||
|
||||
$this->setValues($context, $data);
|
||||
|
||||
$context = $context->withModel($model = $resource->create($model, $context));
|
||||
|
||||
$this->saveFields($context, $data);
|
||||
|
||||
$model = $this->callAfterHook($context, $model);
|
||||
|
||||
$this->loadRelations(Collection::make([$model]), $context->request, $this->getInclude($context));
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function route(): EndpointRoute
|
||||
{
|
||||
return new EndpointRoute(
|
||||
name: 'create',
|
||||
path: $this->path ?? '/',
|
||||
method: 'POST',
|
||||
);
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,70 +3,9 @@
|
|||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
|
||||
use Flarum\Api\Endpoint\Concerns\HasCustomRoute;
|
||||
use Flarum\Api\Resource\Contracts\Deletable;
|
||||
use Nyholm\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use RuntimeException;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
use Tobyz\JsonApiServer\Endpoint\Delete as BaseDelete;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use function Tobyz\JsonApiServer\json_api_response;
|
||||
|
||||
class Delete extends BaseDelete implements Endpoint
|
||||
class Delete extends BaseDelete implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasCustomRoute;
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function handle(Context $context): ?ResponseInterface
|
||||
{
|
||||
$segments = explode('/', $context->path());
|
||||
|
||||
if (count($segments) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$context = $context->withModelId($segments[1]);
|
||||
|
||||
$this->execute($context);
|
||||
|
||||
if ($meta = $this->serializeMeta($context)) {
|
||||
return json_api_response(['meta' => $meta]);
|
||||
}
|
||||
|
||||
return new Response(204);
|
||||
}
|
||||
|
||||
public function execute(Context $context): bool
|
||||
{
|
||||
$model = $this->findResource($context, $context->getModelId());
|
||||
|
||||
$context = $context->withResource(
|
||||
$resource = $context->resource($context->collection->resource($model, $context)),
|
||||
);
|
||||
|
||||
if (!$resource instanceof Deletable) {
|
||||
throw new RuntimeException(
|
||||
sprintf('%s must implement %s', get_class($resource), Deletable::class),
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->isVisible($context = $context->withModel($model))) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
$resource->deleteAction($model, $context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function route(): EndpointRoute
|
||||
{
|
||||
return new EndpointRoute(
|
||||
name: 'delete',
|
||||
path: $this->path ?? '/{id}',
|
||||
method: 'DELETE',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,9 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
use Tobyz\JsonApiServer\Endpoint\Endpoint as BaseEndpoint;
|
||||
|
||||
interface Endpoint extends BaseEndpoint
|
||||
class Endpoint extends BaseEndpoint implements EndpointInterface
|
||||
{
|
||||
/** @var \Flarum\Api\Context $context */
|
||||
public function handle(Context $context): ?Response;
|
||||
|
||||
public function execute(Context $context): mixed;
|
||||
|
||||
public function route(): EndpointRoute;
|
||||
//
|
||||
}
|
||||
|
|
8
framework/core/src/Api/Endpoint/EndpointInterface.php
Normal file
8
framework/core/src/Api/Endpoint/EndpointInterface.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
interface EndpointInterface
|
||||
{
|
||||
//
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
class EndpointRoute
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $path,
|
||||
public string $method,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -23,11 +23,10 @@ use Tobyz\JsonApiServer\Resource\Listable;
|
|||
use Tobyz\JsonApiServer\Serializer;
|
||||
use function Tobyz\JsonApiServer\json_api_response;
|
||||
|
||||
class Index extends BaseIndex implements Endpoint
|
||||
class Index extends BaseIndex implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use HasCustomRoute;
|
||||
use ExtractsListingParams;
|
||||
use HasCustomHooks;
|
||||
|
||||
|
@ -45,11 +44,6 @@ class Index extends BaseIndex implements Endpoint
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function execute(Context $context): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function handle(Context $context): ?Response
|
||||
{
|
||||
|
@ -121,7 +115,7 @@ class Index extends BaseIndex implements Endpoint
|
|||
|
||||
$include = $this->getInclude($context);
|
||||
|
||||
$this->loadRelations($models, $context->request, $include);
|
||||
$this->loadRelations($models, $context, $include);
|
||||
|
||||
$serializer = new Serializer($context);
|
||||
|
||||
|
@ -142,13 +136,4 @@ class Index extends BaseIndex implements Endpoint
|
|||
|
||||
return json_api_response(compact('data', 'included', 'meta', 'links'));
|
||||
}
|
||||
|
||||
public function route(): EndpointRoute
|
||||
{
|
||||
return new EndpointRoute(
|
||||
name: 'index',
|
||||
path: $this->path ?? '/',
|
||||
method: 'GET',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,68 +2,29 @@
|
|||
|
||||
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 Illuminate\Database\Eloquent\Collection;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
use Tobyz\JsonApiServer\Endpoint\Concerns\FindsResources;
|
||||
use Tobyz\JsonApiServer\Endpoint\Concerns\ShowsResources;
|
||||
use Tobyz\JsonApiServer\Endpoint\Show as BaseShow;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use function Tobyz\JsonApiServer\json_api_response;
|
||||
|
||||
class Show extends BaseShow implements Endpoint
|
||||
class Show extends BaseShow implements EndpointInterface
|
||||
{
|
||||
use FindsResources;
|
||||
use ShowsResources;
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use HasCustomRoute;
|
||||
use ExtractsListingParams;
|
||||
use HasCustomHooks;
|
||||
|
||||
public function handle(Context $context): ?ResponseInterface
|
||||
public function setUp(): void
|
||||
{
|
||||
$segments = explode('/', $context->path());
|
||||
parent::setUp();
|
||||
|
||||
$path = $this->route()->path;
|
||||
$this->after(function (Context $context, object $model) {
|
||||
$this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context));
|
||||
|
||||
if ($path !== '/' && count($segments) !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$context = $context->withModelId($path === '/' ? 1 : $segments[1]);
|
||||
|
||||
$this->callBeforeHook($context);
|
||||
|
||||
$model = $this->execute($context);
|
||||
|
||||
if (!$this->isVisible($context = $context->withModel($model))) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
$model = $this->callAfterHook($context, $model);
|
||||
|
||||
$this->loadRelations(Collection::make([$model]), $context->request, $this->getInclude($context));
|
||||
|
||||
return json_api_response($this->showResource($context, $model));
|
||||
}
|
||||
|
||||
public function execute(Context $context): object
|
||||
{
|
||||
return $this->findResource($context, $context->getModelId());
|
||||
}
|
||||
|
||||
public function route(): EndpointRoute
|
||||
{
|
||||
return new EndpointRoute(
|
||||
name: 'show',
|
||||
path: $this->path ?? '/{id}',
|
||||
method: 'GET',
|
||||
);
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,85 +2,27 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
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 Illuminate\Database\Eloquent\Collection;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use RuntimeException;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
use Tobyz\JsonApiServer\Endpoint\Update as BaseUpdate;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use Tobyz\JsonApiServer\Resource\Updatable;
|
||||
use function Tobyz\JsonApiServer\json_api_response;
|
||||
|
||||
class Update extends BaseUpdate implements Endpoint
|
||||
class Update extends BaseUpdate implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use HasCustomRoute;
|
||||
use HasCustomHooks;
|
||||
|
||||
public function handle(Context $context): ?ResponseInterface
|
||||
public function setUp(): void
|
||||
{
|
||||
$segments = explode('/', $context->path());
|
||||
parent::setUp();
|
||||
|
||||
if (count($segments) !== 2) {
|
||||
return null;
|
||||
}
|
||||
$this->after(function (Context $context, object $model) {
|
||||
$this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context));
|
||||
|
||||
$context = $context->withModelId($segments[1]);
|
||||
|
||||
$model = $this->execute($context);
|
||||
|
||||
return json_api_response($this->showResource($context, $model));
|
||||
}
|
||||
|
||||
public function execute(Context $context): object
|
||||
{
|
||||
$model = $this->findResource($context, $context->getModelId());
|
||||
|
||||
$context = $context->withResource(
|
||||
$resource = $context->resource($context->collection->resource($model, $context)),
|
||||
);
|
||||
|
||||
if (!$resource instanceof Updatable) {
|
||||
throw new RuntimeException(
|
||||
sprintf('%s must implement %s', get_class($resource), Updatable::class),
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->isVisible($context = $context->withModel($model))) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
$this->callBeforeHook($context);
|
||||
|
||||
$data = $this->parseData($context);
|
||||
|
||||
$this->assertFieldsValid($context, $data);
|
||||
$this->deserializeValues($context, $data);
|
||||
$this->assertDataValid($context, $data);
|
||||
$this->setValues($context, $data);
|
||||
|
||||
$context = $context->withModel($model = $resource->update($model, $context));
|
||||
|
||||
$this->saveFields($context, $data);
|
||||
|
||||
$model = $this->callAfterHook($context, $model);
|
||||
|
||||
$this->loadRelations(Collection::make([$model]), $context->request, $this->getInclude($context));
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function route(): EndpointRoute
|
||||
{
|
||||
return new EndpointRoute(
|
||||
name: 'update',
|
||||
path: $this->path ?? '/{id}',
|
||||
method: 'PATCH',
|
||||
);
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
namespace Flarum\Api;
|
||||
|
||||
use Flarum\Api\Endpoint\Endpoint;
|
||||
use Flarum\Api\Endpoint\EndpointRoute;
|
||||
use Flarum\Api\Endpoint\EndpointInterface;
|
||||
use Flarum\Api\Resource\AbstractDatabaseResource;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
@ -11,6 +10,7 @@ use Laminas\Diactoros\ServerRequestFactory;
|
|||
use Laminas\Diactoros\Uri;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Tobyz\JsonApiServer\Endpoint\Endpoint;
|
||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||
use Tobyz\JsonApiServer\JsonApi as BaseJsonApi;
|
||||
use Tobyz\JsonApiServer\Resource\Collection;
|
||||
|
@ -19,7 +19,7 @@ use Tobyz\JsonApiServer\Resource\Resource;
|
|||
class JsonApi extends BaseJsonApi
|
||||
{
|
||||
protected string $resourceClass;
|
||||
protected string $endpoint;
|
||||
protected string $endpointName;
|
||||
protected ?Request $baseRequest = null;
|
||||
protected ?Container $container = null;
|
||||
|
||||
|
@ -30,16 +30,16 @@ class JsonApi extends BaseJsonApi
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function forEndpoint(string $endpoint): self
|
||||
public function forEndpoint(string $endpointName): self
|
||||
{
|
||||
$this->endpoint = $endpoint;
|
||||
$this->endpointName = $endpointName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function makeContext(Request $request): Context
|
||||
{
|
||||
if (! $this->endpoint || ! $this->resourceClass || ! class_exists($this->resourceClass)) {
|
||||
if (! $this->endpointName || ! $this->resourceClass || ! class_exists($this->resourceClass)) {
|
||||
throw new BadRequestException('No resource or endpoint specified');
|
||||
}
|
||||
|
||||
|
@ -50,11 +50,11 @@ class JsonApi extends BaseJsonApi
|
|||
->withEndpoint($this->findEndpoint($collection));
|
||||
}
|
||||
|
||||
protected function findEndpoint(?Collection $collection): Endpoint
|
||||
protected function findEndpoint(?Collection $collection): Endpoint&EndpointInterface
|
||||
{
|
||||
/** @var \Flarum\Api\Endpoint\Endpoint $endpoint */
|
||||
/** @var Endpoint&EndpointInterface $endpoint */
|
||||
foreach ($collection->resolveEndpoints() as $endpoint) {
|
||||
if ($endpoint::class === $this->endpoint) {
|
||||
if ($endpoint->name === $this->endpointName) {
|
||||
return $endpoint;
|
||||
}
|
||||
}
|
||||
|
@ -76,11 +76,8 @@ class JsonApi extends BaseJsonApi
|
|||
return $context->endpoint->handle($context);
|
||||
}
|
||||
|
||||
public function execute(array $body, array $internal = [], array $options = []): mixed
|
||||
public function process(array $body, array $internal = [], array $options = []): mixed
|
||||
{
|
||||
/** @var EndpointRoute $route */
|
||||
$route = (new $this->endpoint)->route();
|
||||
|
||||
$request = $this->baseRequest ?? ServerRequestFactory::fromGlobals();
|
||||
|
||||
if (! empty($options['actor'])) {
|
||||
|
@ -90,8 +87,6 @@ class JsonApi extends BaseJsonApi
|
|||
$resource = $this->getCollection($this->resourceClass);
|
||||
|
||||
$request = $request
|
||||
->withMethod($route->method)
|
||||
->withUri(new Uri($route->path))
|
||||
->withParsedBody([
|
||||
...$body,
|
||||
'data' => [
|
||||
|
@ -110,7 +105,13 @@ class JsonApi extends BaseJsonApi
|
|||
$context = $context->withInternal($key, $value);
|
||||
}
|
||||
|
||||
return $context->endpoint->execute($context);
|
||||
$context = $context->withRequest(
|
||||
$request
|
||||
->withMethod($context->endpoint->method)
|
||||
->withUri(new Uri($context->endpoint->path))
|
||||
);
|
||||
|
||||
return $context->endpoint->process($context);
|
||||
}
|
||||
|
||||
public function validateQueryParameters(Request $request): void
|
||||
|
|
|
@ -10,11 +10,10 @@
|
|||
namespace Flarum\Api;
|
||||
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class JsonApiResponse extends JsonResponse
|
||||
{
|
||||
public function __construct(Document $document, $status = 200, array $headers = [], $encodingOptions = 15)
|
||||
public function __construct(array $document, $status = 200, array $headers = [], $encodingOptions = 15)
|
||||
{
|
||||
$headers['content-type'] = 'application/vnd.api+json';
|
||||
|
||||
|
|
|
@ -59,18 +59,18 @@ abstract class AbstractDatabaseResource extends BaseResource implements
|
|||
throw new RuntimeException('Not supported in Flarum, please use a model searcher instead https://docs.flarum.org/extend/search.');
|
||||
}
|
||||
|
||||
public function create(object $model, Context $context): object
|
||||
public function createAction(object $model, Context $context): object
|
||||
{
|
||||
$model = parent::create($model, $context);
|
||||
$model = parent::createAction($model, $context);
|
||||
|
||||
$this->dispatchEventsFor($model, $context->getActor());
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function update(object $model, Context $context): object
|
||||
public function updateAction(object $model, Context $context): object
|
||||
{
|
||||
$model = parent::update($model, $context);
|
||||
$model = parent::updateAction($model, $context);
|
||||
|
||||
$this->dispatchEventsFor($model, $context->getActor());
|
||||
|
||||
|
|
|
@ -7,5 +7,5 @@ use Tobyz\JsonApiServer\Resource\Deletable as BaseDeletable;
|
|||
|
||||
interface Deletable extends BaseDeletable
|
||||
{
|
||||
public function deleteAction(object $model, Context $context): void;
|
||||
//
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Flarum\Api\Resource;
|
|||
use Carbon\Carbon;
|
||||
use Flarum\Api\Context;
|
||||
use Flarum\Api\Endpoint;
|
||||
use Flarum\Api\Endpoint\Create;
|
||||
use Flarum\Api\JsonApi;
|
||||
use Flarum\Api\Schema;
|
||||
use Flarum\Api\Sort\SortColumn;
|
||||
|
@ -302,9 +301,9 @@ class DiscussionResource extends AbstractDatabaseResource
|
|||
// Now that the discussion has been created, we can add the first post.
|
||||
// We will do this by running the PostReply command.
|
||||
$post = $api->forResource(PostResource::class)
|
||||
->forEndpoint(Create::class)
|
||||
->forEndpoint('create')
|
||||
->withRequest($context->request)
|
||||
->execute([
|
||||
->process([
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
'content' => Arr::get($context->body(), 'data.attributes.content'),
|
||||
|
|
60
framework/core/src/Api/Resource/ExtensionReadmeResource.php
Normal file
60
framework/core/src/Api/Resource/ExtensionReadmeResource.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Flarum\Api\Resource;
|
||||
|
||||
use Flarum\Api\Endpoint;
|
||||
use Flarum\Api\Resource\Contracts\Findable;
|
||||
use Flarum\Api\Schema;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Mail\DriverInterface;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Validation\Factory;
|
||||
use stdClass;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
|
||||
/**
|
||||
* @todo: change to a simple ExtensionResource with readme field.
|
||||
*/
|
||||
class ExtensionReadmeResource extends AbstractResource implements Findable
|
||||
{
|
||||
public function __construct(
|
||||
protected ExtensionManager $extensions
|
||||
) {
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
{
|
||||
return 'extension-readmes';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Extension $model
|
||||
*/
|
||||
public function getId(object $model, Context $context): string
|
||||
{
|
||||
return $model->getId();
|
||||
}
|
||||
|
||||
public function find(string $id, Context $context): ?object
|
||||
{
|
||||
return $this->extensions->getExtension($id);
|
||||
}
|
||||
|
||||
public function endpoints(): array
|
||||
{
|
||||
return [
|
||||
Endpoint\Show::make()
|
||||
->admin(),
|
||||
];
|
||||
}
|
||||
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
Schema\Str::make('content')
|
||||
->get(fn (Extension $extension) => $extension->getReadme()),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -38,6 +38,11 @@ class ForumResource extends AbstractResource implements Findable
|
|||
return '1';
|
||||
}
|
||||
|
||||
public function id(\Tobyz\JsonApiServer\Context $context): ?string
|
||||
{
|
||||
return '1';
|
||||
}
|
||||
|
||||
public function find(string $id, \Tobyz\JsonApiServer\Context $context): ?object
|
||||
{
|
||||
return new stdClass();
|
||||
|
@ -48,7 +53,7 @@ class ForumResource extends AbstractResource implements Findable
|
|||
return [
|
||||
Endpoint\Show::make()
|
||||
->defaultInclude(['groups', 'actor.groups'])
|
||||
->path('/'),
|
||||
->route('GET', '/'),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
78
framework/core/src/Api/Resource/MailSettingResource.php
Normal file
78
framework/core/src/Api/Resource/MailSettingResource.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
namespace Flarum\Api\Resource;
|
||||
|
||||
use Flarum\Api\Endpoint;
|
||||
use Flarum\Api\Resource\Contracts\Findable;
|
||||
use Flarum\Api\Schema;
|
||||
use Flarum\Mail\DriverInterface;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Validation\Factory;
|
||||
use stdClass;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
|
||||
class MailSettingResource extends AbstractResource implements Findable
|
||||
{
|
||||
public function __construct(
|
||||
protected SettingsRepositoryInterface $settings,
|
||||
protected Factory $validator,
|
||||
protected Container $container
|
||||
) {
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
{
|
||||
return 'mail-settings';
|
||||
}
|
||||
|
||||
public function getId(object $model, Context $context): string
|
||||
{
|
||||
return '1';
|
||||
}
|
||||
|
||||
public function id(Context $context): ?string
|
||||
{
|
||||
return '1';
|
||||
}
|
||||
|
||||
public function find(string $id, Context $context): ?object
|
||||
{
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
public function endpoints(): array
|
||||
{
|
||||
return [
|
||||
Endpoint\Show::make()
|
||||
->route('GET', '/')
|
||||
->admin(),
|
||||
];
|
||||
}
|
||||
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
Schema\Arr::make('fields')
|
||||
->get(function () {
|
||||
return array_map(fn (DriverInterface $driver) => $driver->availableSettings(), array_map(function ($driver) {
|
||||
return $this->container->make($driver);
|
||||
}, $this->container->make('mail.supported_drivers')));
|
||||
}),
|
||||
Schema\Boolean::make('sending')
|
||||
->get(function () {
|
||||
/** @var DriverInterface $actual */
|
||||
$actual = $this->container->make('mail.driver');
|
||||
|
||||
return $actual->canSend();
|
||||
}),
|
||||
Schema\Arr::make('errors')
|
||||
->get(function () {
|
||||
/** @var DriverInterface $configured */
|
||||
$configured = $this->container->make('flarum.mail.configured_driver');
|
||||
|
||||
return $configured->validate($this->settings, $this->validator);
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -6,11 +6,15 @@ use Flarum\Api\Context;
|
|||
use Flarum\Api\Endpoint;
|
||||
use Flarum\Api\Schema;
|
||||
use Flarum\Api\Sort\SortColumn;
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Foundation\ValidationException;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\Locale\TranslatorInterface;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\AvatarUploader;
|
||||
use Flarum\User\Command\DeleteAvatar;
|
||||
use Flarum\User\Command\UploadAvatar;
|
||||
use Flarum\User\Event\Deleting;
|
||||
use Flarum\User\Event\GroupsChanged;
|
||||
use Flarum\User\Event\RegisteringFromProvider;
|
||||
|
@ -32,7 +36,8 @@ class UserResource extends AbstractDatabaseResource
|
|||
protected SlugManager $slugManager,
|
||||
protected SettingsRepositoryInterface $settings,
|
||||
protected ImageManager $imageManager,
|
||||
protected AvatarUploader $avatarUploader
|
||||
protected AvatarUploader $avatarUploader,
|
||||
protected Dispatcher $bus,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -105,6 +110,22 @@ class UserResource extends AbstractDatabaseResource
|
|||
->can('searchUsers')
|
||||
->defaultInclude(['groups'])
|
||||
->paginate(),
|
||||
Endpoint\Endpoint::make('avatar.upload')
|
||||
->route('POST', '/{id}/avatar')
|
||||
->action(function (Context $context) {
|
||||
$file = Arr::get($context->request->getUploadedFiles(), 'avatar');
|
||||
|
||||
return $this->bus->dispatch(
|
||||
new UploadAvatar((int) $context->modelId, $file, $context->getActor())
|
||||
);
|
||||
}),
|
||||
Endpoint\Endpoint::make('avatar.delete')
|
||||
->route('DELETE', '/{id}/avatar')
|
||||
->action(function (Context $context) {
|
||||
return $this->bus->dispatch(
|
||||
new DeleteAvatar(Arr::get($context->request->getQueryParams(), 'id'), $context->getActor())
|
||||
);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,234 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Closure;
|
||||
use DateTime;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Tobscure\JsonApi\AbstractSerializer as BaseAbstractSerializer;
|
||||
use Tobscure\JsonApi\Collection;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
use Tobscure\JsonApi\Resource;
|
||||
use Tobscure\JsonApi\SerializerInterface;
|
||||
|
||||
abstract class AbstractSerializer extends BaseAbstractSerializer
|
||||
{
|
||||
protected Request $request;
|
||||
protected User $actor;
|
||||
protected static Container $container;
|
||||
|
||||
/**
|
||||
* @var array<string, callable[]>
|
||||
*/
|
||||
protected static array $attributeMutators = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, callable>>
|
||||
*/
|
||||
protected static array $customRelations = [];
|
||||
|
||||
public function getRequest(): Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function setRequest(Request $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->actor = RequestUtil::getActor($request);
|
||||
}
|
||||
|
||||
public function getActor(): User
|
||||
{
|
||||
return $this->actor;
|
||||
}
|
||||
|
||||
public function getAttributes(mixed $model, array $fields = null): array
|
||||
{
|
||||
if (! is_object($model) && ! is_array($model)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = $this->getDefaultAttributes($model);
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$attributeMutators[$class])) {
|
||||
foreach (static::$attributeMutators[$class] as $callback) {
|
||||
$attributes = array_merge(
|
||||
$attributes,
|
||||
$callback($this, $model, $attributes)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default set of serialized attributes for a model.
|
||||
*/
|
||||
abstract protected function getDefaultAttributes(object|array $model): array;
|
||||
|
||||
public function formatDate(DateTime $date = null): ?string
|
||||
{
|
||||
return $date?->format(DateTime::RFC3339);
|
||||
}
|
||||
|
||||
public function getRelationship($model, $name)
|
||||
{
|
||||
if ($relationship = $this->getCustomRelationship($model, $name)) {
|
||||
return $relationship;
|
||||
}
|
||||
|
||||
return parent::getRelationship($model, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a custom relationship.
|
||||
*/
|
||||
protected function getCustomRelationship(object|array $model, string $name): ?Relationship
|
||||
{
|
||||
foreach (array_merge([static::class], class_parents($this)) as $class) {
|
||||
$callback = Arr::get(static::$customRelations, "$class.$name");
|
||||
|
||||
if (is_callable($callback)) {
|
||||
$relationship = $callback($this, $model);
|
||||
|
||||
if (isset($relationship) && ! ($relationship instanceof Relationship)) {
|
||||
throw new LogicException(
|
||||
'GetApiRelationship handler must return an instance of '.Relationship::class
|
||||
);
|
||||
}
|
||||
|
||||
return $relationship;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relationship builder for a has-one relationship.
|
||||
*/
|
||||
public function hasOne(object|array $model, SerializerInterface|Closure|string $serializer, string $relation = null): ?Relationship
|
||||
{
|
||||
return $this->buildRelationship($model, $serializer, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relationship builder for a has-many relationship.
|
||||
*/
|
||||
public function hasMany(object|array $model, SerializerInterface|Closure|string $serializer, string $relation = null): ?Relationship
|
||||
{
|
||||
return $this->buildRelationship($model, $serializer, $relation, true);
|
||||
}
|
||||
|
||||
protected function buildRelationship(object|array $model, SerializerInterface|Closure|string $serializer, string $relation = null, bool $many = false): ?Relationship
|
||||
{
|
||||
if (is_null($relation)) {
|
||||
list(, , $caller) = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3);
|
||||
|
||||
$relation = $caller['function'];
|
||||
}
|
||||
|
||||
$data = $this->getRelationshipData($model, $relation);
|
||||
|
||||
if ($data) {
|
||||
$serializer = $this->resolveSerializer($serializer, $model, $data);
|
||||
|
||||
$type = $many ? Collection::class : Resource::class;
|
||||
|
||||
$element = new $type($data, $serializer);
|
||||
|
||||
return new Relationship($element);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getRelationshipData(object|array $model, string $relation): mixed
|
||||
{
|
||||
if (is_object($model)) {
|
||||
return $model->$relation;
|
||||
}
|
||||
|
||||
return $model[$relation];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function resolveSerializer(SerializerInterface|Closure|string $serializer, object|array $model, mixed $data): SerializerInterface
|
||||
{
|
||||
if ($serializer instanceof Closure) {
|
||||
$serializer = call_user_func($serializer, $model, $data);
|
||||
}
|
||||
|
||||
if (is_string($serializer)) {
|
||||
$serializer = $this->resolveSerializerClass($serializer);
|
||||
}
|
||||
|
||||
if (! ($serializer instanceof SerializerInterface)) {
|
||||
throw new InvalidArgumentException('Serializer must be an instance of '
|
||||
.SerializerInterface::class);
|
||||
}
|
||||
|
||||
return $serializer;
|
||||
}
|
||||
|
||||
protected function resolveSerializerClass(string $class): object
|
||||
{
|
||||
$serializer = static::$container->make($class);
|
||||
|
||||
$serializer->setRequest($this->request);
|
||||
|
||||
return $serializer;
|
||||
}
|
||||
|
||||
public static function getContainer(): Container
|
||||
{
|
||||
return static::$container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function setContainer(Container $container): void
|
||||
{
|
||||
static::$container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function addAttributeMutator(string $serializerClass, callable $callback): void
|
||||
{
|
||||
if (! isset(static::$attributeMutators[$serializerClass])) {
|
||||
static::$attributeMutators[$serializerClass] = [];
|
||||
}
|
||||
|
||||
static::$attributeMutators[$serializerClass][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function setRelationship(string $serializerClass, string $relation, callable $callback): void
|
||||
{
|
||||
static::$customRelations[$serializerClass][$relation] = $callback;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Http\AccessToken;
|
||||
use Flarum\Locale\TranslatorInterface;
|
||||
use InvalidArgumentException;
|
||||
use Jenssegers\Agent\Agent;
|
||||
|
||||
class AccessTokenSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'access-tokens';
|
||||
|
||||
public function __construct(
|
||||
protected TranslatorInterface $translator
|
||||
) {
|
||||
}
|
||||
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
if (! ($model instanceof AccessToken)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.AccessToken::class
|
||||
);
|
||||
}
|
||||
|
||||
$session = $this->request->getAttribute('session');
|
||||
|
||||
$agent = new Agent();
|
||||
$agent->setUserAgent($model->last_user_agent);
|
||||
|
||||
$attributes = [
|
||||
'token' => $model->token,
|
||||
'userId' => $model->user_id,
|
||||
'createdAt' => $this->formatDate($model->created_at),
|
||||
'lastActivityAt' => $this->formatDate($model->last_activity_at),
|
||||
'isCurrent' => $session && $session->get('access_token') === $model->token,
|
||||
'isSessionToken' => in_array($model->type, ['session', 'session_remember'], true),
|
||||
'title' => $model->title,
|
||||
'lastIpAddress' => $model->last_ip_address,
|
||||
'device' => $this->translator->trans('core.forum.security.browser_on_operating_system', [
|
||||
'browser' => $agent->browser(),
|
||||
'os' => $agent->platform(),
|
||||
]),
|
||||
];
|
||||
|
||||
// Unset hidden attributes (like the token value on session tokens)
|
||||
foreach ($model->getHidden() as $name) {
|
||||
unset($attributes[$name]);
|
||||
}
|
||||
|
||||
// Hide the token value to non-actors no matter who they are.
|
||||
if (isset($attributes['token']) && $this->getActor()->id !== $model->user_id) {
|
||||
unset($attributes['token']);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Http\SlugManager;
|
||||
use InvalidArgumentException;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
|
||||
class BasicDiscussionSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'discussions';
|
||||
|
||||
public function __construct(
|
||||
protected SlugManager $slugManager
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
if (! ($model instanceof Discussion)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.Discussion::class
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $model->title,
|
||||
'slug' => $this->slugManager->forResource(Discussion::class)->toSlug($model),
|
||||
];
|
||||
}
|
||||
|
||||
protected function user(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasOne($discussion, BasicUserSerializer::class);
|
||||
}
|
||||
|
||||
protected function firstPost(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasOne($discussion, BasicPostSerializer::class);
|
||||
}
|
||||
|
||||
protected function lastPostedUser(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasOne($discussion, BasicUserSerializer::class);
|
||||
}
|
||||
|
||||
protected function lastPost(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasOne($discussion, BasicPostSerializer::class);
|
||||
}
|
||||
|
||||
protected function posts(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasMany($discussion, PostSerializer::class);
|
||||
}
|
||||
|
||||
protected function mostRelevantPost(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasOne($discussion, PostSerializer::class);
|
||||
}
|
||||
|
||||
protected function hiddenUser(Discussion $discussion): ?Relationship
|
||||
{
|
||||
return $this->hasOne($discussion, BasicUserSerializer::class);
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Exception;
|
||||
use Flarum\Foundation\ErrorHandling\LogReporter;
|
||||
use Flarum\Locale\TranslatorInterface;
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use InvalidArgumentException;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
|
||||
class BasicPostSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'posts';
|
||||
|
||||
public function __construct(
|
||||
protected LogReporter $log,
|
||||
protected TranslatorInterface $translator
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
if (! ($model instanceof Post)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.Post::class
|
||||
);
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'number' => (int) $model->number,
|
||||
'createdAt' => $this->formatDate($model->created_at),
|
||||
'contentType' => $model->type
|
||||
];
|
||||
|
||||
if ($model instanceof CommentPost) {
|
||||
try {
|
||||
$attributes['contentHtml'] = $model->formatContent($this->request);
|
||||
$attributes['renderFailed'] = false;
|
||||
} catch (Exception $e) {
|
||||
$attributes['contentHtml'] = $this->translator->trans('core.lib.error.render_failed_message');
|
||||
$this->log->report($e);
|
||||
$attributes['renderFailed'] = true;
|
||||
}
|
||||
} else {
|
||||
$attributes['content'] = $model->content;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
protected function user(Post $post): ?Relationship
|
||||
{
|
||||
return $this->hasOne($post, BasicUserSerializer::class);
|
||||
}
|
||||
|
||||
protected function discussion(Post $post): ?Relationship
|
||||
{
|
||||
return $this->hasOne($post, BasicDiscussionSerializer::class);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Http\SlugManager;
|
||||
use Flarum\User\User;
|
||||
use InvalidArgumentException;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
|
||||
class BasicUserSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'users';
|
||||
|
||||
public function __construct(
|
||||
protected SlugManager $slugManager
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
if (! ($model instanceof User)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.User::class
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'username' => $model->username,
|
||||
'displayName' => $model->display_name,
|
||||
'avatarUrl' => $model->avatar_url,
|
||||
'slug' => $this->slugManager->forResource(User::class)->toSlug($model)
|
||||
];
|
||||
}
|
||||
|
||||
protected function groups(User $user): Relationship
|
||||
{
|
||||
if ($this->getActor()->can('viewHiddenGroups')) {
|
||||
return $this->hasMany($user, GroupSerializer::class);
|
||||
}
|
||||
|
||||
return $this->hasMany($user, GroupSerializer::class, 'visibleGroups');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\User\User;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class CurrentUserSerializer extends UserSerializer
|
||||
{
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
if (! ($model instanceof User)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.User::class
|
||||
);
|
||||
}
|
||||
|
||||
$attributes = parent::getDefaultAttributes($model);
|
||||
|
||||
$attributes += [
|
||||
'isEmailConfirmed' => (bool) $model->is_email_confirmed,
|
||||
'email' => $model->email,
|
||||
'markedAllAsReadAt' => $this->formatDate($model->marked_all_as_read_at),
|
||||
'unreadNotificationCount' => (int) $model->getUnreadNotificationCount(),
|
||||
'newNotificationCount' => (int) $model->getNewNotificationCount(),
|
||||
'preferences' => (array) $model->preferences,
|
||||
'isAdmin' => $model->isAdmin(),
|
||||
];
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
|
||||
class DiscussionSerializer extends BasicDiscussionSerializer
|
||||
{
|
||||
/**
|
||||
* @param Discussion $model
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
$attributes = parent::getDefaultAttributes($model) + [
|
||||
'commentCount' => (int) $model->comment_count,
|
||||
'participantCount' => (int) $model->participant_count,
|
||||
'createdAt' => $this->formatDate($model->created_at),
|
||||
'lastPostedAt' => $this->formatDate($model->last_posted_at),
|
||||
'lastPostNumber' => (int) $model->last_post_number,
|
||||
'canReply' => $this->actor->can('reply', $model),
|
||||
'canRename' => $this->actor->can('rename', $model),
|
||||
'canDelete' => $this->actor->can('delete', $model),
|
||||
'canHide' => $this->actor->can('hide', $model)
|
||||
];
|
||||
|
||||
if ($model->hidden_at) {
|
||||
$attributes['isHidden'] = true;
|
||||
$attributes['hiddenAt'] = $this->formatDate($model->hidden_at);
|
||||
}
|
||||
|
||||
Discussion::setStateUser($this->actor);
|
||||
|
||||
if ($state = $model->state) {
|
||||
$attributes += [
|
||||
'lastReadAt' => $this->formatDate($state->last_read_at),
|
||||
'lastReadPostNumber' => (int) $state->last_read_post_number
|
||||
];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
|
||||
class ExtensionReadmeSerializer extends AbstractSerializer
|
||||
{
|
||||
/**
|
||||
* @param Extension $model
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
return [
|
||||
'content' => $model->getReadme()
|
||||
];
|
||||
}
|
||||
|
||||
public function getId($extension)
|
||||
{
|
||||
return $extension->getId();
|
||||
}
|
||||
|
||||
public function getType($extension)
|
||||
{
|
||||
return 'extension-readmes';
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Cloud;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
|
||||
class ForumSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'forums';
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var Cloud
|
||||
*/
|
||||
protected $assetsFilesystem;
|
||||
|
||||
/**
|
||||
* @param Config $config
|
||||
* @param Factory $filesystemFactory
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(Config $config, Factory $filesystemFactory, SettingsRepositoryInterface $settings, UrlGenerator $url)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->assetsFilesystem = $filesystemFactory->disk('flarum-assets');
|
||||
$this->settings = $settings;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
public function getId($model)
|
||||
{
|
||||
return '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $model
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
$attributes = [
|
||||
'title' => $this->settings->get('forum_title'),
|
||||
'description' => $this->settings->get('forum_description'),
|
||||
'showLanguageSelector' => (bool) $this->settings->get('show_language_selector', true),
|
||||
'baseUrl' => $url = $this->url->to('forum')->base(),
|
||||
'basePath' => $path = parse_url($url, PHP_URL_PATH) ?: '',
|
||||
'baseOrigin' => substr($url, 0, strlen($url) - strlen($path)),
|
||||
'debug' => $this->config->inDebugMode(),
|
||||
'apiUrl' => $this->url->to('api')->base(),
|
||||
'welcomeTitle' => $this->settings->get('welcome_title'),
|
||||
'welcomeMessage' => $this->settings->get('welcome_message'),
|
||||
'themePrimaryColor' => $this->settings->get('theme_primary_color'),
|
||||
'themeSecondaryColor' => $this->settings->get('theme_secondary_color'),
|
||||
'logoUrl' => $this->getLogoUrl(),
|
||||
'faviconUrl' => $this->getFaviconUrl(),
|
||||
'headerHtml' => $this->settings->get('custom_header'),
|
||||
'footerHtml' => $this->settings->get('custom_footer'),
|
||||
'allowSignUp' => (bool) $this->settings->get('allow_sign_up'),
|
||||
'defaultRoute' => $this->settings->get('default_route'),
|
||||
'canViewForum' => $this->actor->can('viewForum'),
|
||||
'canStartDiscussion' => $this->actor->can('startDiscussion'),
|
||||
'canSearchUsers' => $this->actor->can('searchUsers'),
|
||||
'canCreateAccessToken' => $this->actor->can('createAccessToken'),
|
||||
'canModerateAccessTokens' => $this->actor->can('moderateAccessTokens'),
|
||||
'canEditUserCredentials' => $this->actor->hasPermission('user.editCredentials'),
|
||||
'assetsBaseUrl' => rtrim($this->assetsFilesystem->url(''), '/'),
|
||||
'jsChunksBaseUrl' => $this->assetsFilesystem->url('js'),
|
||||
];
|
||||
|
||||
if ($this->actor->can('administrate')) {
|
||||
$attributes['adminUrl'] = $this->url->to('admin')->base();
|
||||
$attributes['version'] = Application::VERSION;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
protected function groups(array $model): ?Relationship
|
||||
{
|
||||
return $this->hasMany($model, GroupSerializer::class);
|
||||
}
|
||||
|
||||
protected function getLogoUrl(): ?string
|
||||
{
|
||||
$logoPath = $this->settings->get('logo_path');
|
||||
|
||||
return $logoPath ? $this->getAssetUrl($logoPath) : null;
|
||||
}
|
||||
|
||||
protected function getFaviconUrl(): ?string
|
||||
{
|
||||
$faviconPath = $this->settings->get('favicon_path');
|
||||
|
||||
return $faviconPath ? $this->getAssetUrl($faviconPath) : null;
|
||||
}
|
||||
|
||||
public function getAssetUrl(string $assetPath): string
|
||||
{
|
||||
return $this->assetsFilesystem->url($assetPath);
|
||||
}
|
||||
|
||||
protected function actor(array $model): ?Relationship
|
||||
{
|
||||
return $this->hasOne($model, CurrentUserSerializer::class);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Mail\DriverInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class MailSettingsSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'mail-settings';
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
return [
|
||||
'fields' => array_map([$this, 'serializeDriver'], $model['drivers']),
|
||||
'sending' => $model['sending'],
|
||||
'errors' => $model['errors'],
|
||||
];
|
||||
}
|
||||
|
||||
private function serializeDriver(DriverInterface $driver): array
|
||||
{
|
||||
return $driver->availableSettings();
|
||||
}
|
||||
|
||||
public function getId($model)
|
||||
{
|
||||
return 'global';
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Notification\Notification;
|
||||
use InvalidArgumentException;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
|
||||
class NotificationSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'notifications';
|
||||
|
||||
/**
|
||||
* A map of notification types (key) to the serializer that should be used
|
||||
* to output the notification's subject (value).
|
||||
*/
|
||||
protected static array $subjectSerializers = [];
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
if (! ($model instanceof Notification)) {
|
||||
throw new InvalidArgumentException(
|
||||
$this::class.' can only serialize instances of '.Notification::class
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'contentType' => $model->type,
|
||||
'content' => $model->data,
|
||||
'createdAt' => $this->formatDate($model->created_at),
|
||||
'isRead' => (bool) $model->read_at
|
||||
];
|
||||
}
|
||||
|
||||
protected function user(Notification $notification): ?Relationship
|
||||
{
|
||||
return $this->hasOne($notification, BasicUserSerializer::class);
|
||||
}
|
||||
|
||||
protected function fromUser(Notification $notification): ?Relationship
|
||||
{
|
||||
return $this->hasOne($notification, BasicUserSerializer::class);
|
||||
}
|
||||
|
||||
protected function subject(Notification $notification): ?Relationship
|
||||
{
|
||||
return $this->hasOne($notification, function (Notification $notification) {
|
||||
return static::$subjectSerializers[$notification->type];
|
||||
});
|
||||
}
|
||||
|
||||
public static function setSubjectSerializer(string $type, string $serializer): void
|
||||
{
|
||||
static::$subjectSerializers[$type] = $serializer;
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\Post\CommentPost;
|
||||
use Flarum\Post\Post;
|
||||
use Tobscure\JsonApi\Relationship;
|
||||
|
||||
class PostSerializer extends BasicPostSerializer
|
||||
{
|
||||
/**
|
||||
* @param Post $model
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
$attributes = parent::getDefaultAttributes($model);
|
||||
|
||||
unset($attributes['content']);
|
||||
|
||||
$canEdit = $this->actor->can('edit', $model);
|
||||
|
||||
if ($model instanceof CommentPost) {
|
||||
if ($canEdit) {
|
||||
$attributes['content'] = $model->content;
|
||||
}
|
||||
if ($this->actor->can('viewIps', $model)) {
|
||||
$attributes['ipAddress'] = $model->ip_address;
|
||||
}
|
||||
} else {
|
||||
$attributes['content'] = $model->content;
|
||||
}
|
||||
|
||||
if ($model->edited_at) {
|
||||
$attributes['editedAt'] = $this->formatDate($model->edited_at);
|
||||
}
|
||||
|
||||
if ($model->hidden_at) {
|
||||
$attributes['isHidden'] = true;
|
||||
$attributes['hiddenAt'] = $this->formatDate($model->hidden_at);
|
||||
}
|
||||
|
||||
$attributes += [
|
||||
'canEdit' => $canEdit,
|
||||
'canDelete' => $this->actor->can('delete', $model),
|
||||
'canHide' => $this->actor->can('hide', $model)
|
||||
];
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
protected function user(Post $post): ?Relationship
|
||||
{
|
||||
return $this->hasOne($post, UserSerializer::class);
|
||||
}
|
||||
|
||||
protected function discussion(Post $post): ?Relationship
|
||||
{
|
||||
return $this->hasOne($post, BasicDiscussionSerializer::class);
|
||||
}
|
||||
|
||||
protected function editedUser(Post $post): ?Relationship
|
||||
{
|
||||
return $this->hasOne($post, BasicUserSerializer::class);
|
||||
}
|
||||
|
||||
protected function hiddenUser(Post $post): ?Relationship
|
||||
{
|
||||
return $this->hasOne($post, BasicUserSerializer::class);
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Api\Serializer;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
||||
class UserSerializer extends BasicUserSerializer
|
||||
{
|
||||
/**
|
||||
* @param User $model
|
||||
*/
|
||||
protected function getDefaultAttributes(object|array $model): array
|
||||
{
|
||||
$attributes = parent::getDefaultAttributes($model);
|
||||
|
||||
$attributes += [
|
||||
'joinTime' => $this->formatDate($model->joined_at),
|
||||
'discussionCount' => (int) $model->discussion_count,
|
||||
'commentCount' => (int) $model->comment_count,
|
||||
'canEdit' => $this->actor->can('edit', $model),
|
||||
'canEditCredentials' => $this->actor->can('editCredentials', $model),
|
||||
'canEditGroups' => $this->actor->can('editGroups', $model),
|
||||
'canDelete' => $this->actor->can('delete', $model),
|
||||
];
|
||||
|
||||
if ($model->getPreference('discloseOnline') || $this->actor->can('viewLastSeenAt', $model)) {
|
||||
$attributes += [
|
||||
'lastSeenAt' => $this->formatDate($model->last_seen_at)
|
||||
];
|
||||
}
|
||||
|
||||
if ($attributes['canEditCredentials'] || $this->actor->id === $model->id) {
|
||||
$attributes += [
|
||||
'isEmailConfirmed' => (bool) $model->is_email_confirmed,
|
||||
'email' => $model->email
|
||||
];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
|
@ -46,20 +46,6 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
|||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// Upload avatar
|
||||
$map->post(
|
||||
'/users/{id}/avatar',
|
||||
'users.avatar.upload',
|
||||
$route->toController(Controller\UploadAvatarController::class)
|
||||
);
|
||||
|
||||
// Remove avatar
|
||||
$map->delete(
|
||||
'/users/{id}/avatar',
|
||||
'users.avatar.delete',
|
||||
$route->toController(Controller\DeleteAvatarController::class)
|
||||
);
|
||||
|
||||
// send confirmation email
|
||||
$map->post(
|
||||
'/users/{id}/send-confirmation',
|
||||
|
@ -107,13 +93,6 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
|||
$route->toController(Controller\UninstallExtensionController::class)
|
||||
);
|
||||
|
||||
// Get readme for an extension
|
||||
$map->get(
|
||||
'/extension-readmes/{name}',
|
||||
'extension-readmes.show',
|
||||
$route->toController(Controller\ShowExtensionReadmeController::class)
|
||||
);
|
||||
|
||||
// Update settings
|
||||
$map->post(
|
||||
'/settings',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Api\Controller\AbstractSerializeController;
|
||||
use Flarum\Api\Endpoint\Endpoint;
|
||||
use Flarum\Api\Endpoint\EndpointInterface;
|
||||
use Flarum\Api\Resource\Contracts\Collection;
|
||||
use Flarum\Api\Resource\Contracts\Resource;
|
||||
use Flarum\Extension\Extension;
|
||||
|
@ -64,7 +64,7 @@ class ApiResource implements ExtenderInterface
|
|||
/**
|
||||
* Modify an endpoint.
|
||||
*
|
||||
* @param class-string<\Flarum\Api\Endpoint\Endpoint>|array<\Flarum\Api\Endpoint\Endpoint> $endpointClass the class name of the endpoint.
|
||||
* @param class-string<\Flarum\Api\Endpoint\EndpointInterface>|array<\Flarum\Api\Endpoint\EndpointInterface> $endpointClass the class name of the endpoint.
|
||||
* or an array of class names of the endpoints.
|
||||
* @param callable|class-string $mutator a callable that accepts an endpoint and returns the modified endpoint.
|
||||
*/
|
||||
|
@ -182,7 +182,7 @@ class ApiResource implements ExtenderInterface
|
|||
[$endpointsToRemove, $condition] = $removeEndpointClass;
|
||||
|
||||
if ($this->isApplicable($condition, $resource, $container)) {
|
||||
$endpoints = array_filter($endpoints, fn (Endpoint $endpoint) => ! in_array($endpoint::class, $endpointsToRemove));
|
||||
$endpoints = array_filter($endpoints, fn (EndpointInterface $endpoint) => ! in_array($endpoint::class, $endpointsToRemove));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,8 +194,8 @@ class ApiResource implements ExtenderInterface
|
|||
$mutateEndpoint = ContainerUtil::wrapCallback($mutator, $container);
|
||||
$endpoint = $mutateEndpoint($endpoint, $resource);
|
||||
|
||||
if (! $endpoint instanceof Endpoint) {
|
||||
throw new \RuntimeException('The endpoint mutator must return an instance of ' . Endpoint::class);
|
||||
if (! $endpoint instanceof EndpointInterface) {
|
||||
throw new \RuntimeException('The endpoint mutator must return an instance of ' . EndpointInterface::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ namespace Flarum\Foundation\ErrorHandling;
|
|||
use Flarum\Api\JsonApiResponse;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
/**
|
||||
* A formatter to render exceptions as valid {JSON:API} error object.
|
||||
|
@ -28,15 +27,13 @@ class JsonApiFormatter implements HttpFormatter
|
|||
|
||||
public function format(HandledError $error, Request $request): Response
|
||||
{
|
||||
$document = new Document;
|
||||
|
||||
if ($error->hasDetails()) {
|
||||
$document->setErrors($this->withDetails($error));
|
||||
$errors = $this->withDetails($error);
|
||||
} else {
|
||||
$document->setErrors($this->default($error));
|
||||
$errors = $this->default($error);
|
||||
}
|
||||
|
||||
return new JsonApiResponse($document, $error->getStatusCode());
|
||||
return new JsonApiResponse(compact('errors'), $error->getStatusCode());
|
||||
}
|
||||
|
||||
private function default(HandledError $error): array
|
||||
|
|
|
@ -15,7 +15,6 @@ use Laminas\Diactoros\Response\JsonResponse;
|
|||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class MaintenanceModeHandler implements RequestHandlerInterface
|
||||
{
|
||||
|
@ -46,10 +45,12 @@ class MaintenanceModeHandler implements RequestHandlerInterface
|
|||
private function apiResponse(): ResponseInterface
|
||||
{
|
||||
return new JsonResponse(
|
||||
(new Document)->setErrors([
|
||||
'status' => '503',
|
||||
'title' => self::MESSAGE
|
||||
]),
|
||||
[
|
||||
'errors' => [
|
||||
'status' => '503',
|
||||
'title' => self::MESSAGE
|
||||
],
|
||||
],
|
||||
503,
|
||||
['Content-Type' => 'application/vnd.api+json']
|
||||
);
|
||||
|
|
|
@ -41,11 +41,10 @@ class RouteHandlerFactory
|
|||
|
||||
/**
|
||||
* @param class-string<\Tobyz\JsonApiServer\Resource\AbstractResource> $resourceClass
|
||||
* @param class-string<\Flarum\Api\Endpoint\Endpoint> $endpointClass
|
||||
*/
|
||||
public function toApiResource(string $resourceClass, string $endpointClass): Closure
|
||||
public function toApiResource(string $resourceClass, string $endpointName): Closure
|
||||
{
|
||||
return function (Request $request, array $routeParams) use ($resourceClass, $endpointClass) {
|
||||
return function (Request $request, array $routeParams) use ($resourceClass, $endpointName) {
|
||||
/** @var JsonApi $api */
|
||||
$api = $this->container->make(JsonApi::class);
|
||||
|
||||
|
@ -54,7 +53,7 @@ class RouteHandlerFactory
|
|||
$request = $request->withQueryParams(array_merge($request->getQueryParams(), $routeParams));
|
||||
|
||||
return $api->forResource($resourceClass)
|
||||
->forEndpoint($endpointClass)
|
||||
->forEndpoint($endpointName)
|
||||
->handle($request);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\api;
|
||||
|
||||
use Flarum\Api\Controller\AbstractSerializeController;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Tobscure\JsonApi\ElementInterface;
|
||||
use Tobscure\JsonApi\SerializerInterface;
|
||||
|
||||
class AbstractSerializeControllerTest extends TestCase
|
||||
{
|
||||
public function test_missing_serializer_class_throws_exception()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Routes('api'))
|
||||
->get('/dummy-serialize', 'dummy-serialize', DummySerializeController::class)
|
||||
);
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/dummy-serialize')
|
||||
);
|
||||
|
||||
$json = json_decode($contents = (string) $response->getBody(), true);
|
||||
|
||||
$this->assertEquals(500, $response->getStatusCode(), $contents);
|
||||
$this->assertStringStartsWith('InvalidArgumentException: Serializer required for controller: '.DummySerializeController::class, $json['errors'][0]['detail']);
|
||||
}
|
||||
}
|
||||
|
||||
class DummySerializeController extends AbstractSerializeController
|
||||
{
|
||||
public ?string $serializer = null;
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document): mixed
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function createElement(mixed $data, SerializerInterface $serializer): ElementInterface
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -32,8 +32,8 @@ class EventTest extends TestCase
|
|||
$api = $this->app()->getContainer()->make(JsonApi::class);
|
||||
|
||||
return $api->forResource(GroupResource::class)
|
||||
->forEndpoint(Create::class)
|
||||
->execute(
|
||||
->forEndpoint('create')
|
||||
->process(
|
||||
body: [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
|
|
|
@ -34,7 +34,11 @@ class MailTest extends TestCase
|
|||
])
|
||||
);
|
||||
|
||||
$fields = json_decode($response->getBody()->getContents(), true)['data']['attributes']['fields'];
|
||||
$body = $response->getBody()->getContents();
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode(), $body);
|
||||
|
||||
$fields = json_decode($body, true)['data']['attributes']['fields'];
|
||||
|
||||
// The custom driver does not exist
|
||||
$this->assertArrayNotHasKey('custom', $fields);
|
||||
|
|
|
@ -101,8 +101,8 @@ class DiscussionPolicyTest extends TestCase
|
|||
|
||||
$api
|
||||
->forResource(PostResource::class)
|
||||
->forEndpoint(Create::class)
|
||||
->execute(
|
||||
->forEndpoint('create')
|
||||
->process(
|
||||
body: [
|
||||
'data' => [
|
||||
'attributes' => [
|
||||
|
|
Loading…
Reference in New Issue
Block a user