From c2eae076f4bb228ffa6b756666324d42751bf054 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Tue, 22 Jul 2025 23:51:37 +0200 Subject: [PATCH] implemented task/:id/view --- .../umbrella/core/api/ProjectService.java | 8 +- .../umbrella/core/api/TaskService.java | 10 +- .../srsoftware/umbrella/core/model/Task.java | 9 +- frontend/src/App.svelte | 2 + frontend/src/Components/ListTask.svelte | 5 +- frontend/src/routes/task/View.svelte | 121 ++++++++++++++++++ .../srsoftware/umbrella/project/SqliteDb.java | 2 +- .../de/srsoftware/umbrella/task/SqliteDb.java | 34 ++++- .../de/srsoftware/umbrella/task/TaskDb.java | 8 ++ .../srsoftware/umbrella/task/TaskModule.java | 27 +++- translations/src/main/resources/de.json | 1 + 11 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 frontend/src/routes/task/View.svelte diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/ProjectService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/ProjectService.java index 846971c..f98e20d 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/ProjectService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/ProjectService.java @@ -9,10 +9,10 @@ import java.util.Map; public interface ProjectService { CompanyService companyService(); - public Map listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException; - public Map listUserProjects(long userId, boolean includeClosed) throws UmbrellaException; - public Collection loadMembers(Collection projects); - public default Project loadMembers(Project project){ + Map listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException; + Map listUserProjects(long userId, boolean includeClosed) throws UmbrellaException; + Collection loadMembers(Collection projects); + default Project loadMembers(Project project){ loadMembers(List.of(project)); return project; } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java index 09370bd..096e324 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java @@ -2,14 +2,22 @@ package de.srsoftware.umbrella.core.api; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Project; import de.srsoftware.umbrella.core.model.Task; + +import java.util.Collection; import java.util.HashMap; +import java.util.List; public interface TaskService { CompanyService companyService(); HashMap listCompanyTasks(long companyId) throws UmbrellaException; HashMap listProjectTasks(long projectId) throws UmbrellaException; - + Collection loadMembers(Collection tasks); + default Task loadMembers(Task task){ + loadMembers(List.of(task)); + return task; + } ProjectService projectService(); UserService userService(); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Task.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Task.java index c9b020a..22183ac 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Task.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Task.java @@ -12,7 +12,7 @@ import java.time.LocalDate; import java.util.HashMap; import java.util.Map; -public record Task(long id, long projectId, Long parentTaskId, String name, String description, Status status, Double estimatedTime, LocalDate start, LocalDate dueDate, boolean showClosed, boolean noIndex) implements Mappable { +public record Task(long id, long projectId, Long parentTaskId, String name, String description, Status status, Double estimatedTime, LocalDate start, LocalDate dueDate, boolean showClosed, boolean noIndex, Map members) implements Mappable { public static Task of(ResultSet rs) throws SQLException { var estTime = rs.getDouble(EST_TIME); var parentTaskId = rs.getLong(PARENT_TASK_ID); @@ -29,7 +29,8 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri startDate != null ? LocalDate.parse(startDate) : null, dueDate != null ? LocalDate.parse(dueDate) : null, rs.getBoolean(SHOW_CLOSED), - rs.getBoolean(NO_INDEX) + rs.getBoolean(NO_INDEX), + new HashMap<>() ); } @@ -49,4 +50,8 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri map.put(NO_INDEX,noIndex); return map; } + + public boolean hasMember(UmbrellaUser user) { + return members.containsKey(user.id()); + } } diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index ed80cb0..6619c5b 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -20,6 +20,7 @@ import User from "./routes/user/User.svelte"; import ViewDoc from "./routes/document/View.svelte"; import ViewPrj from "./routes/project/View.svelte"; + import ViewTask from "./routes/task/View.svelte"; let translations_ready = $state(false); onMount(async () => { @@ -51,6 +52,7 @@ + diff --git a/frontend/src/Components/ListTask.svelte b/frontend/src/Components/ListTask.svelte index 58c4f56..59a0ba8 100644 --- a/frontend/src/Components/ListTask.svelte +++ b/frontend/src/Components/ListTask.svelte @@ -32,8 +32,9 @@ function openTask(evt){ evt.preventDefault(); - //router.navigate(`/task/${task.id}/view`); - location.href = `https://umbrella.srsoftware.de/task/${task.id}/view`; + console.log('openTask(…)',evt,task); + router.navigate(`/task/${task.id}/view`); + //location.href = `https://umbrella.srsoftware.de/task/${task.id}/view`; } if (task.estimated_time){ diff --git a/frontend/src/routes/task/View.svelte b/frontend/src/routes/task/View.svelte new file mode 100644 index 0000000..20d7363 --- /dev/null +++ b/frontend/src/routes/task/View.svelte @@ -0,0 +1,121 @@ + + +{#if error} +{error} +{/if} +{#if task} + + + {#if project} + + + + + {/if} + + + + + {#if task.description.rendered} + + + + + {/if} + + {#if task.start_date} + + + + + {/if} + {#if task.due_date} + + + + + {/if} + {#if children} + + + + + {/if} + +
{t('project')}{project.name}
{t('task')}{task.name}
{t('description')}{@html task.description.rendered}
{t('start_date')}{task.start_date}
{t('due_date')}{task.due_date}
{t('subtasks')} + +
+{/if} \ No newline at end of file diff --git a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java index 12b84ad..a978f2b 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -155,7 +155,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) if (result == null) throw UmbrellaException.notFound("No project found for id {0}",projectId); return result; } catch (SQLException e) { - throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load items from database"); + throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load project from database"); } } diff --git a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java index 1ab486c..7fa69f9 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java @@ -7,17 +7,18 @@ import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; +import static de.srsoftware.umbrella.project.Constants.*; import static de.srsoftware.umbrella.task.Constants.*; import static java.lang.System.Logger.Level.WARNING; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import de.srsoftware.umbrella.core.model.Status; -import de.srsoftware.umbrella.core.model.Task; -import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.core.model.*; + import java.sql.Connection; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; +import java.util.Map; public class SqliteDb implements TaskDb { @@ -28,6 +29,19 @@ public class SqliteDb implements TaskDb { db = connection; } + @Override + public Map getMembers(Task task) { + try { + var result = new HashMap(); + var rs = select(ALL).from(TABLE_TASKS_USERS).where(TASK_ID,equal(task.id())).exec(db); + while (rs.next()) result.put(rs.getLong(USER_ID),Permission.of(rs.getInt(PERMISSIONS))); + rs.close(); + return result; + } catch (SQLException e){ + throw new UmbrellaException(HTTP_SERVER_ERROR,"Faailed to load task members"); + } + } + public HashMap listTasks(Collection projectIds) throws UmbrellaException { try { var tasks = new HashMap(); @@ -83,4 +97,18 @@ public class SqliteDb implements TaskDb { throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project id"); } } + + @Override + public Task load(long taskId) throws UmbrellaException { + try { + var rs = select(ALL).from(TABLE_TASKS).where(ID, equal(taskId)).exec(db); + Task result = null; + if (rs.next()) result = Task.of(rs); + rs.close(); + if (result == null) throw UmbrellaException.notFound("No task found for id {0}",taskId); + return result; + } catch (SQLException e) { + throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load task from database"); + } + } } diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java index 837b5ee..21cc9fc 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java @@ -2,7 +2,15 @@ package de.srsoftware.umbrella.task; +import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Permission; +import de.srsoftware.umbrella.core.model.Task; + +import java.util.Map; public interface TaskDb { + Task load(long taskId) throws UmbrellaException; + + Map getMembers(Task task); } diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 62cc582..b5541be 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -62,7 +62,12 @@ public class TaskModule extends BaseHandler implements TaskService { return switch (head) { case PERMISSIONS -> getPermissionList(ex); case STATES -> getStateList(ex); - default -> super.doGet(path,ex); + case null -> super.doGet(path,ex); + default -> { + var taskId = Long.parseLong(head); + head = path.pop(); + yield head == null ? getTask(ex,taskId,user.get()) : super.doGet(path,ex); + } }; } catch (UmbrellaException e){ return send(ex,e); @@ -121,6 +126,12 @@ public class TaskModule extends BaseHandler implements TaskService { return sendContent(ex,map); } + private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException { + var task = loadMembers(taskDb.load(taskId)); + if (!task.hasMember(user)) throw forbidden("You are not a member of {0}",task.name()); + return sendContent(ex,task); + } + @Override public HashMap listCompanyTasks(long companyId) throws UmbrellaException { var projectList = projects.listCompanyProjects(companyId,false); @@ -132,6 +143,20 @@ public class TaskModule extends BaseHandler implements TaskService { return taskDb.listTasks(List.of(projectId)); } + @Override + public Collection loadMembers(Collection taskList) { + var userMap = new HashMap(); + for (var task : taskList){ + for (var entry : taskDb.getMembers(task).entrySet()){ + var userId = entry.getKey(); + var permission = entry.getValue(); + var user = userMap.computeIfAbsent(userId,k -> users.loadUser(userId)); + task.members().put(userId,new Member(user,permission)); + } + } + return taskList; + } + private Map placeInTree(Task task, HashMap> taskTree, Map taskMap) { var mappedTask = task.toMap(); if (task.parentTaskId() != null){ diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 26e1fc7..778ae02 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -191,6 +191,7 @@ }, "stock": "Inventar", "subject": "Betreff", + "subtasks": "Unteraufgaben", "task": "Aufgabe", "tasks": "Aufgaben",