Files
XCDesktop/api/core/search/routes.ts
2026-03-08 01:34:54 +08:00

98 lines
3.0 KiB
TypeScript

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