1
0
mirror of https://github.com/flarum/core.git synced 2025-08-08 09:26:34 +02:00

refactor: remove listing of posts in the show discussion endpoint (#4067)

This commit is contained in:
Sami Mazouz
2024-10-16 18:02:46 +01:00
committed by GitHub
parent 40996de39a
commit b0e8f5ca36
12 changed files with 84 additions and 121 deletions

View File

@@ -75,7 +75,6 @@ class DiscussionResource extends AbstractDatabaseResource
->authenticated()
->can('startDiscussion')
->defaultInclude([
'posts',
'user',
'lastPostedUser',
'firstPost',
@@ -89,12 +88,14 @@ class DiscussionResource extends AbstractDatabaseResource
Endpoint\Show::make()
->defaultInclude([
'user',
'posts',
'posts.discussion',
'posts.user',
'posts.user.groups',
'posts.editedUser',
'posts.hiddenUser'
'lastPostedUser',
'firstPost',
'firstPost.discussion',
'firstPost.user',
'firstPost.user.groups',
'firstPost.editedUser',
'firstPost.hiddenUser',
'lastPost'
]),
Endpoint\Index::make()
->defaultInclude([
@@ -206,54 +207,14 @@ class DiscussionResource extends AbstractDatabaseResource
->type('posts'),
Schema\Relationship\ToMany::make('posts')
->withLinkage(function (Context $context) {
return $context->showing(self::class);
return $context->showing(self::class)
|| $context->creating(self::class)
|| $context->creating(PostResource::class);
})
->includable()
// @todo: remove this, and send a second request from the frontend to /posts instead. Revert Serializer::addIncluded while you're at it.
->get(function (Discussion $discussion, Context $context) {
$showingDiscussion = $context->showing(self::class);
if (! $showingDiscussion) {
return fn () => $discussion->posts->all();
}
/** @var Endpoint\Show $endpoint */
$endpoint = $context->endpoint;
$actor = $context->getActor();
$limit = PostResource::$defaultLimit;
if (($near = Arr::get($context->request->getQueryParams(), 'page.near')) > 1) {
$offset = $this->posts->getIndexForNumber($discussion->id, $near, $actor);
$offset = max(0, $offset - $limit / 2);
} else {
$offset = $endpoint->extractOffsetValue($context, $endpoint->defaultExtracts($context));
}
/** @var Endpoint\Endpoint $endpoint */
$endpoint = $context->endpoint;
$posts = $discussion->posts()
->whereVisibleTo($actor)
->with($endpoint->getEagerLoadsFor('posts', $context))
->with($endpoint->getWhereEagerLoadsFor('posts', $context))
->orderBy('number')
->skip($offset)
->take($limit)
->get();
/** @var Post $post */
foreach ($posts as $post) {
$post->setRelation('discussion', $discussion);
}
$allPosts = $discussion->posts()->whereVisibleTo($actor)->orderBy('number')->pluck('id')->all();
$loadedPosts = $posts->all();
array_splice($allPosts, $offset, $limit, $loadedPosts);
return $allPosts;
// @todo: is it possible to refactor the frontend to not need all post IDs?
// some kind of percentage-based stream scrubber?
return $discussion->posts()->whereVisibleTo($context->getActor())->select('id')->get()->all();
}),
Schema\Relationship\ToOne::make('mostRelevantPost')
->visible(fn (Discussion $model, Context $context) => $context->listing())

View File

@@ -101,7 +101,6 @@ class PostResource extends AbstractDatabaseResource
->defaultInclude([
'user',
'discussion',
'discussion.posts',
'discussion.lastPostedUser'
]),
Endpoint\Update::make()

View File

@@ -146,24 +146,17 @@ class Serializer extends \Tobyz\JsonApiServer\Serializer
*/
public function addIncluded(Relationship $field, $model, ?array $include): array
{
if (is_object($model)) {
$relatedResource = $this->resourceForModel($field, $model);
$relatedResource = $this->resourceForModel($field, $model);
if ($include === null) {
return [
'type' => $relatedResource->type(),
'id' => $relatedResource->getId($model, $this->context),
];
}
$data = $this->addToMap($relatedResource, $model, $include);
} else {
$data = [
'type' => $field->collections[0],
'id' => (string) $model,
if ($include === null) {
return [
'type' => $relatedResource->type(),
'id' => $relatedResource->getId($model, $this->context),
];
}
$data = $this->addToMap($relatedResource, $model, $include);
return [
'type' => $data['type'],
'id' => $data['id'],

View File

@@ -33,15 +33,7 @@ class Discussion
$near = intval(Arr::get($queryParams, 'near'));
$page = max(1, intval(Arr::get($queryParams, 'page')), 1 + intdiv($near, 20));
$params = [
'page' => [
'near' => $near,
'offset' => ($page - 1) * 20,
'limit' => 20
]
];
$apiDocument = $this->getApiDocument($request, $id, $params);
$apiDocument = $this->getApiDocument($request, $id);
$getResource = function ($link) use ($apiDocument) {
return Arr::first($apiDocument->included, function ($value) use ($link) {
@@ -64,9 +56,21 @@ class Discussion
($queryString ? '?'.$queryString : '');
};
$params = [
'filter' => [
'discussion' => intval($id),
],
'page' => [
'near' => $near,
'offset' => ($page - 1) * 20,
'limit' => 20,
],
];
$postsApiDocument = $this->getPostsApiDocument($request, $params);
$posts = [];
foreach ($apiDocument->included as $resource) {
foreach ($postsApiDocument->data as $resource) {
if ($resource->type === 'posts' && isset($resource->relationships->discussion) && isset($resource->attributes->contentHtml)) {
$posts[] = $resource;
}
@@ -77,6 +81,15 @@ class Discussion
$document->title = $apiDocument->data->attributes->title;
$document->content = $this->view->make('flarum.forum::frontend.content.discussion', compact('apiDocument', 'page', 'hasPrevPage', 'hasNextPage', 'getResource', 'posts', 'url'));
$apiDocument->included = array_values(array_filter($apiDocument->included, function ($value) {
return $value->type !== 'posts';
}));
$apiDocument->included = array_merge($apiDocument->included, $postsApiDocument->data, $postsApiDocument->included);
$apiDocument->included = array_values(array_filter($apiDocument->included, function ($value) use ($apiDocument) {
return $value->type !== 'discussions' || $value->id !== $apiDocument->data->id;
}));
$document->payload['apiDocument'] = $apiDocument;
$document->canonicalUrl = $url([]);
@@ -91,7 +104,7 @@ class Discussion
*
* @throws RouteNotFoundException
*/
protected function getApiDocument(Request $request, string $id, array $params): object
protected function getApiDocument(Request $request, string $id, array $params = []): object
{
$params['bySlug'] = true;
@@ -104,4 +117,21 @@ class Discussion
->getBody()
);
}
/**
* Get the result of an API request to list the posts of a discussion.
*
* @throws RouteNotFoundException
*/
protected function getPostsApiDocument(Request $request, array $params): object
{
return json_decode(
$this->api
->withoutErrorHandling()
->withParentRequest($request)
->withQueryParams($params)
->get('/posts')
->getBody()
);
}
}

View File

@@ -88,26 +88,31 @@ class ShowTest extends TestCase
public function guest_cannot_see_hidden_posts()
{
$response = $this->send(
$this->request('GET', '/api/discussions/4')
$this->request('GET', '/api/posts')
->withQueryParams([
'filter' => ['discussion' => 4]
])
);
$json = json_decode($response->getBody()->getContents(), true);
$this->assertEmpty(Arr::get($json, 'data.relationships.posts.data'));
$this->assertEmpty(Arr::get($json, 'data'));
}
#[Test]
public function author_can_see_hidden_posts()
{
$response = $this->send(
$this->request('GET', '/api/discussions/4', [
$this->request('GET', '/api/posts', [
'authenticatedAs' => 2,
])->withQueryParams([
'filter' => ['discussion' => 4]
])
);
$json = json_decode($response->getBody()->getContents(), true);
$this->assertEquals(2, Arr::get($json, 'data.relationships.posts.data.0.id'), $response->getBody()->getContents());
$this->assertEquals(2, Arr::get($json, 'data.0.id'), $response->getBody()->getContents());
}
#[Test]

View File

@@ -59,12 +59,15 @@ class ModelVisibilityTest extends TestCase
);
$response = $this->send(
$this->request('GET', '/api/discussions/2')
$this->request('GET', '/api/posts')
->withQueryParams([
'filter' => ['discussion' => 2]
])
);
$json = json_decode($response->getBody()->getContents(), true);
$this->assertEquals(1, Arr::get($json, 'data.relationships.posts.data.0.id'));
$this->assertEquals(1, Arr::get($json, 'data.0.id'));
}
#[Test]