v4: a new link between two dumps shows among their related dumps at once — it showed on the next load of the page: the backlink service recorded the link without telling anyone, and the dump page fetched its related dumps once, the socket then only editing or removing the ones it had. relinkBacklinks now sends dump_related_changed when a note or a comment gains or loses a link, naming the dump and those at the other end, and the page showing one of them fetches /related again. A link shows to those who may see both its ends, so each socket is told only of the dumps its user may see, and not at all without one at each end. A private dump that turns public sends it too, for the links it already had, which are new to everyone but its poster. The page also fetches its related dumps again on a reconnection, as it does its comments and playlists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -654,6 +654,78 @@ wsTest("REST writes broadcast to the right audience", async (connect) => {
|
||||
assertEquals(x.all("playlist_dumps_updated"), []);
|
||||
});
|
||||
|
||||
wsTest(
|
||||
"dump_related_changed reaches those who may see both ends of a link",
|
||||
async (connect) => {
|
||||
const owner = await createUser();
|
||||
const other = await createUser();
|
||||
const src = insertDump({ userId: owner.id, title: "src" });
|
||||
const target = insertDump({ userId: other.id, title: "target" });
|
||||
const secret = insertDump({
|
||||
userId: other.id,
|
||||
title: "secret",
|
||||
isPrivate: true,
|
||||
});
|
||||
const o = await connect(owner);
|
||||
const x = await connect(other);
|
||||
const anon = await connect();
|
||||
|
||||
// A comment linking a public dump and a private one: the latter's poster
|
||||
// alone hears of it.
|
||||
const links =
|
||||
`http://gerbeur.test${target.path} http://gerbeur.test${secret.path}`;
|
||||
const comment = await post<Comment>(`/api/dumps/${src.id}/comments`, {
|
||||
auth: owner.auth,
|
||||
json: { body: links },
|
||||
});
|
||||
for (const c of [o, anon]) {
|
||||
assertEquals((await c.next("dump_related_changed")).dumpIds, [
|
||||
src.id,
|
||||
target.id,
|
||||
]);
|
||||
}
|
||||
const both = (await x.next("dump_related_changed")).dumpIds;
|
||||
assertEquals(both[0], src.id);
|
||||
assertEquals(new Set(both.slice(1)), new Set([target.id, secret.id]));
|
||||
|
||||
// An edit that changes no link says nothing; a deletion drops them.
|
||||
await patch(`/api/comments/${comment.data.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { body: `${links} again` },
|
||||
});
|
||||
await o.next("comment_updated");
|
||||
assertEquals(o.all("dump_related_changed").length, 1);
|
||||
await del(`/api/comments/${comment.data.id}`, { auth: owner.auth });
|
||||
assertEquals((await anon.next("dump_related_changed")).dumpIds, [
|
||||
src.id,
|
||||
target.id,
|
||||
]);
|
||||
await o.next("dump_related_changed");
|
||||
await x.next("dump_related_changed");
|
||||
|
||||
// A private dump's links are its poster's alone, until it turns public.
|
||||
const hidden = insertDump({ userId: owner.id, isPrivate: true });
|
||||
await put(`/api/dumps/${hidden.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { comment: `http://gerbeur.test${target.path}` },
|
||||
});
|
||||
assertEquals((await o.next("dump_related_changed")).dumpIds, [
|
||||
hidden.id,
|
||||
target.id,
|
||||
]);
|
||||
await x.sync();
|
||||
assertEquals(x.all("dump_related_changed").length, 2);
|
||||
await put(`/api/dumps/${hidden.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { isPrivate: false },
|
||||
});
|
||||
assertEquals((await x.next("dump_related_changed")).dumpIds, [
|
||||
hidden.id,
|
||||
target.id,
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
wsTest("a password change closes the user's sockets", async (connect) => {
|
||||
const user = await createUser();
|
||||
const c = await connect(user);
|
||||
|
||||
Reference in New Issue
Block a user