import express, { type Request, type Response } from 'express' import fs from 'fs/promises' import path from 'path' import { asyncHandler } from '../../utils/asyncHandler.js' import { successResponse } from '../../utils/response.js' import { resolveNotebookPath } from '../../utils/pathSafety.js' import type { FileItemDTO } from '../../../shared/types.js' import { toPosixPath } from '../../../shared/utils/path.js' const router = express.Router() router.post( '/', asyncHandler(async (req: Request, res: Response) => { const { keywords } = req.body as { keywords?: string[] } if (!keywords || !Array.isArray(keywords) || keywords.length === 0) { successResponse(res, { items: [] }) return } const searchTerms = keywords.map(k => k.trim().toLowerCase()).filter(k => k.length > 0) if (searchTerms.length === 0) { successResponse(res, { items: [] }) return } const { fullPath: rootPath } = resolveNotebookPath('') const results: FileItemDTO[] = [] const maxResults = 100 const searchDir = async (dir: string, relativeDir: string) => { if (results.length >= maxResults) return const entries = await fs.readdir(dir, { withFileTypes: true }) for (const entry of entries) { if (results.length >= maxResults) break const entryPath = path.join(dir, entry.name) const entryRelativePath = path.join(relativeDir, entry.name) if (entry.name.startsWith('.') || entry.name === 'RB' || entry.name === 'node_modules') continue if (entry.isDirectory()) { await searchDir(entryPath, entryRelativePath) } else if (entry.isFile()) { const fileNameLower = entry.name.toLowerCase() let contentLower = '' let contentLoaded = false const checkKeyword = async (term: string) => { if (fileNameLower.includes(term)) return true if (entry.name.toLowerCase().endsWith('.md')) { if (!contentLoaded) { try { const content = await fs.readFile(entryPath, 'utf-8') contentLower = content.toLowerCase() contentLoaded = true } catch { return false } } return contentLower.includes(term) } return false } let allMatched = true for (const term of searchTerms) { const matched = await checkKeyword(term) if (!matched) { allMatched = false break } } if (allMatched) { results.push({ name: entry.name, path: toPosixPath(entryRelativePath), type: 'file', size: 0, modified: new Date().toISOString(), }) } } } } await searchDir(rootPath, '') successResponse(res, { items: results, limited: results.length >= maxResults }) }), ) export default router