// Search over what lives in NE Hub itself (tasks and calendar events). Files and music are searched from the browser, // because those need the person's own Seafile / Plex sign-in. const cal = require('./cal'); const tasks = require('./tasks'); const users = require('./users'); const notes = require('./notes'), chat = require('./chat'), contacts = require('./contacts'), recipes = require('./recipes'), choresMod = require('./chores'); const json = (res, code, obj) => { res.writeHead(code, { 'content-type': 'application/json' }); res.end(JSON.stringify(obj)); }; const ymd = d => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; async function handle(req, res) { const q = (new URL(req.url, 'http://x').searchParams.get('q') || '').trim().toLowerCase().slice(0, 100); if (q.length < 2) return json(res, 200, { tasks: [], events: [], notes: [], chat: [], contacts: [], chores: [], recipes: [], errors: [] }); const errors = []; let foundTasks = [], foundEvents = []; const me = users.current(); if (users.can(me, 'tasks')) try { foundTasks = (await tasks.allTasks()).filter(t => (t.title + ' ' + t.notes).toLowerCase().includes(q)) .sort((a, b) => (a.done - b.done) || (a.due || '9999').localeCompare(b.due || '9999')).slice(0, 8); } catch (e) { errors.push('Tasks: ' + e.message); } if (users.can(me, 'calendar')) try { // two months back to just over a year ahead const now = new Date(), from = new Date(now); from.setDate(from.getDate() - 60); from.setHours(0, 0, 0, 0); const to = new Date(now); to.setDate(to.getDate() + 400); to.setHours(23, 59, 59, 999); const r = await cal.collectEvents({ from: from.getTime(), to: to.getTime(), fd: ymd(from), td: ymd(to) }); errors.push(...r.errors); const when = e => e.allDay ? Date.parse(e.start + 'T00:00:00') : e.start, t0 = Date.now() - 864e5; foundEvents = r.events.filter(e => (e.title + ' ' + (e.location || '')).toLowerCase().includes(q)) .sort((a, b) => { const ua = when(a) >= t0, ub = when(b) >= t0; return ua === ub ? (ua ? when(a) - when(b) : when(b) - when(a)) : ua ? -1 : 1; }) // upcoming first (soonest), then recent past .slice(0, 8); } catch (e) { errors.push('Calendar: ' + e.message); } // the family parts of the hub const fam = { notes: [], chat: [], contacts: [], chores: [], recipes: [] }; if (users.can(me, 'notes')) try { fam.notes = notes.search(me, q); } catch (e) { errors.push('Notes: ' + e.message); } if (users.can(me, 'chat')) try { fam.chat = chat.searchFor(me, q); } catch (e) { errors.push('Chat: ' + e.message); } if (users.can(me, 'contacts')) try { fam.contacts = contacts.load().filter(c => [c.name, c.org, ...c.emails.map(e => e.value), ...c.phones.map(p => p.value)].some(x => String(x || '').toLowerCase().includes(q))).slice(0, 8).map(c => ({ id: c.id, name: c.name, org: c.org, email: (c.emails[0] || {}).value || '', phone: (c.phones[0] || {}).value || '' })); } catch (e) { errors.push('Contacts: ' + e.message); } if (users.can(me, 'chores')) try { fam.chores = choresMod.find(q).slice(0, 8); } catch (e) { errors.push('Chores: ' + e.message); } if (users.can(me, 'recipes')) fam.recipes = await recipes.search(q); json(res, 200, { tasks: foundTasks, events: foundEvents, ...fam, errors }); } module.exports = { handle };