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 TABLE IF EXISTS home_timeline;
|
||||||
DROP MATERIALIZED VIEW IF EXISTS local_timeline;
|
DROP MATERIALIZED VIEW IF EXISTS local_timeline;
|
||||||
DROP MATERIALIZED VIEW IF EXISTS global_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_renote_id;
|
||||||
DROP MATERIALIZED VIEW IF EXISTS note_by_userid;
|
DROP MATERIALIZED VIEW IF EXISTS note_by_userid;
|
||||||
DROP INDEX IF EXISTS note_by_id;
|
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")
|
PRIMARY KEY ("renoteId", "createdAt", "createdAtDate", "userId", "userHost", "visibility")
|
||||||
WITH CLUSTERING ORDER BY ("createdAt" DESC);
|
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
|
CREATE MATERIALIZED VIEW IF NOT EXISTS global_timeline AS
|
||||||
SELECT * FROM note
|
SELECT * FROM note
|
||||||
WHERE "createdAtDate" IS NOT NULL
|
WHERE "createdAtDate" IS NOT NULL
|
||||||
|
|
|
@ -1,16 +1,36 @@
|
||||||
|
import { prepared, scyllaClient } from "@/db/scylla";
|
||||||
import { Notes } from "@/models/index.js";
|
import { Notes } from "@/models/index.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum of notes that have the given userId and renoteId
|
||||||
|
*/
|
||||||
export async function countSameRenotes(
|
export async function countSameRenotes(
|
||||||
userId: string,
|
userId: string,
|
||||||
renoteId: string,
|
renoteId: string,
|
||||||
excludeNoteId: string | undefined,
|
excludeNoteId: string | undefined,
|
||||||
): Promise<number> {
|
): 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")
|
const query = Notes.createQueryBuilder("note")
|
||||||
.where("note.userId = :userId", { userId })
|
.where("note.userId = :userId", { userId })
|
||||||
.andWhere("note.renoteId = :renoteId", { renoteId });
|
.andWhere("note.renoteId = :renoteId", { renoteId });
|
||||||
|
|
||||||
// 指定した投稿を除く
|
// Exclude if specified
|
||||||
if (excludeNoteId) {
|
if (excludeNoteId) {
|
||||||
query.andWhere("note.id != :excludeNoteId", { excludeNoteId });
|
query.andWhere("note.id != :excludeNoteId", { excludeNoteId });
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ApiError } from "../../error.js";
|
||||||
import { SECOND, HOUR } from "@/const.js";
|
import { SECOND, HOUR } from "@/const.js";
|
||||||
import type { Note } from "@/models/entities/note.js";
|
import type { Note } from "@/models/entities/note.js";
|
||||||
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
||||||
|
import { userByIdCache } from "@/services/user-cache.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -59,6 +60,12 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const note of renotes) {
|
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 { registerOrFetchInstanceDoc } from "../register-or-fetch-instance-doc.js";
|
||||||
import { deliverToRelays } from "../relay.js";
|
import { deliverToRelays } from "../relay.js";
|
||||||
import meilisearch from "@/db/meilisearch.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,13 +46,102 @@ export default async function (
|
||||||
note.renoteId &&
|
note.renoteId &&
|
||||||
(await countSameRenotes(user.id, note.renoteId, note.id)) === 0
|
(await countSameRenotes(user.id, note.renoteId, note.id)) === 0
|
||||||
) {
|
) {
|
||||||
|
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 }, "renoteCount", 1);
|
||||||
Notes.decrement({ id: note.renoteId }, "score", 1);
|
Notes.decrement({ id: note.renoteId }, "score", 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (note.replyId) {
|
if (note.replyId) {
|
||||||
|
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);
|
await Notes.decrement({ id: note.replyId }, "repliesCount", 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
publishNoteStream(note.id, "deleted", {
|
publishNoteStream(note.id, "deleted", {
|
||||||
|
|
Loading…
Reference in a new issue