feat: Add Search endpoint

This commit is contained in:
Jesse Wierzbinski 2023-12-02 18:40:10 -10:00
parent aa0813fef8
commit 553b558c1a
No known key found for this signature in database
3 changed files with 138 additions and 24 deletions

6
cli.ts
View file

@ -3,7 +3,7 @@ import chalk from "chalk";
import { client } from "~database/datasource"; import { client } from "~database/datasource";
import { createNewLocalUser } from "~database/entities/User"; import { createNewLocalUser } from "~database/entities/User";
import Table from "cli-table"; import Table from "cli-table";
import { rebuildSearchIndexes, SonicIndexType } from "@meilisearch"; import { rebuildSearchIndexes, MeiliIndexType } from "@meilisearch";
const args = process.argv; const args = process.argv;
@ -542,7 +542,7 @@ switch (command) {
); );
await rebuildSearchIndexes( await rebuildSearchIndexes(
[SonicIndexType.Statuses], [MeiliIndexType.Statuses],
batchSize batchSize
); );
@ -561,7 +561,7 @@ switch (command) {
); );
await rebuildSearchIndexes( await rebuildSearchIndexes(
[SonicIndexType.Accounts], [MeiliIndexType.Accounts],
batchSize batchSize
); );

View file

@ -1,7 +1,14 @@
import { applyConfig } from "@api"; import { applyConfig } from "@api";
import { MeiliIndexType, meilisearch } from "@meilisearch";
import { parseRequest } from "@request"; import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; 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"; import type { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({ export const meta: APIRouteMeta = applyConfig({
@ -29,9 +36,9 @@ export default async (req: Request): Promise<Response> => {
resolve, resolve,
following, following,
account_id, account_id,
max_id, // max_id,
min_id, // min_id,
limit, limit = 20,
offset, offset,
} = await parseRequest<{ } = await parseRequest<{
q?: string; q?: string;
@ -52,9 +59,81 @@ export default async (req: Request): Promise<Response> => {
); );
} }
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({ return jsonResponse({
accounts: [], accounts: accounts.map(account => userToAPI(account)),
statuses: [], statuses: await Promise.all(
statuses.map(status => statusToAPI(status))
),
hashtags: [], hashtags: [],
}); });
}; };

View file

@ -14,6 +14,22 @@ export const connectMeili = async () => {
if (!config.meilisearch.enabled) return; if (!config.meilisearch.enabled) return;
if (await meilisearch.isHealthy()) { 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( console.log(
`${chalk.green(``)} ${chalk.bold(`Connected to Meilisearch`)}` `${chalk.green(``)} ${chalk.bold(`Connected to Meilisearch`)}`
); );
@ -27,7 +43,7 @@ export const connectMeili = async () => {
} }
}; };
export enum SonicIndexType { export enum MeiliIndexType {
Accounts = "accounts", Accounts = "accounts",
Statuses = "statuses", Statuses = "statuses",
} }
@ -35,7 +51,7 @@ export enum SonicIndexType {
export const getNthDatabaseAccountBatch = ( export const getNthDatabaseAccountBatch = (
n: number, n: number,
batchSize = 1000 batchSize = 1000
): Promise<Record<string, string>[]> => { ): Promise<Record<string, string | Date>[]> => {
return client.user.findMany({ return client.user.findMany({
skip: n * batchSize, skip: n * batchSize,
take: batchSize, take: batchSize,
@ -44,6 +60,10 @@ export const getNthDatabaseAccountBatch = (
username: true, username: true,
displayName: true, displayName: true,
note: true, note: true,
createdAt: true,
},
orderBy: {
createdAt: "asc",
}, },
}); });
}; };
@ -51,25 +71,26 @@ export const getNthDatabaseAccountBatch = (
export const getNthDatabaseStatusBatch = ( export const getNthDatabaseStatusBatch = (
n: number, n: number,
batchSize = 1000 batchSize = 1000
): Promise<Record<string, string>[]> => { ): Promise<Record<string, string | Date>[]> => {
return client.status.findMany({ return client.status.findMany({
skip: n * batchSize, skip: n * batchSize,
take: batchSize, take: batchSize,
select: { select: {
id: true, id: true,
authorId: true,
content: true, content: true,
createdAt: true,
},
orderBy: {
createdAt: "asc",
}, },
}); });
}; };
export const rebuildSearchIndexes = async ( export const rebuildSearchIndexes = async (
indexes: SonicIndexType[], indexes: MeiliIndexType[],
batchSize = 100 batchSize = 100
) => { ) => {
if (indexes.includes(SonicIndexType.Accounts)) { if (indexes.includes(MeiliIndexType.Accounts)) {
// await sonicIngestor.flushc(SonicIndexType.Accounts);
const accountCount = await client.user.count(); const accountCount = await client.user.count();
for (let i = 0; i < accountCount / batchSize; i++) { for (let i = 0; i < accountCount / batchSize; i++) {
@ -81,16 +102,22 @@ export const rebuildSearchIndexes = async (
// Sync with Meilisearch // Sync with Meilisearch
await meilisearch await meilisearch
.index(SonicIndexType.Accounts) .index(MeiliIndexType.Accounts)
.addDocuments(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)) { if (indexes.includes(MeiliIndexType.Statuses)) {
// await sonicIngestor.flushc(SonicIndexType.Statuses);
const statusCount = await client.status.count(); const statusCount = await client.status.count();
for (let i = 0; i < statusCount / batchSize; i++) { for (let i = 0; i < statusCount / batchSize; i++) {
@ -102,10 +129,18 @@ export const rebuildSearchIndexes = async (
// Sync with Meilisearch // Sync with Meilisearch
await meilisearch await meilisearch
.index(SonicIndexType.Statuses) .index(MeiliIndexType.Statuses)
.addDocuments(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`
)}`
);
} }
}; };