diff --git a/cli.ts b/cli.ts index a21d9000..1d619948 100644 --- a/cli.ts +++ b/cli.ts @@ -3,7 +3,7 @@ import chalk from "chalk"; import { client } from "~database/datasource"; import { createNewLocalUser } from "~database/entities/User"; import Table from "cli-table"; -import { rebuildSearchIndexes, SonicIndexType } from "@meilisearch"; +import { rebuildSearchIndexes, MeiliIndexType } from "@meilisearch"; const args = process.argv; @@ -542,7 +542,7 @@ switch (command) { ); await rebuildSearchIndexes( - [SonicIndexType.Statuses], + [MeiliIndexType.Statuses], batchSize ); @@ -561,7 +561,7 @@ switch (command) { ); await rebuildSearchIndexes( - [SonicIndexType.Accounts], + [MeiliIndexType.Accounts], batchSize ); diff --git a/server/api/api/v2/search/index.ts b/server/api/api/v2/search/index.ts index 5e260867..958238cd 100644 --- a/server/api/api/v2/search/index.ts +++ b/server/api/api/v2/search/index.ts @@ -1,7 +1,14 @@ import { applyConfig } from "@api"; +import { MeiliIndexType, meilisearch } from "@meilisearch"; import { parseRequest } from "@request"; import { errorResponse, jsonResponse } from "@response"; -import { getFromRequest } from "~database/entities/User"; +import { client } from "~database/datasource"; +import { statusAndUserRelations, statusToAPI } from "~database/entities/Status"; +import { + getFromRequest, + userRelations, + userToAPI, +} from "~database/entities/User"; import type { APIRouteMeta } from "~types/api"; export const meta: APIRouteMeta = applyConfig({ @@ -29,9 +36,9 @@ export default async (req: Request): Promise => { resolve, following, account_id, - max_id, - min_id, - limit, + // max_id, + // min_id, + limit = 20, offset, } = await parseRequest<{ q?: string; @@ -52,9 +59,81 @@ export default async (req: Request): Promise => { ); } + if (limit < 1 || limit > 40) { + return errorResponse("Limit must be between 1 and 40", 400); + } + + let accountResults: { id: string }[] = []; + let statusResults: { id: string }[] = []; + + if (!type || type === "accounts") { + accountResults = ( + await meilisearch.index(MeiliIndexType.Accounts).search<{ + id: string; + }>(q, { + limit: Number(limit) || 10, + offset: Number(offset) || 0, + sort: ["createdAt:desc"], + }) + ).hits; + } + + if (!type || type === "statuses") { + statusResults = ( + await meilisearch.index(MeiliIndexType.Statuses).search<{ + id: string; + }>(q, { + limit: Number(limit) || 10, + offset: Number(offset) || 0, + sort: ["createdAt:desc"], + }) + ).hits; + } + + const accounts = await client.user.findMany({ + where: { + id: { + in: accountResults.map(hit => hit.id), + }, + relationshipSubjects: { + some: { + subjectId: user?.id, + following: following ? true : undefined, + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + include: userRelations, + }); + + const statuses = await client.status.findMany({ + where: { + id: { + in: statusResults.map(hit => hit.id), + }, + author: { + relationshipSubjects: { + some: { + subjectId: user?.id, + following: following ? true : undefined, + }, + }, + }, + authorId: account_id ? account_id : undefined, + }, + orderBy: { + createdAt: "desc", + }, + include: statusAndUserRelations, + }); + return jsonResponse({ - accounts: [], - statuses: [], + accounts: accounts.map(account => userToAPI(account)), + statuses: await Promise.all( + statuses.map(status => statusToAPI(status)) + ), hashtags: [], }); }; diff --git a/utils/meilisearch.ts b/utils/meilisearch.ts index ba097d0b..1a50ff8d 100644 --- a/utils/meilisearch.ts +++ b/utils/meilisearch.ts @@ -14,6 +14,22 @@ export const connectMeili = async () => { if (!config.meilisearch.enabled) return; if (await meilisearch.isHealthy()) { + await meilisearch + .index(MeiliIndexType.Accounts) + .updateSortableAttributes(["createdAt"]); + + await meilisearch + .index(MeiliIndexType.Accounts) + .updateSearchableAttributes(["username", "displayName", "note"]); + + await meilisearch + .index(MeiliIndexType.Statuses) + .updateSortableAttributes(["createdAt"]); + + await meilisearch + .index(MeiliIndexType.Statuses) + .updateSearchableAttributes(["content"]); + console.log( `${chalk.green(`✓`)} ${chalk.bold(`Connected to Meilisearch`)}` ); @@ -27,7 +43,7 @@ export const connectMeili = async () => { } }; -export enum SonicIndexType { +export enum MeiliIndexType { Accounts = "accounts", Statuses = "statuses", } @@ -35,7 +51,7 @@ export enum SonicIndexType { export const getNthDatabaseAccountBatch = ( n: number, batchSize = 1000 -): Promise[]> => { +): Promise[]> => { return client.user.findMany({ skip: n * batchSize, take: batchSize, @@ -44,6 +60,10 @@ export const getNthDatabaseAccountBatch = ( username: true, displayName: true, note: true, + createdAt: true, + }, + orderBy: { + createdAt: "asc", }, }); }; @@ -51,25 +71,26 @@ export const getNthDatabaseAccountBatch = ( export const getNthDatabaseStatusBatch = ( n: number, batchSize = 1000 -): Promise[]> => { +): Promise[]> => { return client.status.findMany({ skip: n * batchSize, take: batchSize, select: { id: true, - authorId: true, content: true, + createdAt: true, + }, + orderBy: { + createdAt: "asc", }, }); }; export const rebuildSearchIndexes = async ( - indexes: SonicIndexType[], + indexes: MeiliIndexType[], batchSize = 100 ) => { - if (indexes.includes(SonicIndexType.Accounts)) { - // await sonicIngestor.flushc(SonicIndexType.Accounts); - + if (indexes.includes(MeiliIndexType.Accounts)) { const accountCount = await client.user.count(); for (let i = 0; i < accountCount / batchSize; i++) { @@ -81,16 +102,22 @@ export const rebuildSearchIndexes = async ( // Sync with Meilisearch await meilisearch - .index(SonicIndexType.Accounts) + .index(MeiliIndexType.Accounts) .addDocuments(accounts); } - console.log(`${chalk.green(`✓`)} ${chalk.bold(`Done!`)}`); + const meiliAccountCount = ( + await meilisearch.index(MeiliIndexType.Accounts).getStats() + ).numberOfDocuments; + + console.log( + `${chalk.green(`✓`)} ${chalk.bold( + `Done! ${meiliAccountCount} accounts indexed` + )}` + ); } - if (indexes.includes(SonicIndexType.Statuses)) { - // await sonicIngestor.flushc(SonicIndexType.Statuses); - + if (indexes.includes(MeiliIndexType.Statuses)) { const statusCount = await client.status.count(); for (let i = 0; i < statusCount / batchSize; i++) { @@ -102,10 +129,18 @@ export const rebuildSearchIndexes = async ( // Sync with Meilisearch await meilisearch - .index(SonicIndexType.Statuses) + .index(MeiliIndexType.Statuses) .addDocuments(statuses); } - console.log(`${chalk.green(`✓`)} ${chalk.bold(`Done!`)}`); + const meiliStatusCount = ( + await meilisearch.index(MeiliIndexType.Statuses).getStats() + ).numberOfDocuments; + + console.log( + `${chalk.green(`✓`)} ${chalk.bold( + `Done! ${meiliStatusCount} statuses indexed` + )}` + ); } };