diff --git a/src/service.test.ts b/src/service.test.ts index 818a2457b..47c72a7ed 100644 --- a/src/service.test.ts +++ b/src/service.test.ts @@ -303,6 +303,42 @@ await test('find', async (t) => { name: OBJECT, res: obj, }, + // Search functionality tests + { + name: POSTS, + params: { q: 'a' }, + res: [post1, post2, post3], + }, + { + name: POSTS, + params: { q: 'baz' }, + res: [post3], + }, + { + name: POSTS, + params: { q: 'c' }, + res: [post3], + }, + { + name: POSTS, + params: { q: 'foo' }, + res: [post1, post3], + }, + { + name: POSTS, + params: { q: 'bar' }, + res: [post1, post2], + }, + { + name: POSTS, + params: { q: 'nonexistent' }, + res: [], + }, + { + name: COMMENTS, + params: { q: 'a' }, + res: [comment1], + }, ] for (const tc of arr) { await t.test(`${tc.name} ${JSON.stringify(tc.params)}`, () => { diff --git a/src/service.ts b/src/service.ts index bb7a63f3b..232df471f 100644 --- a/src/service.ts +++ b/src/service.ts @@ -198,6 +198,14 @@ export class Service { return items } + // Handle full-text search with q parameter + if (query['q'] && typeof query['q'] === 'string') { + const searchTerm = query['q'].toLowerCase() + items = items.filter((item: Item) => { + return this.#searchInItem(item, searchTerm) + }) + } + // Convert query params to conditions const conds: [string, Condition, string | string[]][] = [] for (const [key, value] of Object.entries(query)) { @@ -221,6 +229,7 @@ export class Service { '_limit', '_page', '_per_page', + 'q', ].includes(key) ) { continue @@ -458,4 +467,44 @@ export class Service { await this.#db.write() return item } + + #searchInItem(item: Item, searchTerm: string): boolean { + for (const [, value] of Object.entries(item)) { + if (typeof value === 'string' && value.toLowerCase().includes(searchTerm)) { + return true + } + if (typeof value === 'object' && value !== null) { + if (this.#searchInObject(value as Record, searchTerm)) { + return true + } + } + } + return false + } + + #searchInObject(obj: Record, searchTerm: string): boolean { + for (const [, value] of Object.entries(obj)) { + if (typeof value === 'string' && value.toLowerCase().includes(searchTerm)) { + return true + } + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + if (this.#searchInObject(value as Record, searchTerm)) { + return true + } + } + if (Array.isArray(value)) { + for (const arrayItem of value) { + if (typeof arrayItem === 'string' && arrayItem.toLowerCase().includes(searchTerm)) { + return true + } + if (typeof arrayItem === 'object' && arrayItem !== null) { + if (this.#searchInObject(arrayItem as Record, searchTerm)) { + return true + } + } + } + } + } + return false + } }