Skip to content

Commit f19bb25

Browse files
committed
Recompute old retargeted quote counts
Refresh the previous accepted quote target when an inbound QuoteRequest moves the same remote quote object to a different local target, so stale quotes_count values do not remain on the old target. Fixes #457 (comment) Assisted-by: Codex:gpt-5.5
1 parent f605053 commit f19bb25

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

src/federation/inbox.test.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,101 @@ describe("quote request lifecycle", () => {
894894
expect(sendActivity).toHaveBeenCalledTimes(2);
895895
});
896896

897+
it("recomputes quote counts when accepted quotes are retargeted", async () => {
898+
expect.assertions(6);
899+
900+
const author = await createAccount({ username: "quote-author" });
901+
const oldPostId = crypto.randomUUID() as Uuid;
902+
const newPostId = crypto.randomUUID() as Uuid;
903+
const oldPostIri = `https://hollo.test/@quote-author/${oldPostId}`;
904+
const newPostIri = `https://hollo.test/@quote-author/${newPostId}`;
905+
const quotePostIri = "https://remote.test/@quoter/quote-retargeted";
906+
const sendActivity = vi.fn(async () => undefined);
907+
const requestCtx = {
908+
...ctx,
909+
sendActivity,
910+
} as unknown as InboxContext<void>;
911+
912+
await db.insert(posts).values([
913+
{
914+
id: oldPostId,
915+
iri: oldPostIri,
916+
type: "Note",
917+
accountId: author.id as Uuid,
918+
visibility: "public",
919+
quoteApprovalPolicy: "public",
920+
contentHtml: "<p>Old quoted post</p>",
921+
content: "Old quoted post",
922+
published: new Date(),
923+
},
924+
{
925+
id: newPostId,
926+
iri: newPostIri,
927+
type: "Note",
928+
accountId: author.id as Uuid,
929+
visibility: "public",
930+
quoteApprovalPolicy: "public",
931+
contentHtml: "<p>New quoted post</p>",
932+
content: "New quoted post",
933+
published: new Date(),
934+
},
935+
]);
936+
937+
const oldRequest = new QuoteRequest({
938+
actor: new URL("https://remote.test/@quoter"),
939+
object: new URL(oldPostIri),
940+
instrument: new Note({
941+
id: new URL(quotePostIri),
942+
attribution: new Person({
943+
id: new URL("https://remote.test/@quoter"),
944+
name: "quoter",
945+
preferredUsername: "quoter",
946+
inbox: new URL("https://remote.test/@quoter/inbox"),
947+
}),
948+
quote: new URL(oldPostIri),
949+
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
950+
content: "<p>Remote quote</p>",
951+
}),
952+
});
953+
const newRequest = new QuoteRequest({
954+
actor: new URL("https://remote.test/@quoter"),
955+
object: new URL(newPostIri),
956+
instrument: new Note({
957+
id: new URL(quotePostIri),
958+
attribution: new Person({
959+
id: new URL("https://remote.test/@quoter"),
960+
name: "quoter",
961+
preferredUsername: "quoter",
962+
inbox: new URL("https://remote.test/@quoter/inbox"),
963+
}),
964+
quote: new URL(newPostIri),
965+
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
966+
content: "<p>Remote quote retargeted</p>",
967+
}),
968+
});
969+
970+
await onQuoteRequested(requestCtx, oldRequest);
971+
await onQuoteRequested(requestCtx, newRequest);
972+
973+
const quote = await db.query.posts.findFirst({
974+
where: eq(posts.iri, quotePostIri),
975+
});
976+
const oldPost = await db.query.posts.findFirst({
977+
where: eq(posts.id, oldPostId),
978+
});
979+
const newPost = await db.query.posts.findFirst({
980+
where: eq(posts.id, newPostId),
981+
});
982+
expect(quote?.quoteTargetId).toBe(newPostId);
983+
expect(quote?.quoteState).toBe("accepted");
984+
expect(quote?.quoteAuthorizationIri).toBe(
985+
`${newPostIri}/quote_authorizations/${quote?.id}`,
986+
);
987+
expect(oldPost?.quotesCount).toBe(0);
988+
expect(newPost?.quotesCount).toBe(1);
989+
expect(sendActivity).toHaveBeenCalledTimes(2);
990+
});
991+
897992
it("rejects a private QuoteRequest from an approved follower", async () => {
898993
expect.assertions(4);
899994

src/federation/inbox.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,10 @@ export async function onQuoteRequested(
671671
existingQuote?.quoteState === "accepted" &&
672672
(existingQuote.quoteTargetId === target.id ||
673673
existingQuote.quoteTargetIri === target.iri);
674+
const previousAcceptedTargetId =
675+
existingQuote?.quoteState === "accepted"
676+
? existingQuote.quoteTargetId
677+
: null;
674678
const accepted =
675679
wasAccepted ||
676680
(await canAutomaticallyAcceptQuoteRequest(target, persistedQuote));
@@ -694,6 +698,12 @@ export async function onQuoteRequested(
694698
} else if (accepted) {
695699
await updatePostStats(tx, { id: target.id });
696700
}
701+
if (
702+
previousAcceptedTargetId != null &&
703+
previousAcceptedTargetId !== target.id
704+
) {
705+
await updatePostStats(tx, { id: previousAcceptedTargetId });
706+
}
697707
});
698708
if (accepted) {
699709
await createQuoteNotification(

0 commit comments

Comments
 (0)