Suche: Notizen, Kalendereinträge, Board, Changelog jetzt vollständig im Frontend und Backend

This commit is contained in:
2026-06-05 13:17:24 +02:00
parent f5988d3af6
commit afc56d9fca
3 changed files with 190 additions and 18 deletions

View File

@@ -504,4 +504,21 @@ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='lin
`);
}
// ── Kalender-Event-Cache für Suche ────────────────────────────────────────────
if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='calendar_event_cache'").get()) {
db.exec(`
CREATE TABLE calendar_event_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
feed_id INTEGER NOT NULL REFERENCES calendar_feeds(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
summary TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
location TEXT NOT NULL DEFAULT '',
start_dt TEXT,
end_dt TEXT,
synced_at DATETIME DEFAULT NULL
)
`);
}
module.exports = db;

View File

@@ -1,18 +1,97 @@
const express = require('express');
const https = require('https');
const http = require('http');
const db = require('../db');
const { authenticate } = require('../middleware/auth');
const router = express.Router();
router.get('/', authenticate, (req, res) => {
const safeAll = (fn) => { try { return fn(); } catch { return []; } };
// iCal-Feeds parsen und in Cache schreiben (max alle 10 Min pro User)
function syncCalendarEvents(uid) {
try {
const feeds = db.prepare('SELECT * FROM calendar_feeds WHERE user_id=?').all(uid);
if (!feeds.length) return;
// Prüfen ob sync nötig
const lastSync = db.prepare(
"SELECT MAX(synced_at) as s FROM calendar_event_cache WHERE user_id=?"
).get(uid)?.s;
if (lastSync) {
const age = Date.now() - new Date(lastSync).getTime();
if (age < 10 * 60 * 1000) return; // < 10 Min: kein Sync nötig
}
feeds.forEach(feed => {
try {
fetchIcal(feed.url).then(raw => {
if (!raw) return;
const events = parseIcal(raw);
db.prepare('DELETE FROM calendar_event_cache WHERE feed_id=?').run(feed.id);
const ins = db.prepare(`
INSERT INTO calendar_event_cache (feed_id, user_id, summary, description, location, start_dt, end_dt, synced_at)
VALUES (?,?,?,?,?,?,?,datetime('now','localtime'))
`);
db.transaction(() => {
events.slice(0, 200).forEach(ev => {
ins.run(feed.id, uid, ev.summary||'', ev.description||'', ev.location||'', ev.start||'', ev.end||'');
});
})();
}).catch(() => {});
} catch {}
});
} catch {}
}
function fetchIcal(url) {
return new Promise((resolve) => {
try {
const lib = url.startsWith('https') ? https : http;
const req = lib.get(url, { headers:{'User-Agent':'DickenDock/1.0'} }, res => {
let data = '';
res.on('data', d => { data += d; if (data.length > 500000) { res.destroy(); resolve(null); }});
res.on('end', () => resolve(data));
});
req.on('error', () => resolve(null));
req.setTimeout(8000, () => { req.destroy(); resolve(null); });
} catch { resolve(null); }
});
}
function parseIcal(raw) {
const events = [];
const blocks = raw.split('BEGIN:VEVENT');
for (let i = 1; i < blocks.length; i++) {
const block = blocks[i];
const get = (key) => {
const m = block.match(new RegExp(`${key}[^:]*:([^\r\n]+)`));
return m ? m[1].replace(/\\n/g,' ').replace(/\\,/g,',').trim() : '';
};
events.push({
summary: get('SUMMARY'),
description: get('DESCRIPTION'),
location: get('LOCATION'),
start: get('DTSTART'),
end: get('DTEND'),
});
}
return events;
}
router.get('/', authenticate, async (req, res) => {
const q = (req.query.q || '').trim();
const uid = req.user.id;
if (q.length < 2) return res.json({
links:[], folders:[], snippets:[], calculations:[], orders:[],
todos:[], files:[], file_folders:[], push_schedules:[], calendar_feeds:[], qr_codes:[]
todos:[], notes:[], board_items:[], changelog:[],
files:[], file_folders:[], push_schedules:[],
calendar_feeds:[], calendar_events:[], qr_codes:[]
});
const uid = req.user.id;
const like = `%${q.toLowerCase()}%`;
const safeAll = (fn) => { try { return fn(); } catch { return []; } };
// Kalender-Events im Hintergrund synchronisieren
syncCalendarEvents(uid);
const links = safeAll(() => db.prepare(`
SELECT id, title, url, icon, description FROM link_list
@@ -47,9 +126,34 @@ router.get('/', authenticate, (req, res) => {
const todos = safeAll(() => db.prepare(`
SELECT id, text, done FROM todos
WHERE user_id=? AND LOWER(text) LIKE ?
ORDER BY done, created_at DESC LIMIT 5
ORDER BY done, created_at DESC LIMIT 6
`).all(uid, like));
// Notiz: Vorschau des passenden Textbereichs
const noteRow = safeAll(() => db.prepare(
'SELECT content FROM notes WHERE user_id=? AND LOWER(content) LIKE ?'
).get(uid, like));
const notes = noteRow ? [{
id: 'note',
preview: (() => {
const idx = noteRow.content.toLowerCase().indexOf(q.toLowerCase());
const start = Math.max(0, idx - 30);
return (start > 0 ? '…' : '') + noteRow.content.slice(start, idx + 60) + '…';
})()
}] : [];
const board_items = safeAll(() => db.prepare(`
SELECT id, title, description, type FROM board_items
WHERE user_id=? AND (LOWER(title) LIKE ? OR LOWER(description) LIKE ?)
ORDER BY created_at DESC LIMIT 5
`).all(uid, like, like));
const changelog = safeAll(() => db.prepare(`
SELECT id, version, title, body FROM changelog
WHERE LOWER(title) LIKE ? OR LOWER(body) LIKE ?
ORDER BY created_at DESC LIMIT 4
`).all(like, like));
const files = safeAll(() => db.prepare(`
SELECT id, originalname, mimetype, size FROM files
WHERE user_id=? AND (LOWER(originalname) LIKE ? OR LOWER(filename) LIKE ?)
@@ -71,16 +175,26 @@ router.get('/', authenticate, (req, res) => {
const calendar_feeds = safeAll(() => db.prepare(`
SELECT id, name, url FROM calendar_feeds
WHERE user_id=? AND (LOWER(name) LIKE ? OR LOWER(url) LIKE ?)
ORDER BY name LIMIT 5
ORDER BY name LIMIT 4
`).all(uid, like, like));
const calendar_events = safeAll(() => db.prepare(`
SELECT c.id, c.summary, c.description, c.location, c.start_dt, f.name as feed_name, f.color
FROM calendar_event_cache c
JOIN calendar_feeds f ON f.id=c.feed_id
WHERE c.user_id=? AND (LOWER(c.summary) LIKE ? OR LOWER(c.description) LIKE ? OR LOWER(c.location) LIKE ?)
ORDER BY c.start_dt LIMIT 6
`).all(uid, like, like, like));
const qr_codes = safeAll(() => db.prepare(`
SELECT id, label, url FROM qr_codes
WHERE user_id=? AND (LOWER(label) LIKE ? OR LOWER(url) LIKE ?)
ORDER BY created_at DESC LIMIT 5
`).all(uid, like, like));
res.json({ links, folders, snippets, calculations, orders, todos, files, file_folders, push_schedules, calendar_feeds, qr_codes });
res.json({ links, folders, snippets, calculations, orders, todos, notes,
board_items, changelog, files, file_folders, push_schedules,
calendar_feeds, calendar_events, qr_codes });
});
module.exports = router;