fix: search compatibility
This commit is contained in:
parent
ed26870807
commit
4f05dbbc9b
5 changed files with 49 additions and 77 deletions
|
@ -6,6 +6,8 @@ import { Note } from "@/models/entities/note.js";
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
import { ILocalUser } from "@/models/entities/user.js";
|
import { ILocalUser } from "@/models/entities/user.js";
|
||||||
import { Followings, Users } from "@/models/index.js";
|
import { Followings, Users } from "@/models/index.js";
|
||||||
|
import { userByIdCache } from "@/services/user-cache.js";
|
||||||
|
import { ScyllaNote } from "./scylla.js";
|
||||||
|
|
||||||
const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
|
const logger = dbLogger.createSubLogger("meilisearch", "gray", false);
|
||||||
|
|
||||||
|
@ -347,20 +349,14 @@ export default hasConfig
|
||||||
sort: sortRules,
|
sort: sortRules,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
ingestNote: async (ingestNotes: Note | Note[]) => {
|
ingestNote: async (ingestNotes: Note[] | ScyllaNote[]) => {
|
||||||
if (ingestNotes instanceof Note) {
|
|
||||||
ingestNotes = [ingestNotes];
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexingBatch: MeilisearchNote[] = [];
|
const indexingBatch: MeilisearchNote[] = [];
|
||||||
|
|
||||||
for (const note of ingestNotes) {
|
for (const note of ingestNotes) {
|
||||||
if (note.user === undefined) {
|
if (!note.user) {
|
||||||
note.user = await Users.findOne({
|
note.user = await userByIdCache.fetch(note.userId, () =>
|
||||||
where: {
|
Users.findOneByOrFail({ id: note.userId }),
|
||||||
id: note.userId,
|
);
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let attachmentType = "";
|
let attachmentType = "";
|
||||||
|
@ -408,40 +404,15 @@ export default hasConfig
|
||||||
return {
|
return {
|
||||||
health: health.status,
|
health: health.status,
|
||||||
size: stats.databaseSize,
|
size: stats.databaseSize,
|
||||||
indexed_count: stats.indexes["posts"].numberOfDocuments,
|
indexed_count: stats.indexes.posts.numberOfDocuments,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
deleteNotes: async (note: Note | Note[] | string | string[]) => {
|
deleteNotes: async (notes: Note[] | ScyllaNote[] | string[]) => {
|
||||||
if (note instanceof Note) {
|
const deletionBatch = notes
|
||||||
note = [note];
|
.map((n) => (typeof n === "string" ? n : n.id))
|
||||||
}
|
.filter((id) => !!id);
|
||||||
if (typeof note === "string") {
|
|
||||||
note = [note];
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletionBatch = note
|
await posts.deleteDocuments(deletionBatch).then(() => {
|
||||||
.map((n) => {
|
|
||||||
if (n instanceof Note) {
|
|
||||||
return n.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n.length > 0) return n;
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
`Failed to delete note from Meilisearch, invalid post ID: ${JSON.stringify(
|
|
||||||
n,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`Invalid note ID passed to meilisearch deleteNote: ${JSON.stringify(
|
|
||||||
n,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter((el) => el !== null);
|
|
||||||
|
|
||||||
await posts.deleteDocuments(deletionBatch as string[]).then(() => {
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`submitted ${deletionBatch.length} large batch for deletion`,
|
`submitted ${deletionBatch.length} large batch for deletion`,
|
||||||
);
|
);
|
||||||
|
|
|
@ -414,12 +414,11 @@ export const NoteRepository = db.getRepository(Note).extend({
|
||||||
const targets = [...notes.map((n) => n.id), ...renoteIds];
|
const targets = [...notes.map((n) => n.id), ...renoteIds];
|
||||||
let myReactions: NoteReaction[] = [];
|
let myReactions: NoteReaction[] = [];
|
||||||
if (scyllaClient) {
|
if (scyllaClient) {
|
||||||
const result = await scyllaClient.execute(
|
myReactions = await scyllaClient
|
||||||
prepared.reaction.select.byNoteAndUser,
|
.execute(prepared.reaction.select.byNoteAndUser, [targets, [meId]], {
|
||||||
[targets, [meId]],
|
prepare: true,
|
||||||
{ prepare: true },
|
})
|
||||||
);
|
.then((result) => result.rows.map(parseScyllaReaction));
|
||||||
myReactions = result.rows.map(parseScyllaReaction);
|
|
||||||
} else {
|
} else {
|
||||||
myReactions = await NoteReactions.findBy({
|
myReactions = await NoteReactions.findBy({
|
||||||
userId: meId,
|
userId: meId,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { generateVisibilityQuery } from "../../common/generate-visibility-query.
|
||||||
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
||||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||||
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
||||||
|
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -68,7 +69,7 @@ export const paramDef = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default define(meta, paramDef, async (ps, me) => {
|
export default define(meta, paramDef, async (ps, me) => {
|
||||||
if (es == null && sonic == null && meilisearch == null) {
|
if (!es && !sonic && !meilisearch && !scyllaClient) {
|
||||||
const query = makePaginationQuery(
|
const query = makePaginationQuery(
|
||||||
Notes.createQueryBuilder("note"),
|
Notes.createQueryBuilder("note"),
|
||||||
ps.sinceId,
|
ps.sinceId,
|
||||||
|
@ -86,17 +87,8 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
query
|
query
|
||||||
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` })
|
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` })
|
||||||
.andWhere("note.visibility = 'public'")
|
.andWhere("note.visibility = 'public'")
|
||||||
.innerJoinAndSelect("note.user", "user")
|
|
||||||
.leftJoinAndSelect("user.avatar", "avatar")
|
|
||||||
.leftJoinAndSelect("user.banner", "banner")
|
|
||||||
.leftJoinAndSelect("note.reply", "reply")
|
.leftJoinAndSelect("note.reply", "reply")
|
||||||
.leftJoinAndSelect("note.renote", "renote")
|
.leftJoinAndSelect("note.renote", "renote");
|
||||||
.leftJoinAndSelect("reply.user", "replyUser")
|
|
||||||
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
|
|
||||||
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
|
|
||||||
.leftJoinAndSelect("renote.user", "renoteUser")
|
|
||||||
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
|
|
||||||
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
|
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
generateVisibilityQuery(query, me);
|
||||||
if (me) generateMutedUserQuery(query, me);
|
if (me) generateMutedUserQuery(query, me);
|
||||||
|
@ -110,7 +102,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
const chunkSize = 100;
|
const chunkSize = 100;
|
||||||
|
|
||||||
// Use sonic to fetch and step through all search results that could match the requirements
|
// Use sonic to fetch and step through all search results that could match the requirements
|
||||||
const ids = [];
|
const ids: string[] = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
const results = await sonic.search.query(
|
const results = await sonic.search.query(
|
||||||
sonic.collection,
|
sonic.collection,
|
||||||
|
@ -151,18 +143,25 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort all the results by note id DESC (newest first)
|
// Sort all the results by note id DESC (newest first)
|
||||||
ids.sort((a, b) => b - a);
|
ids.sort().reverse();
|
||||||
|
|
||||||
// Fetch the notes from the database until we have enough to satisfy the limit
|
// Fetch the notes from the database until we have enough to satisfy the limit
|
||||||
start = 0;
|
start = 0;
|
||||||
const found = [];
|
const found = [];
|
||||||
while (found.length < ps.limit && start < ids.length) {
|
while (found.length < ps.limit && start < ids.length) {
|
||||||
const chunk = ids.slice(start, start + chunkSize);
|
const chunk = ids.slice(start, start + chunkSize);
|
||||||
const notes: Note[] = await Notes.find({
|
let notes: Note[] = [];
|
||||||
where: {
|
if (scyllaClient) {
|
||||||
id: In(chunk),
|
notes = await scyllaClient
|
||||||
},
|
.execute(prepared.note.select.byIds, [chunk], { prepare: true })
|
||||||
});
|
.then((result) => result.rows.map(parseScyllaNote));
|
||||||
|
} else {
|
||||||
|
notes = await Notes.find({
|
||||||
|
where: {
|
||||||
|
id: In(chunk),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// The notes are checked for visibility and muted/blocked users when packed
|
// The notes are checked for visibility and muted/blocked users when packed
|
||||||
found.push(...(await Notes.packMany(notes, me)));
|
found.push(...(await Notes.packMany(notes, me)));
|
||||||
|
@ -239,13 +238,14 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
while (found.length < ps.limit && start < noteIDs.length) {
|
while (found.length < ps.limit && start < noteIDs.length) {
|
||||||
const chunk = noteIDs.slice(start, start + chunkSize);
|
const chunk = noteIDs.slice(start, start + chunkSize);
|
||||||
|
|
||||||
let query: FindManyOptions = {
|
let notes: Note[] = [];
|
||||||
where: {
|
if (scyllaClient) {
|
||||||
id: In(chunk),
|
notes = await scyllaClient
|
||||||
},
|
.execute(prepared.note.select.byIds, [chunk], { prepare: true })
|
||||||
};
|
.then((result) => result.rows.map(parseScyllaNote));
|
||||||
|
} else {
|
||||||
const notes: Note[] = await Notes.find(query);
|
notes = await Notes.findBy({ id: In(chunk) });
|
||||||
|
}
|
||||||
|
|
||||||
// Re-order the note result according to the noteIDs array (cannot be undefined, we map this earlier)
|
// Re-order the note result according to the noteIDs array (cannot be undefined, we map this earlier)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -262,7 +262,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
} else {
|
} else if (es && !scyllaClient) {
|
||||||
const userQuery =
|
const userQuery =
|
||||||
ps.userId != null
|
ps.userId != null
|
||||||
? [
|
? [
|
||||||
|
@ -329,7 +329,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
const hits = result.body.hits.hits.map((hit: any) => hit._id);
|
const hits = result.body.hits.hits.map((hit: any) => hit._id);
|
||||||
|
|
||||||
if (hits.length === 0) return [];
|
if (hits.length === 0) return await Notes.packMany([]);
|
||||||
|
|
||||||
// Fetch found notes
|
// Fetch found notes
|
||||||
const notes = await Notes.find({
|
const notes = await Notes.find({
|
||||||
|
@ -343,4 +343,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
return await Notes.packMany(notes, me);
|
return await Notes.packMany(notes, me);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await Notes.packMany([]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1007,7 +1007,7 @@ export async function index(note: Note, reindexing: boolean): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meilisearch && !reindexing) {
|
if (meilisearch && !reindexing) {
|
||||||
await meilisearch.ingestNote(note);
|
await meilisearch.ingestNote([note]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -326,7 +326,7 @@ export default async function (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meilisearch) {
|
if (meilisearch) {
|
||||||
await meilisearch.deleteNotes(note.id);
|
await meilisearch.deleteNotes([note.id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue