mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat: Add Search endpoint
This commit is contained in:
parent
aa0813fef8
commit
553b558c1a
6
cli.ts
6
cli.ts
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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: [],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue