feat(worker): add index queue and wire Meilisearch indexing after approval
Adds index.queue.ts and index.processor.ts to handle BullMQ indexing jobs, updates main.ts to create a Meilisearch client, configure the index on startup, and enqueue index + forward jobs from ApprovalResult after a star reaction.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { processIndexJob } from './index.processor';
|
||||
import { indexMessage } from '@tower/search';
|
||||
import { IndexJobData } from '@tower/types';
|
||||
|
||||
jest.mock('@tower/search', () => ({
|
||||
indexMessage: jest.fn().mockResolvedValue(undefined),
|
||||
MESSAGES_INDEX: 'tower-messages',
|
||||
}));
|
||||
|
||||
function makeJob(overrides: Partial<IndexJobData> = {}): IndexJobData {
|
||||
return {
|
||||
messageId: 'msg-1',
|
||||
content: 'hello world',
|
||||
senderName: 'Alice',
|
||||
sourceGroupId: 'grp-1',
|
||||
sourceGroupName: 'UP Parivar',
|
||||
tags: ['#important'],
|
||||
platform: 'whatsapp',
|
||||
approvedAt: '2026-05-27T10:00:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('processIndexJob', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('calls indexMessage with MeiliDocument shape', async () => {
|
||||
const mockClient = {} as any;
|
||||
await processIndexJob(makeJob(), mockClient);
|
||||
expect(indexMessage).toHaveBeenCalledWith(mockClient, {
|
||||
id: 'msg-1',
|
||||
content: 'hello world',
|
||||
senderName: 'Alice',
|
||||
sourceGroupId: 'grp-1',
|
||||
sourceGroupName: 'UP Parivar',
|
||||
tags: ['#important'],
|
||||
platform: 'whatsapp',
|
||||
approvedAt: new Date('2026-05-27T10:00:00.000Z').getTime(),
|
||||
});
|
||||
});
|
||||
|
||||
it('converts null senderName to empty string', async () => {
|
||||
const mockClient = {} as any;
|
||||
await processIndexJob(makeJob({ senderName: null }), mockClient);
|
||||
expect(indexMessage).toHaveBeenCalledWith(
|
||||
mockClient,
|
||||
expect.objectContaining({ senderName: '' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('converts approvedAt ISO string to Unix ms number', async () => {
|
||||
const mockClient = {} as any;
|
||||
await processIndexJob(makeJob({ approvedAt: '2026-01-01T00:00:00.000Z' }), mockClient);
|
||||
expect(indexMessage).toHaveBeenCalledWith(
|
||||
mockClient,
|
||||
expect.objectContaining({ approvedAt: new Date('2026-01-01T00:00:00.000Z').getTime() }),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import { IndexJobData } from '@tower/types';
|
||||
import { MeiliSearch, MeiliDocument, indexMessage } from '@tower/search';
|
||||
import { parseRedisUrl } from './redis-connection';
|
||||
|
||||
export async function processIndexJob(job: IndexJobData, meiliClient: MeiliSearch): Promise<void> {
|
||||
const doc: MeiliDocument = {
|
||||
id: job.messageId,
|
||||
content: job.content,
|
||||
senderName: job.senderName ?? '',
|
||||
sourceGroupId: job.sourceGroupId,
|
||||
sourceGroupName: job.sourceGroupName,
|
||||
tags: job.tags,
|
||||
platform: job.platform,
|
||||
approvedAt: new Date(job.approvedAt).getTime(),
|
||||
};
|
||||
await indexMessage(meiliClient, doc);
|
||||
}
|
||||
|
||||
export function createIndexWorker(redisUrl: string, meiliClient: MeiliSearch): Worker<IndexJobData> {
|
||||
return new Worker<IndexJobData>(
|
||||
'tower-index',
|
||||
async (job) => processIndexJob(job.data, meiliClient),
|
||||
{ connection: parseRedisUrl(redisUrl) },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Queue } from 'bullmq';
|
||||
import { IndexJobData } from '@tower/types';
|
||||
import { parseRedisUrl } from './redis-connection';
|
||||
|
||||
export function createIndexQueue(redisUrl: string): Queue<IndexJobData> {
|
||||
return new Queue<IndexJobData>('tower-index', { connection: parseRedisUrl(redisUrl) });
|
||||
}
|
||||
Reference in New Issue
Block a user