Merge branch 'fix/local-user-notes-count' into 'develop'

fix: recalculate notesCount of local user when notes are deleted.

Co-authored-by: Linca <lhcfllinca@gmail.com>
Co-authored-by: Lhcfl <Lhcfl@outlook.com>

See merge request firefish/firefish!10701
This commit is contained in:
naskya 2024-03-23 08:01:39 +00:00
commit df8078d9a4

View file

@ -16,11 +16,24 @@ import {
import { countSameRenotes } from "@/misc/count-same-renotes.js"; import { countSameRenotes } from "@/misc/count-same-renotes.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js"; import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
import { deliverToRelays } from "@/services/relay.js"; import { deliverToRelays } from "@/services/relay.js";
import type { IActivity } from "@/remote/activitypub/type.js";
async function recalculateNotesCountOfLocalUser(user: {
id: User["id"];
host: User["host"];
}) {
if (Users.isLocalUser(user)) {
await Notes.countBy({ userId: user.id }).then((newCount: number) =>
Users.update(user.id, { updatedAt: new Date(), notesCount: newCount }),
);
}
}
/** /**
* 稿 * 稿
* @param user 稿 * @param user 稿
* @param note 稿 * @param note 稿
* @param deleteFromDb false if called by making private
*/ */
export default async function ( export default async function (
user: { id: User["id"]; uri: User["uri"]; host: User["host"] }, user: { id: User["id"]; uri: User["uri"]; host: User["host"] },
@ -44,6 +57,16 @@ export default async function (
await Notes.decrement({ id: note.replyId }, "repliesCount", 1); await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
} }
const cascadingNotes = await findCascadingNotes(note);
const affectedLocalUsers: Record<
User["id"],
{ id: User["id"]; uri: User["uri"]; host: null }
> = {};
if (Users.isLocalUser(user)) {
affectedLocalUsers[user.id] = user;
}
const instanceNotesCountDecreasement: Record<string, number> = {};
if (!quiet) { if (!quiet) {
// Only broadcast "deleted" to local if the note is deleted from db // Only broadcast "deleted" to local if the note is deleted from db
if (deleteFromDb) { if (deleteFromDb) {
@ -87,12 +110,23 @@ export default async function (
} }
// also deliever delete activity to cascaded notes // also deliever delete activity to cascaded notes
const cascadingNotes = (await findCascadingNotes(note)).filter(
(note) => !note.localOnly,
); // filter out local-only notes
for (const cascadingNote of cascadingNotes) { for (const cascadingNote of cascadingNotes) {
if (deleteFromDb) {
// For other notes, publishNoteStream is also required.
publishNoteStream(cascadingNote.id, "deleted", {
deletedAt: deletedAt,
});
}
if (!cascadingNote.user) continue; if (!cascadingNote.user) continue;
if (!Users.isLocalUser(cascadingNote.user)) continue; if (!Users.isLocalUser(cascadingNote.user)) {
if (!Users.isRemoteUser(cascadingNote.user)) continue;
instanceNotesCountDecreasement[cascadingNote.user.host] ??= 0;
instanceNotesCountDecreasement[cascadingNote.user.host]++;
continue; // filter out remote users
}
affectedLocalUsers[cascadingNote.user.id] ??= cascadingNote.user;
if (cascadingNote.localOnly) continue; // filter out local-only notes
const content = renderActivity( const content = renderActivity(
renderDelete( renderDelete(
renderTombstone(`${config.url}/notes/${cascadingNote.id}`), renderTombstone(`${config.url}/notes/${cascadingNote.id}`),
@ -104,8 +138,14 @@ export default async function (
//#endregion //#endregion
if (Users.isRemoteUser(user)) { if (Users.isRemoteUser(user)) {
registerOrFetchInstanceDoc(user.host).then((i) => { instanceNotesCountDecreasement[user.host] ??= 0;
Instances.decrement({ id: i.id }, "notesCount", 1); instanceNotesCountDecreasement[user.host]++;
}
for (const [host, count] of Object.entries(
instanceNotesCountDecreasement,
)) {
registerOrFetchInstanceDoc(host).then((i) => {
Instances.decrement({ id: i.id }, "notesCount", count);
}); });
} }
} }
@ -115,13 +155,20 @@ export default async function (
id: note.id, id: note.id,
userId: user.id, userId: user.id,
}); });
// Handle cascading deletion (it's not as simple as notesCount -= 1)
await Promise.all(
Object.values(affectedLocalUsers).map((user) =>
recalculateNotesCountOfLocalUser(user),
),
);
} }
} }
async function findCascadingNotes(note: Note) { async function findCascadingNotes(note: Note) {
const cascadingNotes: Note[] = []; const cascadingNotes: Note[] = [];
const recursive = async (noteId: string) => { const findRepliesAndQuotes = async (noteId: string) => {
const query = Notes.createQueryBuilder("note") const query = Notes.createQueryBuilder("note")
.where("note.replyId = :noteId", { noteId }) .where("note.replyId = :noteId", { noteId })
.orWhere( .orWhere(
@ -132,15 +179,18 @@ async function findCascadingNotes(note: Note) {
}), }),
) )
.leftJoinAndSelect("note.user", "user"); .leftJoinAndSelect("note.user", "user");
const replies = await query.getMany(); const repliesAndQuotes = await query.getMany();
for (const reply of replies) {
cascadingNotes.push(reply);
await recursive(reply.id);
}
};
await recursive(note.id);
return cascadingNotes.filter((note) => note.userHost === null); // filter out non-local users await Promise.all(
repliesAndQuotes.map((n: Note) => {
cascadingNotes.push(n);
return findRepliesAndQuotes(n.id);
}),
);
};
await findRepliesAndQuotes(note.id);
return cascadingNotes;
} }
async function getMentionedRemoteUsers(note: Note) { async function getMentionedRemoteUsers(note: Note) {
@ -171,7 +221,7 @@ async function getMentionedRemoteUsers(note: Note) {
async function deliverToConcerned( async function deliverToConcerned(
user: { id: ILocalUser["id"]; host: null }, user: { id: ILocalUser["id"]; host: null },
note: Note, note: Note,
content: any, content: IActivity | null,
) { ) {
deliverToFollowers(user, content); deliverToFollowers(user, content);
deliverToRelays(user, content); deliverToRelays(user, content);