diff --git a/classes/search/search-manager.ts b/classes/search/search-manager.ts index f1f5b601..01884b32 100644 --- a/classes/search/search-manager.ts +++ b/classes/search/search-manager.ts @@ -184,16 +184,18 @@ export class SonicSearchManager { * Rebuild search indexes * @param indexes Indexes to rebuild * @param batchSize Size of each batch + * @param progressCallback Callback for progress updates */ async rebuildSearchIndexes( indexes: SonicIndexType[], batchSize = 100, + progressCallback?: (progress: number) => void, ): Promise { for (const index of indexes) { if (index === SonicIndexType.Accounts) { - await this.rebuildAccountsIndex(batchSize); + await this.rebuildAccountsIndex(batchSize, progressCallback); } else if (index === SonicIndexType.Statuses) { - await this.rebuildStatusesIndex(batchSize); + await this.rebuildStatusesIndex(batchSize, progressCallback); } } } @@ -201,8 +203,12 @@ export class SonicSearchManager { /** * Rebuild accounts index * @param batchSize Size of each batch + * @param progressCallback Callback for progress updates */ - private async rebuildAccountsIndex(batchSize: number): Promise { + private async rebuildAccountsIndex( + batchSize: number, + progressCallback?: (progress: number) => void, + ): Promise { const accountCount = await User.getCount(); const batchCount = Math.ceil(accountCount / batchSize); @@ -221,15 +227,19 @@ export class SonicSearchManager { ), ), ); - this.logger.info`Indexed accounts batch ${i + 1}/${batchCount}`; + progressCallback?.((i + 1) / batchCount); } } /** * Rebuild statuses index * @param batchSize Size of each batch + * @param progressCallback Callback for progress updates */ - private async rebuildStatusesIndex(batchSize: number): Promise { + private async rebuildStatusesIndex( + batchSize: number, + progressCallback?: (progress: number) => void, + ): Promise { const statusCount = await Note.getCount(); const batchCount = Math.ceil(statusCount / batchSize); @@ -245,7 +255,7 @@ export class SonicSearchManager { ), ), ); - this.logger.info`Indexed statuses batch ${i + 1}/${batchCount}`; + progressCallback?.((i + 1) / batchCount); } } diff --git a/cli/commands/index/rebuild.ts b/cli/commands/index/rebuild.ts new file mode 100644 index 00000000..00feed3d --- /dev/null +++ b/cli/commands/index/rebuild.ts @@ -0,0 +1,68 @@ +import { Args, Flags } from "@oclif/core"; +import ora from "ora"; +import { SonicIndexType, searchManager } from "~/classes/search/search-manager"; +import { BaseCommand } from "~/cli/base"; +import { config } from "~/packages/config-manager"; + +export default class IndexRebuild extends BaseCommand { + static override args = { + type: Args.string({ + description: "Index category to rebuild", + options: ["accounts", "statuses"], + required: true, + }), + }; + + static override description = "Rebuild search indexes"; + + static override examples = ["<%= config.bin %> <%= command.id %>"]; + + static override flags = { + "batch-size": Flags.integer({ + char: "b", + description: "Number of items to process in each batch", + default: 100, + }), + }; + + public async run(): Promise { + const { flags, args } = await this.parse(IndexRebuild); + + if (!config.sonic.enabled) { + this.error("Sonic search is disabled"); + this.exit(1); + } + + await searchManager.connect(); + + const spinner = ora("Rebuilding search indexes").start(); + + switch (args.type) { + case "accounts": + await searchManager.rebuildSearchIndexes( + [SonicIndexType.Accounts], + flags["batch-size"], + (progress) => { + spinner.text = `Rebuilding search indexes (${(progress * 100).toFixed(2)}%)`; + }, + ); + break; + case "statuses": + await searchManager.rebuildSearchIndexes( + [SonicIndexType.Statuses], + flags["batch-size"], + (progress) => { + spinner.text = `Rebuilding search indexes (${(progress * 100).toFixed(2)}%)`; + }, + ); + break; + default: { + this.error("Invalid index type"); + } + } + + spinner.succeed("Search indexes rebuilt"); + + this.exit(0); + } +} diff --git a/cli/index.ts b/cli/index.ts index 9abb96fb..6a553a18 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -1,8 +1,10 @@ +import { configureLoggers } from "@/loggers"; import { execute } from "@oclif/core"; import EmojiAdd from "./commands/emoji/add"; import EmojiDelete from "./commands/emoji/delete"; import EmojiImport from "./commands/emoji/import"; import EmojiList from "./commands/emoji/list"; +import IndexRebuild from "./commands/index/rebuild"; import Start from "./commands/start"; import UserCreate from "./commands/user/create"; import UserDelete from "./commands/user/delete"; @@ -10,6 +12,8 @@ import UserList from "./commands/user/list"; import UserRefetch from "./commands/user/refetch"; import UserReset from "./commands/user/reset"; +await configureLoggers(); + // Use "explicit" oclif strategy to avoid issues with oclif's module resolver and bundling export const commands = { "user:list": UserList, @@ -21,6 +25,7 @@ export const commands = { "emoji:delete": EmojiDelete, "emoji:list": EmojiList, "emoji:import": EmojiImport, + "index:rebuild": IndexRebuild, start: Start, };