From d3c63fadff2cb8e6397bb6401ca16430afcaae6f Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 3 Sep 2025 08:47:18 +0200 Subject: [PATCH] implemented search within time tracks --- frontend/src/routes/search/Search.svelte | 30 ++++++ .../de/srsoftware/umbrella/time/SqliteDb.java | 23 ++++- .../de/srsoftware/umbrella/time/TimeDb.java | 8 +- .../srsoftware/umbrella/time/TimeModule.java | 94 ++++++++++--------- 4 files changed, 108 insertions(+), 47 deletions(-) diff --git a/frontend/src/routes/search/Search.svelte b/frontend/src/routes/search/Search.svelte index 83731ab..d51d4c5 100644 --- a/frontend/src/routes/search/Search.svelte +++ b/frontend/src/routes/search/Search.svelte @@ -3,6 +3,7 @@ import { useTinyRouter } from 'svelte-tiny-router'; import { api } from '../../urls.svelte.js'; import { t } from '../../translations.svelte.js'; + import { display } from '../../time.svelte'; import Bookmark from '../bookmark/Template.svelte'; @@ -16,6 +17,7 @@ let notes = $state(null); let projects = $state(null); let tasks = $state(null); + let times = $state(null); async function setKey(ev){ if (ev) ev.preventDefault(); @@ -44,6 +46,7 @@ fetch(api('notes/search'),options).then(handleNotes); fetch(api('project/search'),options).then(handleProjects); fetch(api('task/search'),options).then(handleTasks); + fetch(api('time/search'),options).then(handleTimes); } function onclick(e){ @@ -100,6 +103,15 @@ } } + async function handleTimes(resp){ + if (resp.ok){ + const res = await resp.json(); + times = Object.keys(res).length ? res : null; + } else { + error = await resp.text(); + } + } + $effect(() => doSearch(key)) @@ -197,3 +209,21 @@ {/if} +{#if times} +
+ + {t('timetracks')} + + +
+{/if} diff --git a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java index c2bda8c..2ddcee2 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java @@ -16,9 +16,7 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Time; import java.sql.Connection; import java.sql.SQLException; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; +import java.util.*; public class SqliteDb extends BaseDb implements TimeDb { @@ -91,6 +89,25 @@ CREATE TABLE IF NOT EXISTS {0} ( } } + @Override + public Map find(long userId, List keys, boolean fulltext) { + try { + var query = select(ALL).from(TABLE_TIMES).where(USER_ID,equal(userId)); + for (var key : keys) query.where(format("CONCAT({0},\" \",{1})",SUBJECT,DESCRIPTION),like("%"+key+"%")); + var rs = query.exec(db); + var times = new HashMap(); + while (rs.next()) { + var time = Time.of(rs); + times.put(time.id(),time); + } + rs.close(); + return times; + } catch (Exception e) { + + throw new UmbrellaException("Failed to search for times"); + } + } + @Override public HashMap listTimes(Collection taskIds, boolean showClosed) throws UmbrellaException { try { diff --git a/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java b/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java index 9207e1f..4d3f64d 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java @@ -5,13 +5,17 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Time; import java.util.Collection; import java.util.HashMap; +import java.util.List; +import java.util.Map; public interface TimeDb { Long delete(long timeId); - HashMap listTimes(Collection taskIds, boolean showClosed) throws UmbrellaException; + Map find(long userId, List keys, boolean fulltext); - HashMap listUserTimes(long userId, boolean showClosed); + Map listTimes(Collection taskIds, boolean showClosed) throws UmbrellaException; + + Map listUserTimes(long userId, boolean showClosed); Time load(long timeId); diff --git a/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java b/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java index 6dd77d6..e852fc3 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java @@ -105,6 +105,7 @@ public class TimeModule extends BaseHandler implements TimeService { return switch (head) { case JOIN -> joinTimes(ex,user.get()); case LIST -> listTimes(ex,user.get()); + case SEARCH -> postSearch(ex, user.get()); case TRACK_TASK -> trackTask(user.get(),path,ex); default -> { try { @@ -180,48 +181,6 @@ public class TimeModule extends BaseHandler implements TimeService { return sendContent(ex,time); } - private boolean patchTime(UmbrellaUser user, long timeId, HttpExchange ex) throws IOException { - var time = timeDb.load(timeId); - if (time.userId() != user.id()) throw forbidden("You are not allowed to alter this time!"); - var json = json(ex); - timeDb.save(time.patch(json)); - return sendContent(ex,time); - } - - private boolean trackTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException { - if (path.empty()) throw missingFieldException(TASK_ID); - Task task; - try { - var taskId = Long.parseLong(path.pop()); - task = taskService().load(List.of(taskId)).get(taskId); - if (task == null) throw UmbrellaException.notFound("Failed to load task with id = {0}",taskId); - } catch (NumberFormatException e) { - throw invalidFieldException(TASK_ID,"long value"); - } - - long now; - try { - now = Long.parseLong(body(ex)); - } catch (NumberFormatException e) { - throw unprocessable("request body does not contain a timestamp!"); - } - - var opt = getStartedTime(user); - if (opt.isPresent()){ - var startedTime = opt.get(); - - if (startedTime.taskIds().contains(task.id())) { - // if the time started last already belongs to the task, there is nothing left to do - return sendContent(ex,startedTime); - } - timeDb.save(startedTime.stop(now)); - } - var track = new Time(0,user.id(),task.name(),task.description(),now,null,Started,List.of(task.id())); - timeDb.save(track); - - return sendContent(ex,track); - } - private boolean getStartedTime(UmbrellaUser user, HttpExchange ex) throws IOException { var startedTime = getStartedTime(user); if (startedTime.isPresent()) return sendContent(ex,startedTime.get()); @@ -272,4 +231,55 @@ public class TimeModule extends BaseHandler implements TimeService { } + private boolean patchTime(UmbrellaUser user, long timeId, HttpExchange ex) throws IOException { + var time = timeDb.load(timeId); + if (time.userId() != user.id()) throw forbidden("You are not allowed to alter this time!"); + var json = json(ex); + timeDb.save(time.patch(json)); + return sendContent(ex,time); + } + + private boolean postSearch(HttpExchange ex, UmbrellaUser user) throws IOException { + var json = json(ex); + if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingFieldException(KEY); + var keys = Arrays.asList(key.split(" ")); + var fulltext = json.has(FULLTEXT) && json.get(FULLTEXT) instanceof Boolean val && val; + var notes = timeDb.find(user.id(),keys,fulltext); + return sendContent(ex,mapValues(notes)); + } + + private boolean trackTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException { + if (path.empty()) throw missingFieldException(TASK_ID); + Task task; + try { + var taskId = Long.parseLong(path.pop()); + task = taskService().load(List.of(taskId)).get(taskId); + if (task == null) throw UmbrellaException.notFound("Failed to load task with id = {0}",taskId); + } catch (NumberFormatException e) { + throw invalidFieldException(TASK_ID,"long value"); + } + + long now; + try { + now = Long.parseLong(body(ex)); + } catch (NumberFormatException e) { + throw unprocessable("request body does not contain a timestamp!"); + } + + var opt = getStartedTime(user); + if (opt.isPresent()){ + var startedTime = opt.get(); + + if (startedTime.taskIds().contains(task.id())) { + // if the time started last already belongs to the task, there is nothing left to do + return sendContent(ex,startedTime); + } + timeDb.save(startedTime.stop(now)); + } + var track = new Time(0,user.id(),task.name(),task.description(),now,null,Started,List.of(task.id())); + timeDb.save(track); + + return sendContent(ex,track); + } + }