From 6852772bc2a0453bfdeb7f362dfe259e3ac73772 Mon Sep 17 00:00:00 2001 From: CatNoir2006 <93994507+CatNoir2006@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:41:39 +0200 Subject: [PATCH 1/2] feat(server): add optional timestamp_from/timestamp_to range filter Adds support for an explicit epoch_second-based timestamp range in both the JSON API (POST/GET /api/query) and the RSS feed. - SearchEngine: new range filter on 'timestamp' using gte/lte when timestamp_from or timestamp_to is provided in the query. - If an explicit range is set, the 'future: false' filter is skipped so the two filters do not collide on the same field. - RSSFeedGenerator: parses timestamp_from / timestamp_to from the URL query string and forwards them to the SearchEngine. --- server/RSSFeedGenerator.ts | 2 ++ server/SearchEngine.ts | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/server/RSSFeedGenerator.ts b/server/RSSFeedGenerator.ts index 10e866cf..fd182f81 100644 --- a/server/RSSFeedGenerator.ts +++ b/server/RSSFeedGenerator.ts @@ -71,6 +71,8 @@ export class RSSFeedGenerator { sortBy: urlQuery.sortBy ?? 'timestamp', sortOrder: urlQuery.sortOrder ?? 'desc', future, + timestamp_from: (typeof urlQuery.timestamp_from == 'string') ? parseInt(urlQuery.timestamp_from) : undefined, + timestamp_to: (typeof urlQuery.timestamp_to == 'string') ? parseInt(urlQuery.timestamp_to) : undefined, duration_min: parsedQuery.duration_min, duration_max: parsedQuery.duration_max, offset: (typeof urlQuery.offset == 'string') ? parseInt(urlQuery.offset) : 0, diff --git a/server/SearchEngine.ts b/server/SearchEngine.ts index 9799326c..eba41367 100644 --- a/server/SearchEngine.ts +++ b/server/SearchEngine.ts @@ -148,7 +148,23 @@ export class SearchEngine { opensearchQuery.body.query.bool.filter.push(durationFilter); } - if (query.future === false) { + const hasExplicitTimestampRange = query.timestamp_from != undefined || query.timestamp_to != undefined; + if (hasExplicitTimestampRange) { + const timestampFilter: { range: { timestamp: { gte?: number, lte?: number } } } = { + range: { timestamp: {} } + }; + + if (query.timestamp_from != undefined) { + timestampFilter.range.timestamp.gte = query.timestamp_from; + } + + if (query.timestamp_to != undefined) { + timestampFilter.range.timestamp.lte = query.timestamp_to; + } + opensearchQuery.body.query.bool.filter.push(timestampFilter); + } + + if (query.future === false && !hasExplicitTimestampRange) { opensearchQuery.body.query.bool.filter.push({ range: { timestamp: { to: 'now+1h/h' } From d26a1108f2f853bfc32ce0773f4818414dfa7e77 Mon Sep 17 00:00:00 2001 From: CatNoir2006 <93994507+CatNoir2006@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:40:16 +0200 Subject: [PATCH 2/2] feat(client): add timestamp_from/timestamp_to range filter UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two date inputs ('Von' / 'Bis') to the search bar that wire through to the new server-side timestamp range filter from the previous commit. - store.svelte.ts: new timestampFrom/timestampTo state, included in the request body, URL hash and trackSearch payload; getter/setter reset the page on change like the existing future toggle. - SearchBar.svelte: two date inputs between the 'Zukünftige' toggle and the sort dropdown; epoch_second conversion done inline on onchange. Styled consistently with the search input (h-10, dark mode aware, color-scheme for native picker). --- client/src/lib/components/SearchBar.svelte | 28 ++++++++++++++++++++++ client/src/lib/store.svelte.ts | 19 ++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/client/src/lib/components/SearchBar.svelte b/client/src/lib/components/SearchBar.svelte index d7837994..cd7d5253 100644 --- a/client/src/lib/components/SearchBar.svelte +++ b/client/src/lib/components/SearchBar.svelte @@ -8,6 +8,19 @@ const sortValue = $derived(`${appState.sortBy}_${appState.sortOrder}`); + const dateFrom = $derived(appState.timestampFrom != undefined ? new Date(appState.timestampFrom * 1000).toISOString().slice(0, 10) : ''); + const dateTo = $derived(appState.timestampTo != undefined ? new Date(appState.timestampTo * 1000).toISOString().slice(0, 10) : ''); + + function handleDateFromChange(event: Event) { + const value = (event.target as HTMLInputElement).value; + appState.timestampFrom = value ? Math.floor(new Date(value).getTime() / 1000) : undefined; + } + + function handleDateToChange(event: Event) { + const value = (event.target as HTMLInputElement).value; + appState.timestampTo = value ? Math.floor(new Date(value).getTime() / 1000) : undefined; + } + function handleSortChange(event: Event) { const target = event.target as HTMLSelectElement; if (target) { @@ -52,6 +65,12 @@ +
+ + + + +
{#if appState.viewMode === 'grid'} @@ -97,4 +116,13 @@ .search-input-right { @apply absolute right-0 inset-y-0 flex items-center px-4 leading-none text-gray-600 hover:text-gray-800 dark:text-gray-300 dark:hover:text-white cursor-pointer transition-opacity; } + + .date-input { + @apply h-10 rounded-md border border-gray-300 bg-gray-50 px-3 text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:ring-blue-500; + color-scheme: light dark; + } + + .date-label { + @apply text-sm text-gray-700 dark:text-gray-300; + } diff --git a/client/src/lib/store.svelte.ts b/client/src/lib/store.svelte.ts index bfc1d181..f4867225 100644 --- a/client/src/lib/store.svelte.ts +++ b/client/src/lib/store.svelte.ts @@ -7,6 +7,8 @@ function createAppState() { let query = $state(''); let everywhere = $state(false); let future = $state(true); + let timestampFrom = $state(undefined); + let timestampTo = $state(undefined); let sortBy = $state('timestamp'); let sortOrder = $state('desc'); let currentPage = $state(0); @@ -55,6 +57,8 @@ function createAppState() { sortBy, sortOrder, future, + timestamp_from: timestampFrom, + timestamp_to: timestampTo, duration_min: parsedQuery.duration_min, duration_max: parsedQuery.duration_max, offset: currentPage * itemsPerPage, @@ -107,6 +111,11 @@ function createAppState() { everywhere = params['everywhere'] === 'true'; future = params['future'] !== 'false'; + const from = parseInt(params['timestamp_from'], 10); + timestampFrom = !isNaN(from) ? from : undefined; + const to = parseInt(params['timestamp_to'], 10); + timestampTo = !isNaN(to) ? to : undefined; + const sBy = params['sortBy']; if (sBy && isSortBy(sBy)) sortBy = sBy; else sortBy = 'timestamp'; @@ -135,6 +144,8 @@ function createAppState() { query, everywhere, future, + timestamp_from: timestampFrom, + timestamp_to: timestampTo, sortBy, sortOrder, page: currentPage + 1 @@ -171,12 +182,14 @@ function createAppState() { const cleanup = $effect.root(() => { $effect(() => { _search(); - trackSearch({ query, everywhere, future, sortBy, sortOrder, itemsPerPage, page: currentPage + 1 }); + trackSearch({ query, everywhere, future, timestampFrom, timestampTo, sortBy, sortOrder, itemsPerPage, page: currentPage + 1 }); updateUrlHash({ query: query || undefined, everywhere: everywhere || undefined, future: future ? undefined : 'false', + timestamp_from: timestampFrom, + timestamp_to: timestampTo, sortBy: sortBy === 'timestamp' ? undefined : sortBy, sortOrder: sortOrder === 'desc' ? undefined : sortOrder, page: currentPage > 0 ? currentPage + 1 : undefined @@ -219,6 +232,10 @@ function createAppState() { set everywhere(value) { everywhere = value; currentPage = 0; }, get future() { return future; }, set future(value) { future = value; currentPage = 0; }, + get timestampFrom() { return timestampFrom; }, + set timestampFrom(value) { timestampFrom = value; currentPage = 0; }, + get timestampTo() { return timestampTo; }, + set timestampTo(value) { timestampTo = value; currentPage = 0; }, get sortBy() { return sortBy; }, get sortOrder() { return sortOrder; }, get currentPage() { return currentPage; },