Skip to content

Commit 4c2e44f

Browse files
authored
[2.x] fix(api): surface event posts in discussion PATCH responses and route title via rename() (#4658)
* [2.x] fix(api): surface event posts in discussion PATCH responses and route title via rename() Fixes two related 2.x regressions where discussion changes silently fail to surface in the UI: 1. Event posts created via `mergePost()` (sticky/tags/lock) were absent from PATCH responses. The 1.x `UpdateDiscussionController` refreshed the `posts` linkage and inlined modified posts on every update; the 2.x `DiscussionResource` rewrite dropped that. 2. Renaming a discussion no longer raised `Renamed`. The 2.x `DiscussionResource` title field delegated to the default attribute setter, bypassing `Discussion::rename()`. The `DiscussionRenamedLogger` listener never ran, so neither the `discussionRenamed` event post nor the notification fired. Together these broke the "rename -> event post appears -> author gets notified" flow that worked end-to-end in 1.x. Core fixes: - `DiscussionResource::posts` field now exposes linkage on update - `DiscussionResource::title` field now routes through `rename()` - `RenameDiscussionModal` chains redraw onto the `update()` promise Sticky/Tags/Lock JS: - Chain redraw onto the `stream.update()` promise (was firing synchronously, before the new post was in-store) Sticky/Tags realtime: - Register broadcasts via the Realtime extender so other users see the event posts as they happen, mirroring the existing `lock` template. Recipient permission is enforced per-user by the Realtime Generator (internal API request runs as the recipient). Closes #4620 * [2.x] chore(ts): map ext:flarum/realtime/* path in sticky and tags tsconfig build-typings was failing because the new `extendRealtime.ts` files import from `ext:flarum/realtime/forum/extenders/Realtime`, but neither extension's tsconfig.json mapped that module specifier to realtime's dist-typings. Mirror the mapping that flarum-lock already has.
1 parent 357920e commit 4c2e44f

15 files changed

Lines changed: 247 additions & 15 deletions

File tree

extensions/lock/js/src/forum/addLockControl.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ export default function addLockControl() {
1919
DiscussionControls.lockAction = function () {
2020
this.save({ isLocked: !this.isLocked() }).then(() => {
2121
if (app.current.matches(DiscussionPage)) {
22-
app.current.get('stream').update();
22+
app.current
23+
.get('stream')
24+
.update()
25+
.then(() => m.redraw());
26+
} else {
27+
m.redraw();
2328
}
24-
25-
m.redraw();
2629
});
2730
};
2831
}

extensions/sticky/extend.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Flarum\Discussion\Discussion;
1313
use Flarum\Discussion\Search\DiscussionSearcher;
1414
use Flarum\Extend;
15+
use Flarum\Realtime\Extend\Realtime as RealtimeExtend;
1516
use Flarum\Search\Database\DatabaseSearchDriver;
1617
use Flarum\Sticky\Api\DiscussionResourceFields;
1718
use Flarum\Sticky\Event\DiscussionWasStickied;
@@ -58,4 +59,15 @@
5859
(new Extend\Settings())
5960
->default('flarum-sticky.only_sticky_unread_discussions', true)
6061
->serializeToForum('onlyStickyUnreadDiscussions', 'flarum-sticky.only_sticky_unread_discussions', 'boolval'),
62+
63+
(new Extend\Conditional())
64+
->whenExtensionEnabled('flarum-realtime', fn () => [
65+
(new RealtimeExtend())
66+
->broadcastModelEvent(
67+
[DiscussionWasStickied::class, DiscussionWasUnstickied::class],
68+
fn ($event) => $event->discussion,
69+
fn ($event) => $event->user,
70+
'stickiedEvent'
71+
),
72+
]),
6173
];

extensions/sticky/js/src/forum/addStickyControl.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ export default function addStickyControl() {
1919
DiscussionControls.stickyAction = function () {
2020
this.save({ isSticky: !this.isSticky() }).then(() => {
2121
if (app.current.matches(DiscussionPage)) {
22-
app.current.get('stream').update();
22+
app.current
23+
.get('stream')
24+
.update()
25+
.then(() => m.redraw());
26+
} else {
27+
m.redraw();
2328
}
24-
25-
m.redraw();
2629
});
2730
};
2831
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import app from 'flarum/forum/app';
2+
import RealtimeExtend from 'ext:flarum/realtime/forum/extenders/Realtime';
3+
4+
export default function extendRealtime() {
5+
new RealtimeExtend().onDiscussionStreamEvent('stickiedEvent').extend(app, { name: 'flarum-sticky', exports: {} });
6+
}

extensions/sticky/js/src/forum/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import addStickyBadge from './addStickyBadge';
44
import addStickyControl from './addStickyControl';
55
import addStickyExcerpt from './addStickyExcerpt';
66
import addStickyClass from './addStickyClass';
7+
import extendRealtime from './extendRealtime';
78

89
export { default as extend } from './extend';
910

@@ -12,4 +13,10 @@ app.initializers.add('flarum-sticky', () => {
1213
addStickyControl();
1314
addStickyExcerpt();
1415
addStickyClass();
16+
17+
// Register a discussion stream update event with flarum/realtime when enabled.
18+
// Stickying or unstickying a discussion will trigger a DiscussionPage stream reload for other users.
19+
if ('flarum-realtime' in flarum.extensions) {
20+
extendRealtime();
21+
}
1522
});

extensions/sticky/js/tsconfig.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
// This will match all .ts, .tsx, .d.ts, .js, .jsx files in your `src` folder
55
// and also tells your Typescript server to read core's global typings for
66
// access to `dayjs` and `$` in the global namespace.
7-
"include": ["src/**/*", "../../../framework/core/js/dist-typings/@types/**/*", "@types/**/*"],
7+
"include": ["src/**/*", "../../../framework/core/js/dist-typings/@types/**/*", "@types/**/*", "../../realtime/js/dist-typings/@types/**/*"],
88
"compilerOptions": {
99
// This will output typings to `dist-typings`
1010
"declarationDir": "./dist-typings",
1111
"paths": {
12-
"flarum/*": ["../../../framework/core/js/dist-typings/*"]
12+
"flarum/*": ["../../../framework/core/js/dist-typings/*"],
13+
"ext:flarum/realtime/*": ["../../realtime/js/dist-typings/*"]
1314
}
1415
}
1516
}

extensions/sticky/tests/integration/api/StickyDiscussionsTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,54 @@ public static function stickyDataProvider(): array
9898
[3, false, false],
9999
];
100100
}
101+
102+
#[Test]
103+
public function sticky_response_exposes_new_event_post_in_linkage()
104+
{
105+
// Discussion 2 starts un-stickied with one comment post (id 2). Stickying
106+
// creates a `discussionStickied` event post via mergePost(). The PATCH
107+
// response must surface that new post in the discussion's `posts`
108+
// relationship linkage so the client can refresh its post stream without
109+
// a full reload. See flarum/framework#TBD.
110+
$response = $this->send(
111+
$this->request('PATCH', '/api/discussions/2', [
112+
'authenticatedAs' => 1,
113+
'json' => [
114+
'data' => [
115+
'attributes' => [
116+
'isSticky' => true,
117+
],
118+
],
119+
],
120+
])
121+
);
122+
123+
$body = $response->getBody()->getContents();
124+
$json = json_decode($body, true);
125+
126+
$this->assertEquals(200, $response->getStatusCode(), $body);
127+
128+
$linkage = $json['data']['relationships']['posts']['data'] ?? null;
129+
$this->assertIsArray($linkage, 'PATCH response must include the discussion posts relationship linkage');
130+
131+
$linkageIds = array_map(fn (array $entry) => $entry['id'], $linkage);
132+
133+
// The original comment post is still there.
134+
$this->assertContains('2', $linkageIds, 'Linkage should still contain the original comment post id');
135+
136+
// And the newly-created event post is too — its id is whatever the next
137+
// available id is after the fixture posts (1–4). The discussion gained
138+
// exactly one post; assert the linkage grew by one.
139+
$this->assertCount(2, $linkageIds, 'Linkage should contain the comment post and the new event post');
140+
141+
// Identify the new post id (the one that isn't '2') and assert it
142+
// corresponds to a `discussionStickied` post in the included array.
143+
$newPostId = array_values(array_diff($linkageIds, ['2']))[0] ?? null;
144+
$this->assertNotNull($newPostId);
145+
146+
$stickiedRow = Post::query()->find($newPostId);
147+
$this->assertNotNull($stickiedRow, 'New post in linkage should exist');
148+
$this->assertEquals('discussionStickied', $stickiedRow->type);
149+
$this->assertEquals(2, $stickiedRow->discussion_id);
150+
}
101151
}

extensions/tags/extend.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Flarum\Flags\Api\Resource\FlagResource;
1818
use Flarum\Post\Filter\PostSearcher;
1919
use Flarum\Post\Post;
20+
use Flarum\Realtime\Extend\Realtime as RealtimeExtend;
2021
use Flarum\Search\Database\DatabaseSearchDriver;
2122
use Flarum\Tags\Access;
2223
use Flarum\Tags\Api;
@@ -178,4 +179,15 @@ function (Endpoint\Index|Endpoint\Show|Endpoint\Create $endpoint) {
178179
return $endpoint
179180
->addDefaultInclude(['eventPostMentionsTags']);
180181
}),
182+
183+
(new Extend\Conditional())
184+
->whenExtensionEnabled('flarum-realtime', fn () => [
185+
(new RealtimeExtend())
186+
->broadcastModelEvent(
187+
[DiscussionWasTagged::class],
188+
fn ($event) => $event->discussion,
189+
fn ($event) => $event->actor,
190+
'taggedEvent'
191+
),
192+
]),
181193
];

extensions/tags/js/src/forum/components/TagDiscussionModal.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ export default class TagDiscussionModal extends TagSelectionModal<TagDiscussionM
4949
if (discussion) {
5050
discussion.save({ relationships: { tags } }).then(() => {
5151
if (app.current.matches(DiscussionPage)) {
52-
app.current.get('stream').update();
52+
app.current
53+
.get('stream')
54+
.update()
55+
.then(() => m.redraw());
56+
} else {
57+
m.redraw();
5358
}
54-
55-
m.redraw();
5659
});
5760
}
5861

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import app from 'flarum/forum/app';
2+
import RealtimeExtend from 'ext:flarum/realtime/forum/extenders/Realtime';
3+
4+
export default function extendRealtime() {
5+
new RealtimeExtend().onDiscussionStreamEvent('taggedEvent').extend(app, { name: 'flarum-tags', exports: {} });
6+
}

0 commit comments

Comments
 (0)