diff --git a/framework/core/src/Api/Actions/ActionInterface.php b/framework/core/src/Api/Actions/ActionInterface.php new file mode 100644 index 000000000..db5b66ea2 --- /dev/null +++ b/framework/core/src/Api/Actions/ActionInterface.php @@ -0,0 +1,14 @@ +params = $params; - } - - public function get($key, $default = null) - { - return array_get($this->params, $key, $default); - } - - public function range($key, $default = null, $min = null, $max = null) - { - $value = (int) $this->get($key, $default); - - if (! is_null($min)) { - $value = max($value, $min); - } - if (! is_null($max)) { - $value = min($value, $max); - } - return $value; - } - - public function included($available) - { - $requested = explode(',', $this->get('include')); - return array_intersect((array) $available, $requested); - } - - // public function explodeIds($ids) - // { - // return array_unique(array_map('intval', array_filter(explode(',', $ids)))); - // } - - public function in($key, $options) - { - $value = $this->get($key); - - if (array_key_exists($key, $options)) { - return $options[$key]; - } - if (! in_array($value, $options)) { - $value = reset($options); - } - - return $value; - } - - public function sort($options) - { - $criteria = (string) $this->get('sort', ''); - $order = null; - - if ($criteria && $criteria[0] == '-') { - $order = 'desc'; - $criteria = substr($criteria, 1); - } - - if (! in_array($criteria, $options)) { - $criteria = reset($options); - } - - if ($criteria && ! $order) { - $order = 'asc'; - } - - return [ - 'field' => $criteria, - 'order' => $order, - 'string' => ($order == 'desc' ? '-' : '').$criteria - ]; - } - - public function start() - { - return $this->range('start', 0, 0); - } - - public function count($default, $max = 100) - { - return $this->range('count', $default, 1, $max); - } -} diff --git a/framework/core/src/Api/Actions/BaseAction.php b/framework/core/src/Api/Actions/BaseAction.php deleted file mode 100644 index 4a6e9ed52..000000000 --- a/framework/core/src/Api/Actions/BaseAction.php +++ /dev/null @@ -1,115 +0,0 @@ -actor = $actor; - $this->bus = $bus; - } - - public function handle(Request $request, $routeParams = []) - { - if (str_contains($request->header('CONTENT_TYPE'), 'application/vnd.api+json')) { - $input = $request->json(); - } else { - $input = $request; - } - $params = array_merge($input->all(), $routeParams); - - return $this->call($params); - } - - public function call($params = []) - { - $params = new ApiParams($params); - - return $this->run($params); - } - - /** - * @param ApiParams $params - * @return mixed - */ - protected function run(ApiParams $params) - { - // Should be implemented by subclasses - } - - public function hydrate($object, $params) - { - foreach ($params as $k => $v) { - $object->$k = $v; - } - } - - protected function dispatch($command, $params = []) - { - $this->event(new CommandWillBeDispatched($command, $params)); - return $this->bus->dispatch($command); - } - - protected function event($event) - { - event($event); - } - - public function document() - { - return new Document; - } - - protected function buildUrl($route, $params = [], $input = []) - { - $url = route('flarum.api.'.$route, $params); - $queryString = $input ? '?'.http_build_query($input) : ''; - - return $url.$queryString; - } - - protected function respondWithoutContent($statusCode = 204, $headers = []) - { - return Response::make('', $statusCode, $headers); - } - - protected function respondWithArray($array, $statusCode = 200, $headers = []) - { - return Response::json($array, $statusCode, $headers); - } - - protected function respondWithDocument($document, $statusCode = 200, $headers = []) - { - $headers['Content-Type'] = 'application/vnd.api+json'; - - $this->event(new WillRespondWithDocument($document, $statusCode, $headers)); - - return $this->respondWithArray($document->toArray(), $statusCode, $headers); - } - - protected function respondWithErrors($errors, $httpCode = 500) - { - return Response::json(['errors' => $errors], $httpCode); - } - - protected function respondWithError($error, $httpCode = 500, $detail = null) - { - $error = ['code' => $error]; - - if ($detail) { - $error['detail'] = $detail; - } - - return $this->respondWithErrors([$error], $httpCode); - } -} diff --git a/framework/core/src/Api/Actions/CreateAction.php b/framework/core/src/Api/Actions/CreateAction.php new file mode 100644 index 000000000..26b09c939 --- /dev/null +++ b/framework/core/src/Api/Actions/CreateAction.php @@ -0,0 +1,29 @@ +setStatusCode(201); + + return $this->create($request, $response); + } + + /** + * Create the resource. + * + * @return \Flarum\Core\Models\Model + */ + abstract protected function create(JsonApiRequest $request, JsonApiResponse $response); +} diff --git a/framework/core/src/Api/Actions/DeleteAction.php b/framework/core/src/Api/Actions/DeleteAction.php new file mode 100644 index 000000000..b247e7bd5 --- /dev/null +++ b/framework/core/src/Api/Actions/DeleteAction.php @@ -0,0 +1,30 @@ +delete($request, $response = new Response('', 204)); + + return $response; + } + + /** + * Delete the resource. + * + * @param \Flarum\Api\Request $request + * @param \Flarum\Api\Response $response + * @return void + */ + abstract protected function delete(Request $request, Response $response); +} diff --git a/framework/core/src/Api/Actions/Discussions/CreateAction.php b/framework/core/src/Api/Actions/Discussions/CreateAction.php index 7115cde1d..d48d370af 100644 --- a/framework/core/src/Api/Actions/Discussions/CreateAction.php +++ b/framework/core/src/Api/Actions/Discussions/CreateAction.php @@ -2,40 +2,78 @@ use Flarum\Core\Commands\StartDiscussionCommand; use Flarum\Core\Commands\ReadDiscussionCommand; -use Flarum\Api\Actions\BaseAction; -use Flarum\Api\Actions\ApiParams; -use Flarum\Api\Serializers\DiscussionSerializer; +use Flarum\Core\Models\Forum; +use Flarum\Api\Actions\CreateAction as BaseCreateAction; +use Flarum\Api\JsonApiRequest; +use Flarum\Api\JsonApiResponse; +use Illuminate\Contracts\Bus\Dispatcher; -class CreateAction extends BaseAction +class CreateAction extends BaseCreateAction { /** - * Start a new discussion. + * The command bus. * - * @return Response + * @var \Illuminate\Contracts\Bus\Dispatcher */ - protected function run(ApiParams $params) - { - // By default, the only required attributes of a discussion are the - // title and the content. We'll extract these from the rbaseequest data - // and pass them through to the StartDiscussionCommand. - $title = $params->get('data.title'); - $content = $params->get('data.content'); - $user = $this->actor->getUser(); + protected $bus; - $command = new StartDiscussionCommand($title, $content, $user, app('flarum.forum')); - $discussion = $this->dispatch($command, $params); + /** + * The default forum instance. + * + * @var \Flarum\Core\Models\Forum + */ + protected $forum; + + /** + * The name of the serializer class to output results with. + * + * @var string + */ + public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + + /** + * The relations that are included by default. + * + * @var array + */ + public static $include = ['posts', 'startUser', 'lastUser', 'startPost', 'lastPost']; + + /** + * Initialize the action. + * + * @param \Illuminate\Contracts\Bus\Dispatcher $bus + * @param \Flarum\Core\Models\Forum $forum + */ + public function __construct(Dispatcher $bus, Forum $forum) + { + $this->bus = $bus; + $this->forum = $forum; + } + + /** + * Create a discussion according to input from the API request. + * + * @param \Flarum\Api\JsonApiRequest $request + * @param \Flarum\Api\JsonApiResponse $response + * @return \Flarum\Core\Models\Discussion + */ + protected function create(JsonApiRequest $request, JsonApiResponse $response) + { + $user = $request->actor->getUser(); + + $discussion = $this->bus->dispatch( + new StartDiscussionCommand($user, $this->forum, $request->get('data')) + ); // After creating the discussion, we assume that the user has seen all // of the posts in the discussion; thus, we will mark the discussion // as read if they are logged in. if ($user->exists) { - $command = new ReadDiscussionCommand($discussion->id, $user, 1); - $this->dispatch($command, $params); + $this->bus->dispatch( + new ReadDiscussionCommand($discussion->id, $user, 1) + ); } - $serializer = new DiscussionSerializer(['posts', 'startUser', 'lastUser', 'startPost', 'lastPost']); - $document = $this->document()->setData($serializer->resource($discussion)); - - return $this->respondWithDocument($document); + return $discussion; } } diff --git a/framework/core/src/Api/Actions/Discussions/DeleteAction.php b/framework/core/src/Api/Actions/Discussions/DeleteAction.php index 78f8a7fab..637713d27 100644 --- a/framework/core/src/Api/Actions/Discussions/DeleteAction.php +++ b/framework/core/src/Api/Actions/Discussions/DeleteAction.php @@ -1,23 +1,40 @@ get('id'); + $this->bus = $bus; + } - $command = new DeleteDiscussionCommand($discussionId, $this->actor->getUser()); - $this->dispatch($command, $params); - - return $this->respondWithoutContent(); + /** + * Delete a discussion according to input from the API request. + * + * @param \Flarum\Api\Request $request + * @return void + */ + protected function delete(Request $request, Response $response) + { + $this->bus->dispatch( + new DeleteDiscussionCommand($request->get('id'), $request->actor->getUser()) + ); } } diff --git a/framework/core/src/Api/Actions/Discussions/IndexAction.php b/framework/core/src/Api/Actions/Discussions/IndexAction.php index 1d995ebd1..8051bd2f2 100644 --- a/framework/core/src/Api/Actions/Discussions/IndexAction.php +++ b/framework/core/src/Api/Actions/Discussions/IndexAction.php @@ -2,12 +2,11 @@ use Flarum\Core\Search\Discussions\DiscussionSearchCriteria; use Flarum\Core\Search\Discussions\DiscussionSearcher; -use Flarum\Support\Actor; -use Flarum\Api\Actions\BaseAction; -use Flarum\Api\Actions\ApiParams; -use Flarum\Api\Serializers\DiscussionSerializer; +use Flarum\Api\Actions\SerializeCollectionAction; +use Flarum\Api\JsonApiRequest; +use Flarum\Api\JsonApiResponse; -class IndexAction extends BaseAction +class IndexAction extends SerializeCollectionAction { /** * The discussion searcher. @@ -16,62 +15,90 @@ class IndexAction extends BaseAction */ protected $searcher; + /** + * The name of the serializer class to output results with. + * + * @var string + */ + public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + + /** + * The relations that are available to be included. + * + * @var array + */ + public static $includeAvailable = ['startUser', 'lastUser', 'startPost', 'lastPost', 'relevantPosts']; + + /** + * The relations that are included by default. + * + * @var array + */ + public static $include = ['startUser', 'lastUser']; + + /** + * The maximum number of records that can be requested. + * + * @var integer + */ + public static $limitMax = 50; + + /** + * The number of records included by default. + * + * @var integer + */ + public static $limit = 20; + + /** + * The fields that are available to be sorted by. + * + * @var array + */ + public static $sortAvailable = ['lastTime', 'commentsCount', 'startTime']; + + /** + * The default field to sort by. + * + * @var string + */ + public static $sort = ['lastTime' => 'desc']; + /** * Instantiate the action. * * @param \Flarum\Core\Search\Discussions\DiscussionSearcher $searcher */ - public function __construct(Actor $actor, DiscussionSearcher $searcher) + public function __construct(DiscussionSearcher $searcher) { - $this->actor = $actor; $this->searcher = $searcher; } /** - * Show a list of discussions. + * Get the discussion results, ready to be serialized and assigned to the + * document response. * - * @return \Illuminate\Http\Response + * @param \Flarum\Api\JsonApiRequest $request + * @param \Flarum\Api\JsonApiResponse $response + * @return \Illuminate\Database\Eloquent\Collection */ - protected function run(ApiParams $params) + protected function data(JsonApiRequest $request, JsonApiResponse $response) { - $query = $params->get('q'); - $start = $params->start(); - $include = $params->included(['startUser', 'lastUser', 'startPost', 'lastPost', 'relevantPosts']); - $count = $params->count(20, 50); - $sort = $params->sort(['', 'lastPost', 'replies', 'created']); + $criteria = new DiscussionSearchCriteria( + $request->actor->getUser(), + $request->get('q'), + $request->sort + ); - // Set up the discussion searcher with our search criteria, and get the - // requested range of results with the necessary relations loaded. - $criteria = new DiscussionSearchCriteria($this->actor->getUser(), $query, $sort['field'], $sort['order']); - $load = array_merge($include, ['state']); - - $results = $this->searcher->search($criteria, $count, $start, $load); - - $document = $this->document(); + $load = array_merge($request->include, ['state']); + $results = $this->searcher->search($criteria, $request->limit, $request->offset, $load); if (($total = $results->getTotal()) !== null) { - $document->addMeta('total', $total); + $response->content->addMeta('total', $total); } - // If there are more results, then we need to construct a URL to the - // next results page and add that to the metadata. We do this by - // compacting all of the valid query parameters which have been - // specified. - if ($results->areMoreResults()) { - $start += $count; - $sort = $sort['string']; - $input = array_filter(compact('query', 'sort', 'start', 'count')) + ['include' => implode(',', $include)]; - $moreUrl = $this->buildUrl('discussions.index', [], $input); - } else { - $moreUrl = ''; - } - $document->addMeta('moreUrl', $moreUrl); + // $response->content->addMeta('moreUrl', $moreUrl); - // Finally, we can set up the discussion serializer and use it to create - // a collection of discussion results. - $serializer = new DiscussionSerializer($include); - $document->setData($serializer->collection($results->getDiscussions())); - - return $this->respondWithDocument($document); + return $results->getDiscussions(); } } diff --git a/framework/core/src/Api/Actions/Discussions/ShowAction.php b/framework/core/src/Api/Actions/Discussions/ShowAction.php index f5eeb0d0b..823418a77 100644 --- a/framework/core/src/Api/Actions/Discussions/ShowAction.php +++ b/framework/core/src/Api/Actions/Discussions/ShowAction.php @@ -1,14 +1,13 @@ 'asc']; + + /** + * The maximum number of records that can be requested. + * + * @var integer + */ + public static $limitMax = 50; + + /** + * The number of records included by default. + * + * @var integer + */ + public static $limit = 20; + /** * Instantiate the action. * * @param PostRepository $posts */ - public function __construct(Actor $actor, DiscussionRepository $discussions, PostRepository $posts) + public function __construct(DiscussionRepositoryInterface $discussions, PostRepositoryInterface $posts) { - $this->actor = $actor; $this->discussions = $discussions; $this->posts = $posts; } /** - * Show a single discussion. + * Get a single discussion, ready to be serialized and assigned to the + * JsonApi response. * - * @return Response + * @param \Flarum\Api\JsonApiRequest $request + * @param \Flarum\Api\JsonApiResponse $response + * @return \Flarum\Core\Models\Discussion */ - protected function run(ApiParams $params) + protected function data(JsonApiRequest $request, JsonApiResponse $response) { - $include = $params->included(['startPost', 'lastPost', 'posts']); + $user = $request->actor->getUser(); - $user = $this->actor->getUser(); + $discussion = $this->discussions->findOrFail($request->get('id'), $user); - $discussion = $this->discussions->findOrFail($params->get('id'), $user); $discussion->posts_ids = $discussion->posts()->whereCan($user, 'view')->get(['id'])->fetch('id')->all(); - if (in_array('posts', $include)) { - $relations = ['user', 'user.groups', 'editUser', 'hideUser']; - $discussion->posts = $this->getPosts($params, ['discussion_id' => $discussion->id])->load($relations); + if (in_array('posts', $request->include)) { + $length = strlen($prefix = 'posts.'); + $relations = array_filter(array_map(function ($relationship) use ($prefix, $length) { + return substr($relationship, 0, $length) === $prefix ? substr($relationship, $length) : false; + }, $request->include)); - $include = array_merge($include, array_map(function ($relation) { - return 'posts.'.$relation; - }, $relations)); + $discussion->posts = $this->getPosts($request, ['discussion_id' => $discussion->id])->load($relations); } - // Set up the discussion serializer, which we will use to create the - // document's primary resource. As well as including the requested - // relations, we will specify that we want the 'posts' relation to be - // linked so that a list of post IDs will show up in the response. - $serializer = new DiscussionSerializer($include, ['posts']); - $document = $this->document()->setData($serializer->resource($discussion)); - - return $this->respondWithDocument($document); + return $discussion; } } diff --git a/framework/core/src/Api/Actions/Discussions/UpdateAction.php b/framework/core/src/Api/Actions/Discussions/UpdateAction.php index 8af13074b..588534747 100644 --- a/framework/core/src/Api/Actions/Discussions/UpdateAction.php +++ b/framework/core/src/Api/Actions/Discussions/UpdateAction.php @@ -3,57 +3,77 @@ use Flarum\Core\Commands\EditDiscussionCommand; use Flarum\Core\Commands\ReadDiscussionCommand; use Flarum\Core\Exceptions\PermissionDeniedException; -use Flarum\Api\Actions\BaseAction; -use Flarum\Api\Actions\ApiParams; -use Flarum\Api\Serializers\DiscussionSerializer; +use Flarum\Api\Actions\SerializeResourceAction; +use Flarum\Api\Actions\Posts\GetsPosts; +use Flarum\Api\JsonApiRequest; +use Flarum\Api\JsonApiResponse; +use Illuminate\Contracts\Bus\Dispatcher; -class UpdateAction extends BaseAction +class UpdateAction extends SerializeResourceAction { /** - * Edit a discussion. Allows renaming the discussion, and updating its read - * state with regards to the current user. + * The command bus. * - * @return Response + * @var \Illuminate\Contracts\Bus\Dispatcher */ - protected function run(ApiParams $params) + protected $bus; + + /** + * The name of the serializer class to output results with. + * + * @var string + */ + public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + + /** + * The relations that are included by default. + * + * @var array + */ + public static $include = ['addedPosts', 'addedPosts.user']; + + /** + * Initialize the action. + * + * @param \Illuminate\Contracts\Bus\Dispatcher $bus + */ + public function __construct(Dispatcher $bus) { - $discussionId = $params->get('id'); - $user = $this->actor->getUser(); + $this->bus = $bus; + } + + /** + * Update a discussion according to input from the API request, and return + * it ready to be serialized and assigned to the JsonApi response. + * + * @param \Flarum\Api\Request $request + * @return \Flarum\Core\Models\Discussion + */ + protected function data(JsonApiRequest $request, JsonApiResponse $response) + { + $user = $request->actor->getUser(); + $discussionId = $request->get('id'); // First, we will run the EditDiscussionCommand. This will update the // discussion's direct properties; by default, this is just the title. // As usual, however, we will fire an event to allow plugins to update // additional properties. - if ($data = array_except($params->get('data'), ['readNumber'])) { - try { - $command = new EditDiscussionCommand($discussionId, $user); - $this->hydrate($command, $params->get('data')); - $discussion = $this->dispatch($command, $params); - } catch (PermissionDeniedException $e) { - // Temporary fix. See @todo below - $discussion = \Flarum\Core\Models\Discussion::find($discussionId); - } + if ($data = array_except($request->get('data'), ['readNumber'])) { + $discussion = $this->bus->dispatch( + new EditDiscussionCommand($discussionId, $user, $data) + ); } // Next, if a read number was specified in the request, we will run the // ReadDiscussionCommand. - // - // @todo Currently, if the user doesn't have permission to edit a - // discussion, they're unable to update their readNumber because a - // PermissionDeniedException is thrown by the - // EditDiscussionCommand above. So this needs to be extracted into - // its own endpoint. - if ($readNumber = $params->get('data.readNumber')) { - $command = new ReadDiscussionCommand($discussionId, $user, $readNumber); - $discussion = $this->dispatch($command, $params)->discussion; + if ($readNumber = $request->get('data.readNumber')) { + $state = $this->bus->dispatch( + new ReadDiscussionCommand($discussionId, $user, $readNumber) + ); + + $discussion = $state->discussion; } - // Presumably, the discussion was updated successfully. (One of the command - // handlers would have thrown an exception if not.) We set this - // discussion as our document's primary element. - $serializer = new DiscussionSerializer(['addedPosts', 'addedPosts.user']); - $document = $this->document()->setData($serializer->resource($discussion)); - - return $this->respondWithDocument($document); + return $discussion; } } diff --git a/framework/core/src/Api/Actions/Posts/GetsPosts.php b/framework/core/src/Api/Actions/Posts/GetsPosts.php index 89febc4b3..7470550c8 100644 --- a/framework/core/src/Api/Actions/Posts/GetsPosts.php +++ b/framework/core/src/Api/Actions/Posts/GetsPosts.php @@ -1,19 +1,17 @@ sort(['time']); - $count = $params->count(20, 50); - $user = $this->actor->getUser(); + $user = $request->actor->getUser(); - if (isset($where['discussion_id']) && ($near = $params->get('near')) > 1) { + if (isset($where['discussion_id']) && ($near = $request->get('near')) > 1) { $start = $this->posts->getIndexForNumber($where['discussion_id'], $near, $user); - $start = max(0, $start - $count / 2); + $start = max(0, $request->offset - $request->limit / 2); } else { $start = 0; } @@ -21,10 +19,9 @@ trait GetsPosts return $this->posts->findWhere( $where, $user, - $sort['field'], - $sort['order'] ?: 'asc', - $count, - $start + $request->sort, + $request->limit, + $request->offset ); } } diff --git a/framework/core/src/Api/Actions/SerializeAction.php b/framework/core/src/Api/Actions/SerializeAction.php new file mode 100644 index 000000000..919ece2f4 --- /dev/null +++ b/framework/core/src/Api/Actions/SerializeAction.php @@ -0,0 +1,160 @@ +data($request, $response = new JsonApiResponse); + + $serializer = new static::$serializer($request->actor, $request->include, $request->link); + + $response->content->setData($this->serialize($serializer, $data)); + + return $response; + } + + /** + * Get the data to be serialized and assigned to the response document. + * + * @param Flarum\Api\JsonApiRequest $request + * @param Flarum\Api\JsonApiResponse $response + * @return array + */ + abstract protected function data(JsonApiRequest $request, JsonApiResponse $response); + + /** + * Serialize the data as appropriate. + * + * @param \Tobscure\JsonApi\SerializerInterface $serializer + * @param array $data + * @return \Tobscure\JsonApi\Elements\ElementInterface + */ + abstract protected function serialize(SerializerInterface $serializer, $data); + + /** + * Extract parameters from the request input and assign them to the + * request, restricted by the action's specifications. + * + * @param Flarum\Api\Request $request + * @return void + */ + protected static function buildJsonApiRequest(Request $request) + { + $request = new JsonApiRequest($request->input, $request->actor, $request->httpRequest); + + $criteria = new Criteria($request->input); + + $request->include = static::sanitizeInclude($criteria->getInclude()); + $request->sort = static::sanitizeSort($criteria->getSort()); + $request->offset = $criteria->getOffset(); + $request->limit = static::sanitizeLimit($criteria->getLimit()); + $request->link = static::$link; + + return $request; + } + + /** + * Sanitize an array of included relationships according to the action's + * configuration. + * + * @param array $include + * @return array + */ + protected static function sanitizeInclude(array $include) + { + return array_intersect($include, static::$includeAvailable) ?: static::$include; + } + + /** + * Sanitize an array of sort criteria according to the action's + * configuration. + * + * @param array $sort + * @return array + */ + protected static function sanitizeSort(array $sort) + { + return array_intersect_key($sort, array_flip(static::$sortAvailable)) ?: static::$sort; + } + + /** + * Sanitize a limit according to the action's configuration. + * + * @param int $limit + * @return int + */ + protected static function sanitizeLimit($limit) + { + return min($limit, static::$limitMax) ?: static::$limit; + } +} diff --git a/framework/core/src/Api/Actions/SerializeCollectionAction.php b/framework/core/src/Api/Actions/SerializeCollectionAction.php new file mode 100644 index 000000000..e735eb0cf --- /dev/null +++ b/framework/core/src/Api/Actions/SerializeCollectionAction.php @@ -0,0 +1,18 @@ +collection($data); + } +} diff --git a/framework/core/src/Api/Actions/SerializeResourceAction.php b/framework/core/src/Api/Actions/SerializeResourceAction.php new file mode 100644 index 000000000..3592094ed --- /dev/null +++ b/framework/core/src/Api/Actions/SerializeResourceAction.php @@ -0,0 +1,18 @@ +resource($data); + } +} diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index 8d1378ac7..c36217197 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -18,8 +18,6 @@ class ApiServiceProvider extends ServiceProvider ); include __DIR__.'/routes.php'; - - BaseSerializer::setActor($this->app['Flarum\Support\Actor']); } /** diff --git a/framework/core/src/Api/JsonApiRequest.php b/framework/core/src/Api/JsonApiRequest.php new file mode 100644 index 000000000..8322a86f3 --- /dev/null +++ b/framework/core/src/Api/JsonApiRequest.php @@ -0,0 +1,29 @@ +headers->set('Content-Type', 'application/vnd.api+json'); + + $this->content = new Document; + $this->content->setData($data); + } +} diff --git a/framework/core/src/Api/Request.php b/framework/core/src/Api/Request.php new file mode 100644 index 000000000..5ce0e3d08 --- /dev/null +++ b/framework/core/src/Api/Request.php @@ -0,0 +1,25 @@ +input = $input; + $this->actor = $actor; + $this->httpRequest = $httpRequest; + } + + public function get($key, $default = null) + { + return isset($this->input[$key]) ? $this->input[$key] : $default; + } +} diff --git a/framework/core/src/Api/Serializers/BaseSerializer.php b/framework/core/src/Api/Serializers/BaseSerializer.php index f4bce63c4..e4db35e62 100644 --- a/framework/core/src/Api/Serializers/BaseSerializer.php +++ b/framework/core/src/Api/Serializers/BaseSerializer.php @@ -11,22 +11,13 @@ use Closure; */ abstract class BaseSerializer extends SerializerAbstract { - /** - * The actor who is requesting the serialized objects. - * - * @var \Flarum\Support\Actor - */ - protected static $actor; + protected $actor; - /** - * Set the actor who is requesting the serialized objects. - * - * @param \Flarum\Support\Actor $actor - * @return void - */ - public static function setActor(Actor $actor) + public function __construct(Actor $actor, $include = null, $link = null) { - static::$actor = $actor; + parent::__construct($include, $link); + + $this->actor = $actor; } /** @@ -70,7 +61,7 @@ abstract class BaseSerializer extends SerializerAbstract $class = get_class(is_object($data) ? $data : $model->$relation()->getRelated()); $serializer = $serializer[$class]; } - $serializer = new $serializer($links); + $serializer = new $serializer($this->actor, $links); return $many ? $serializer->collection($data) : $serializer->resource($data); }; } diff --git a/framework/core/src/Api/Serializers/DiscussionSerializer.php b/framework/core/src/Api/Serializers/DiscussionSerializer.php index ca6697241..bc844153c 100644 --- a/framework/core/src/Api/Serializers/DiscussionSerializer.php +++ b/framework/core/src/Api/Serializers/DiscussionSerializer.php @@ -19,7 +19,7 @@ class DiscussionSerializer extends DiscussionBasicSerializer { $attributes = parent::attributes($discussion); - $user = static::$actor->getUser(); + $user = $this->actor->getUser(); $state = $discussion->stateFor($user); $attributes += [ diff --git a/framework/core/src/Api/Serializers/PostSerializer.php b/framework/core/src/Api/Serializers/PostSerializer.php index 7d1e52eb3..a85938149 100644 --- a/framework/core/src/Api/Serializers/PostSerializer.php +++ b/framework/core/src/Api/Serializers/PostSerializer.php @@ -18,7 +18,7 @@ class PostSerializer extends PostBasicSerializer protected function attributes($post) { $attributes = parent::attributes($post); - $user = static::$actor->getUser(); + $user = $this->actor->getUser(); unset($attributes['content']); diff --git a/framework/core/src/Api/Serializers/UserSerializer.php b/framework/core/src/Api/Serializers/UserSerializer.php index df7e6df31..0488c1c4b 100644 --- a/framework/core/src/Api/Serializers/UserSerializer.php +++ b/framework/core/src/Api/Serializers/UserSerializer.php @@ -19,7 +19,7 @@ class UserSerializer extends UserBasicSerializer { $attributes = parent::attributes($user); - $actorUser = static::$actor->getUser(); + $actorUser = $this->actor->getUser(); $canEdit = $user->can($actorUser, 'edit'); $attributes += [ diff --git a/framework/core/src/Api/routes.php b/framework/core/src/Api/routes.php index c1fb00eec..67cd4e46b 100644 --- a/framework/core/src/Api/routes.php +++ b/framework/core/src/Api/routes.php @@ -1,11 +1,25 @@ app->make($class); - $request = $this->app['request']->instance(); - $parameters = $this->app['router']->current()->parameters(); - return $action->handle($request, $parameters); + + $httpRequest = $this->app['request']->instance(); + $routeParams = $this->app['router']->current()->parameters(); + $actor = $this->app['Flarum\Support\Actor']; + + if (str_contains($httpRequest->header('CONTENT_TYPE'), 'application/vnd.api+json')) { + $input = $httpRequest->json(); + } else { + $input = $httpRequest->all(); + } + $input = array_merge($input, $routeParams); + + $request = new Request($input, $actor, $httpRequest); + + return $action->handle($request); }; };