email: full-text search (FTS5) — backend index + /email/search + search box in the list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 17:38:03 +00:00
co-authored by Claude Opus 4.8
parent 4b08d0b99c
commit 3f00999a2a
3 changed files with 124 additions and 11 deletions
+19 -1
View File
@@ -3,7 +3,7 @@ import { join } from 'node:path';
import type { EmailMessage } from 'types';
import { createRouter } from '../../create-router';
import { DATA_PATH } from '@@/data-path';
import { openEmailDb, rowToSummary, getSyncMeta } from './email-db';
import { openEmailDb, rowToSummary, getSyncMeta, searchEmails } from './email-db';
import { accountsRouter } from './accounts';
export const emailRouter = createRouter();
@@ -64,6 +64,24 @@ emailRouter.get('/events', (ctx) => {
});
});
// Full-text search across all mail (subject / sender / recipients / snippet / body), newest first.
emailRouter.get('/search', async (ctx) => {
const email = ctx.get('user').email;
const q = (ctx.req.query('q') ?? '').trim();
const page = Math.max(1, Number(ctx.req.query('page') ?? '1') || 1);
const limit = Math.min(100, Math.max(1, Number(ctx.req.query('limit') ?? '50') || 50));
const offset = (page - 1) * limit;
if (!q) return ctx.json({ messages: [], total: 0 });
const db = openEmailDb(email);
try {
const { rows, total } = searchEmails(db, q, limit, offset);
return ctx.json({ messages: rows.map(rowToSummary), total });
} finally {
db.close();
}
});
emailRouter.get('/messages', async (ctx) => {
const email = ctx.get('user').email;
const page = Number(ctx.req.query('page') ?? '1');