fix reaction and unrenote
This commit is contained in:
parent
965ad49c9b
commit
2e6c04a6cb
6 changed files with 146 additions and 21 deletions
|
@ -113,6 +113,20 @@ export const scyllaQueries = {
|
|||
byId: `SELECT * FROM home_timeline WHERE "id" = ?`,
|
||||
},
|
||||
delete: `DELETE FROM home_timeline WHERE "feedUserId" = ? AND "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ?`,
|
||||
update: {
|
||||
renoteCount: `UPDATE home_timeline SET
|
||||
"renoteCount" = ?,
|
||||
"score" = ?
|
||||
WHERE "feedUserId" = ? AND "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ? IF EXISTS`,
|
||||
repliesCount: `UPDATE home_timeline SET
|
||||
"repliesCount" = ?
|
||||
WHERE "feedUserId" = ? AND "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ? IF EXISTS`,
|
||||
reactions: `UPDATE home_timeline SET
|
||||
"emojis" = ?,
|
||||
"reactions" = ?,
|
||||
"score" = ?
|
||||
WHERE "feedUserId" = ? AND "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ? IF EXISTS`,
|
||||
}
|
||||
},
|
||||
localTimeline: {
|
||||
select: {
|
||||
|
|
|
@ -125,17 +125,17 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
UserBlockedCache.init(user.id).then((cache) => cache.getAll()),
|
||||
RenoteMutingsCache.init(user.id).then((cache) => cache.getAll()),
|
||||
]);
|
||||
const validUserIds = [user.id, ...followingUserIds];
|
||||
const homeUserIds = [user.id, ...followingUserIds];
|
||||
const optFilter = (n: ScyllaNote) =>
|
||||
!n.renoteId || !!n.text || n.files.length > 0 || n.hasPoll;
|
||||
|
||||
const filter = async (notes: ScyllaNote[]) => {
|
||||
let filtered = notes.filter(
|
||||
(n) =>
|
||||
validUserIds.includes(n.userId) ||
|
||||
(n.visibility === "public" && !n.userHost),
|
||||
);
|
||||
filtered = await filterChannel(filtered, user, followingChannelIds);
|
||||
const homeFilter = (notes: ScyllaNote[]) =>
|
||||
notes.filter((n) => homeUserIds.includes(n.userId));
|
||||
const localFilter = (notes: ScyllaNote[]) =>
|
||||
notes.filter((n) => !homeUserIds.includes(n.userId));
|
||||
|
||||
const commonFilter = async (notes: ScyllaNote[]) => {
|
||||
let filtered = await filterChannel(notes, user, followingChannelIds);
|
||||
filtered = await filterReply(filtered, ps.withReplies, user);
|
||||
filtered = await filterVisibility(filtered, user, followingUserIds);
|
||||
filtered = await filterMutedUser(
|
||||
|
@ -168,8 +168,15 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
const foundPacked = [];
|
||||
while (foundPacked.length < ps.limit) {
|
||||
const [homeFoundNotes, localFoundNotes] = await Promise.all([
|
||||
execNotePaginationQuery("home", ps, filter, user.id),
|
||||
execNotePaginationQuery("local", ps, filter),
|
||||
execNotePaginationQuery(
|
||||
"home",
|
||||
ps,
|
||||
(notes) => commonFilter(homeFilter(notes)),
|
||||
user.id,
|
||||
),
|
||||
execNotePaginationQuery("local", ps, (notes) =>
|
||||
commonFilter(localFilter(notes)),
|
||||
),
|
||||
]);
|
||||
const foundNotes = [...homeFoundNotes, ...localFoundNotes]
|
||||
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) // Descendent
|
||||
|
|
|
@ -4,6 +4,8 @@ import define from "../../define.js";
|
|||
import { getNote } from "../../common/getters.js";
|
||||
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";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
@ -42,10 +44,19 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
throw err;
|
||||
});
|
||||
|
||||
const renotes = await Notes.findBy({
|
||||
userId: user.id,
|
||||
renoteId: note.id,
|
||||
});
|
||||
let renotes: Note[] = [];
|
||||
|
||||
if (scyllaClient) {
|
||||
const notes = await scyllaClient
|
||||
.execute(prepared.note.select.byRenoteId, [note.id], { prepare: true })
|
||||
.then((result) => result.rows.map(parseScyllaNote));
|
||||
renotes = notes.filter((n) => n.userId === user.id);
|
||||
} else {
|
||||
renotes = await Notes.findBy({
|
||||
userId: user.id,
|
||||
renoteId: note.id,
|
||||
});
|
||||
}
|
||||
|
||||
for (const note of renotes) {
|
||||
deleteNote(await Users.findOneByOrFail({ id: user.id }), note);
|
||||
|
|
|
@ -68,7 +68,12 @@ import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
|||
import meilisearch from "../../db/meilisearch.js";
|
||||
import { redisClient } from "@/db/redis.js";
|
||||
import { Mutex } from "redis-semaphore";
|
||||
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import {
|
||||
parseHomeTimeline,
|
||||
parseScyllaNote,
|
||||
prepared,
|
||||
scyllaClient,
|
||||
} from "@/db/scylla.js";
|
||||
|
||||
export const mutedWordsCache = new Cache<
|
||||
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
|
||||
|
@ -696,6 +701,26 @@ async function incRenoteCount(renote: Note) {
|
|||
],
|
||||
{ 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,
|
||||
[
|
||||
count + 1,
|
||||
score + 1,
|
||||
timeline.feedUserId,
|
||||
timeline.createdAtDate,
|
||||
timeline.createdAt,
|
||||
timeline.userId,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Notes.createQueryBuilder()
|
||||
.update()
|
||||
|
@ -857,7 +882,7 @@ async function insertNote(
|
|||
// Include the local user itself
|
||||
localFollowers.push(user.id);
|
||||
}
|
||||
// Do not issue BATCH because different queries of inserting post to home timelines involve different partitions
|
||||
// Do not issue BATCH because different home timelines involve different partitions
|
||||
for (const follower of localFollowers) {
|
||||
// no need to wait
|
||||
scyllaClient.execute(
|
||||
|
@ -1047,6 +1072,25 @@ async function saveReply(reply: Note) {
|
|||
],
|
||||
{ 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,
|
||||
[
|
||||
count + 1,
|
||||
timeline.feedUserId,
|
||||
timeline.createdAtDate,
|
||||
timeline.createdAt,
|
||||
timeline.userId,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await Notes.increment({ id: reply.id }, "repliesCount", 1);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import deleteReaction from "./delete.js";
|
|||
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
|
||||
import type { NoteReaction } from "@/models/entities/note-reaction.js";
|
||||
import { IdentifiableError } from "@/misc/identifiable-error.js";
|
||||
import { prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import { parseHomeTimeline, prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import { EmojiCache } from "@/misc/populate-emojis.js";
|
||||
|
||||
export default async (
|
||||
|
@ -108,12 +108,13 @@ export default async (
|
|||
note.reactions[_reaction] = current + 1;
|
||||
const emojiName = decodeReaction(_reaction).reaction.replaceAll(":", "");
|
||||
const date = new Date(note.createdAt.getTime());
|
||||
const score = isNaN(note.score) ? 0 : note.score;
|
||||
await scyllaClient.execute(
|
||||
prepared.note.update.reactions,
|
||||
[
|
||||
note.emojis.concat(emojiName),
|
||||
note.reactions,
|
||||
(note.score ?? 0) + 1,
|
||||
score + 1,
|
||||
date,
|
||||
date,
|
||||
note.userId,
|
||||
|
@ -122,6 +123,27 @@ export default async (
|
|||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
const homeTimelines = await scyllaClient
|
||||
.execute(prepared.homeTimeline.select.byId, [note.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.reactions,
|
||||
[
|
||||
note.emojis.concat(emojiName),
|
||||
note.reactions,
|
||||
score + 1,
|
||||
timeline.feedUserId,
|
||||
timeline.createdAtDate,
|
||||
timeline.createdAt,
|
||||
timeline.userId,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const sql = `jsonb_set("reactions", '{${_reaction}}', (COALESCE("reactions"->>'${_reaction}', '0')::int + 1)::text::jsonb)`;
|
||||
await Notes.createQueryBuilder()
|
||||
|
|
|
@ -8,7 +8,12 @@ import type { User, IRemoteUser } from "@/models/entities/user.js";
|
|||
import type { Note } from "@/models/entities/note.js";
|
||||
import { NoteReactions, Users, Notes } from "@/models/index.js";
|
||||
import { decodeReaction } from "@/misc/reaction-lib.js";
|
||||
import { parseScyllaReaction, prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import {
|
||||
parseHomeTimeline,
|
||||
parseScyllaReaction,
|
||||
prepared,
|
||||
scyllaClient,
|
||||
} from "@/db/scylla.js";
|
||||
import type { NoteReaction } from "@/models/entities/note-reaction.js";
|
||||
|
||||
export default async (
|
||||
|
@ -19,7 +24,7 @@ export default async (
|
|||
if (scyllaClient) {
|
||||
const result = await scyllaClient.execute(
|
||||
prepared.reaction.select.byNoteAndUser,
|
||||
[note.id, user.id],
|
||||
[[note.id], [user.id]],
|
||||
{ prepare: true },
|
||||
);
|
||||
reaction =
|
||||
|
@ -58,13 +63,14 @@ export default async (
|
|||
const date = new Date(note.createdAt.getTime());
|
||||
const emojiName = reaction.reaction.replaceAll(":", "");
|
||||
const emojiIndex = note.emojis.indexOf(emojiName);
|
||||
const score = isNaN(note.score) ? 0 : note.score;
|
||||
if (emojiIndex >= 0 && count === 0) note.emojis.splice(emojiIndex, 1);
|
||||
await scyllaClient.execute(
|
||||
prepared.note.update.reactions,
|
||||
[
|
||||
note.emojis,
|
||||
note.reactions,
|
||||
Math.max((note.score ?? 0) - 1, 0),
|
||||
Math.max(score - 1, 0),
|
||||
date,
|
||||
date,
|
||||
note.userId,
|
||||
|
@ -73,6 +79,27 @@ export default async (
|
|||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
const homeTimelines = await scyllaClient
|
||||
.execute(prepared.homeTimeline.select.byId, [note.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.reactions,
|
||||
[
|
||||
note.emojis.concat(emojiName),
|
||||
note.reactions,
|
||||
Math.max(score - 1, 0),
|
||||
timeline.feedUserId,
|
||||
timeline.createdAtDate,
|
||||
timeline.createdAt,
|
||||
timeline.userId,
|
||||
],
|
||||
{ prepare: true },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const sql = `jsonb_set("reactions", '{${reaction.reaction}}', (COALESCE("reactions"->>'${reaction.reaction}', '0')::int - 1)::text::jsonb)`;
|
||||
await Notes.createQueryBuilder()
|
||||
|
|
Loading…
Reference in a new issue