Initial commit
This commit is contained in:
97
api/core/search/routes.ts
Normal file
97
api/core/search/routes.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
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
|
||||
Reference in New Issue
Block a user