fix: decrement counts of boost and reply
This commit is contained in:
parent
2e6c04a6cb
commit
d0665c2fc2
5 changed files with 140 additions and 7 deletions
|
@ -6,6 +6,7 @@ DROP INDEX IF EXISTS home_by_id;
|
|||
DROP TABLE IF EXISTS home_timeline;
|
||||
DROP MATERIALIZED VIEW IF EXISTS local_timeline;
|
||||
DROP MATERIALIZED VIEW IF EXISTS global_timeline;
|
||||
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id_and_user_id;
|
||||
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id;
|
||||
DROP MATERIALIZED VIEW IF EXISTS note_by_userid;
|
||||
DROP INDEX IF EXISTS note_by_id;
|
||||
|
|
|
@ -97,6 +97,17 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_renote_id AS
|
|||
PRIMARY KEY ("renoteId", "createdAt", "createdAtDate", "userId", "userHost", "visibility")
|
||||
WITH CLUSTERING ORDER BY ("createdAt" DESC);
|
||||
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_renote_id_and_user_id AS
|
||||
SELECT "renoteId", "userId", "createdAt", "createdAtDate", "userHost", "visibility", "id" FROM note
|
||||
WHERE "renoteId" IS NOT NULL
|
||||
AND "createdAt" IS NOT NULL
|
||||
AND "createdAtDate" IS NOT NULL
|
||||
AND "userId" IS NOT NULL
|
||||
AND "userHost" IS NOT NULL
|
||||
AND "visibility" IS NOT NULL
|
||||
PRIMARY KEY (("renoteId", "userId"), "createdAt", "createdAtDate", "userHost", "visibility")
|
||||
WITH CLUSTERING ORDER BY ("createdAt" DESC);
|
||||
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS global_timeline AS
|
||||
SELECT * FROM note
|
||||
WHERE "createdAtDate" IS NOT NULL
|
||||
|
|
|
@ -1,16 +1,36 @@
|
|||
import { prepared, scyllaClient } from "@/db/scylla";
|
||||
import { Notes } from "@/models/index.js";
|
||||
|
||||
/**
|
||||
* Sum of notes that have the given userId and renoteId
|
||||
*/
|
||||
export async function countSameRenotes(
|
||||
userId: string,
|
||||
renoteId: string,
|
||||
excludeNoteId: string | undefined,
|
||||
): Promise<number> {
|
||||
// 指定したユーザーの指定したノートのリノートがいくつあるか数える
|
||||
if (scyllaClient) {
|
||||
const result = await scyllaClient.execute(
|
||||
`SELECT "renoteId","userId","createdAt","id" FROM note_by_renote_id_and_user_id WHERE "renoteId" = ? AND "userId" = ?`,
|
||||
[renoteId, userId],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (excludeNoteId) {
|
||||
const renotes = result.rows
|
||||
.map((row) => row.get("id") as string)
|
||||
.filter((id) => id !== excludeNoteId);
|
||||
|
||||
return renotes.length;
|
||||
}
|
||||
|
||||
return result.rowLength;
|
||||
}
|
||||
|
||||
const query = Notes.createQueryBuilder("note")
|
||||
.where("note.userId = :userId", { userId })
|
||||
.andWhere("note.renoteId = :renoteId", { renoteId });
|
||||
|
||||
// 指定した投稿を除く
|
||||
// Exclude if specified
|
||||
if (excludeNoteId) {
|
||||
query.andWhere("note.id != :excludeNoteId", { excludeNoteId });
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ApiError } from "../../error.js";
|
|||
import { SECOND, HOUR } from "@/const.js";
|
||||
import type { Note } from "@/models/entities/note.js";
|
||||
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import { userByIdCache } from "@/services/user-cache.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
@ -59,6 +60,12 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
}
|
||||
|
||||
for (const note of renotes) {
|
||||
deleteNote(await Users.findOneByOrFail({ id: user.id }), note);
|
||||
deleteNote(
|
||||
await userByIdCache.fetch(
|
||||
user.id,
|
||||
() => Users.findOneByOrFail({ id: user.id }),
|
||||
),
|
||||
note,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,7 +22,12 @@ import { countSameRenotes } from "@/misc/count-same-renotes.js";
|
|||
import { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js";
|
||||
import { deliverToRelays } from "../relay.js";
|
||||
import meilisearch from "@/db/meilisearch.js";
|
||||
import { parseHomeTimeline, prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import {
|
||||
parseHomeTimeline,
|
||||
parseScyllaNote,
|
||||
prepared,
|
||||
scyllaClient,
|
||||
} from "@/db/scylla.js";
|
||||
|
||||
/**
|
||||
* 投稿を削除します。
|
||||
|
@ -41,12 +46,101 @@ export default async function (
|
|||
note.renoteId &&
|
||||
(await countSameRenotes(user.id, note.renoteId, note.id)) === 0
|
||||
) {
|
||||
Notes.decrement({ id: note.renoteId }, "renoteCount", 1);
|
||||
Notes.decrement({ id: note.renoteId }, "score", 1);
|
||||
if (scyllaClient) {
|
||||
const result = await scyllaClient.execute(
|
||||
prepared.note.select.byId,
|
||||
[note.renoteId],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (result.rowLength > 0) {
|
||||
const renote = parseScyllaNote(result.first());
|
||||
const count = isNaN(renote.renoteCount) ? 0 : renote.renoteCount;
|
||||
const score = isNaN(renote.score) ? 0 : renote.score;
|
||||
await scyllaClient.execute(
|
||||
prepared.note.update.renoteCount,
|
||||
[
|
||||
Math.max(count - 1, 0),
|
||||
Math.max(score - 1, 0),
|
||||
renote.createdAt,
|
||||
renote.createdAt,
|
||||
renote.userId,
|
||||
renote.userHost ?? "local",
|
||||
renote.visibility,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
const homeTimelines = await scyllaClient
|
||||
.execute(prepared.homeTimeline.select.byId, [renote.id], {
|
||||
prepare: true,
|
||||
})
|
||||
.then((result) => result.rows.map(parseHomeTimeline));
|
||||
// Do not issue BATCH because different home timelines involve different partitions
|
||||
for (const timeline of homeTimelines) {
|
||||
scyllaClient.execute(
|
||||
prepared.homeTimeline.update.renoteCount,
|
||||
[
|
||||
Math.max(count - 1, 0),
|
||||
Math.max(score - 1, 0),
|
||||
timeline.feedUserId,
|
||||
timeline.createdAtDate,
|
||||
timeline.createdAt,
|
||||
timeline.userId,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Notes.decrement({ id: note.renoteId }, "renoteCount", 1);
|
||||
Notes.decrement({ id: note.renoteId }, "score", 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (note.replyId) {
|
||||
await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
|
||||
if (scyllaClient) {
|
||||
const result = await scyllaClient.execute(
|
||||
prepared.note.select.byId,
|
||||
[note.replyId],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (result.rowLength > 0) {
|
||||
const reply = parseScyllaNote(result.first());
|
||||
const count = isNaN(reply.repliesCount) ? 0 : reply.repliesCount;
|
||||
await scyllaClient.execute(
|
||||
prepared.note.update.repliesCount,
|
||||
[
|
||||
Math.max(count - 1, 0),
|
||||
reply.createdAt,
|
||||
reply.createdAt,
|
||||
reply.userId,
|
||||
reply.userHost ?? "local",
|
||||
reply.visibility,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
const homeTimelines = await scyllaClient
|
||||
.execute(prepared.homeTimeline.select.byId, [reply.id], {
|
||||
prepare: true,
|
||||
})
|
||||
.then((result) => result.rows.map(parseHomeTimeline));
|
||||
// Do not issue BATCH because different home timelines involve different partitions
|
||||
for (const timeline of homeTimelines) {
|
||||
scyllaClient.execute(
|
||||
prepared.homeTimeline.update.repliesCount,
|
||||
[
|
||||
Math.max(count - 1, 0),
|
||||
timeline.feedUserId,
|
||||
timeline.createdAtDate,
|
||||
timeline.createdAt,
|
||||
timeline.userId,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
|
|
Loading…
Reference in a new issue