diff --git a/build.gradle.kts b/build.gradle.kts index 42c93ad..5cfa7f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,7 +41,7 @@ subprojects { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") implementation("de.srsoftware:configuration.api:1.0.2") - implementation("de.srsoftware:tools.jdbc:1.3.2") + implementation("de.srsoftware:tools.jdbc:1.3.3") implementation("de.srsoftware:tools.http:6.0.4") implementation("de.srsoftware:tools.logging:1.3.2") implementation("de.srsoftware:tools.optionals:1.0.0") diff --git a/company/src/main/java/de/srsoftware/umbrella/company/SqliteDb.java b/company/src/main/java/de/srsoftware/umbrella/company/SqliteDb.java index a497206..217a792 100644 --- a/company/src/main/java/de/srsoftware/umbrella/company/SqliteDb.java +++ b/company/src/main/java/de/srsoftware/umbrella/company/SqliteDb.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.company; import static de.srsoftware.tools.jdbc.Condition.equal; +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.company.Constants.TABLE_COMPANIES; import static de.srsoftware.umbrella.company.Constants.TABLE_COMPANIES_USERS; @@ -29,7 +30,7 @@ public class SqliteDb implements CompanyDb { @Override public Collection getMembers(long companyId) throws UmbrellaException { try { - var rs = select("*").from(TABLE_COMPANIES_USERS).where(COMPANY_ID, equal(companyId)).exec(db); + var rs = select(ALL).from(TABLE_COMPANIES_USERS).where(COMPANY_ID, equal(companyId)).exec(db); var ids = new HashSet(); while (rs.next()) ids.add(rs.getLong(USER_ID)); rs.close(); @@ -42,7 +43,7 @@ public class SqliteDb implements CompanyDb { @Override public Map listCompaniesOf(long userId) throws UmbrellaException { try { - var rs = select("*").from(TABLE_COMPANIES).leftJoin(ID,TABLE_COMPANIES_USERS,COMPANY_ID).where(USER_ID,equal(userId)).exec(db); + var rs = select(ALL).from(TABLE_COMPANIES).leftJoin(ID,TABLE_COMPANIES_USERS,COMPANY_ID).where(USER_ID,equal(userId)).exec(db); var companies = new HashMap(); while (rs.next()) { var company = Company.of(rs); @@ -58,7 +59,7 @@ public class SqliteDb implements CompanyDb { @Override public Company load(long companyId) throws UmbrellaException { try { - var rs = select("*").from(TABLE_COMPANIES).where(ID, equal(companyId)).exec(db); + var rs = select(ALL).from(TABLE_COMPANIES).where(ID, equal(companyId)).exec(db); Company company = null; if (rs.next()) company = Company.of(rs); rs.close(); diff --git a/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java b/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java index 1b10bea..06ecbe1 100644 --- a/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java +++ b/contact/src/main/java/de/srsoftware/umbrella/contact/SqliteDb.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.contact; import static de.srsoftware.tools.jdbc.Condition.equal; +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.contact.Constants.TABLE_CONTACTS; import static de.srsoftware.umbrella.contact.Constants.TABLE_CONTACTS_USERS; @@ -24,7 +25,7 @@ public class SqliteDb implements ContactDb{ @Override public Collection listContactsOf(long userId) throws UmbrellaException{ try { - var rs = select("*").from(TABLE_CONTACTS).leftJoin(ID,TABLE_CONTACTS_USERS,USER_ID).where(USER_ID,equal(userId)).exec(conn); + var rs = select(ALL).from(TABLE_CONTACTS).leftJoin(ID,TABLE_CONTACTS_USERS,USER_ID).where(USER_ID,equal(userId)).exec(conn); var contacts = new HashSet(); while (rs.next()) contacts.add(Contact.of(rs)); rs.close(); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Util.java b/core/src/main/java/de/srsoftware/umbrella/core/Util.java index 064e4b5..bede39d 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Util.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Util.java @@ -9,6 +9,7 @@ import static java.lang.System.Logger.Level.WARNING; import static java.nio.charset.StandardCharsets.UTF_8; import com.xrbpowered.jparsedown.JParsedown; +import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Query; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import java.io.*; @@ -18,6 +19,7 @@ import java.net.URL; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.json.JSONObject; @@ -40,6 +42,12 @@ public class Util { }; } + public static Map> mapValues(Map map){ + var result = new HashMap>(); + for (var entry : map.entrySet()) result.put(entry.getKey(),entry.getValue().toMap()); + return result; + } + public static String markdown(String source){ try { if (plantumlJar.exists()) { 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 25dfbb8..09370bd 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 @@ -3,12 +3,12 @@ package de.srsoftware.umbrella.core.api; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Task; -import java.util.Collection; +import java.util.HashMap; public interface TaskService { CompanyService companyService(); - Collection listCompanyTasks(long companyId) throws UmbrellaException; - Collection listProjectTasks(long projectId) throws UmbrellaException; + HashMap listCompanyTasks(long companyId) throws UmbrellaException; + HashMap listProjectTasks(long projectId) throws UmbrellaException; ProjectService projectService(); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java index 233d6ce..4096289 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java @@ -1,7 +1,6 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.model; -import static de.srsoftware.tools.Optionals.nullIfEmpty; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Util.markdown; diff --git a/frontend/src/Components/TaskList.svelte b/frontend/src/Components/TaskList.svelte new file mode 100644 index 0000000..6485fee --- /dev/null +++ b/frontend/src/Components/TaskList.svelte @@ -0,0 +1,15 @@ + + +
    + {#each sortedTasks as task} +
  • {task.name}
  • + {/each} +
\ No newline at end of file diff --git a/frontend/src/routes/project/List.svelte b/frontend/src/routes/project/List.svelte index d45206c..377b0d7 100644 --- a/frontend/src/routes/project/List.svelte +++ b/frontend/src/routes/project/List.svelte @@ -8,6 +8,8 @@ let projects = $state(null); let companies = $state(null); + let sortedProjects = $derived.by(() => Object.values(projects).sort((a, b) => a.name.localeCompare(b.name))); + async function loadProjects(){ let url = `${location.protocol}//${location.host.replace('5173','8080')}/api/company/list`; let resp = await fetch(url,{credentials:'include'}); @@ -48,8 +50,8 @@ - {#each Object.entries(projects) as [pid,project]} - router.navigate(`/project/${pid}/view`)}> + {#each sortedProjects as project} + router.navigate(`/project/${project.id}/view`)}> {project.name} {#if project.company_id} diff --git a/frontend/src/routes/project/View.svelte b/frontend/src/routes/project/View.svelte index baf6683..e6e780f 100644 --- a/frontend/src/routes/project/View.svelte +++ b/frontend/src/routes/project/View.svelte @@ -1,21 +1,39 @@ @@ -55,7 +73,11 @@ {t('tasks')} - TODO + + {#if tasks} + + {/if} + {t('members')} diff --git a/items/src/main/java/de/srsoftware/umbrella/items/SqliteDb.java b/items/src/main/java/de/srsoftware/umbrella/items/SqliteDb.java index cca7b11..fb11c45 100644 --- a/items/src/main/java/de/srsoftware/umbrella/items/SqliteDb.java +++ b/items/src/main/java/de/srsoftware/umbrella/items/SqliteDb.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.items; import static de.srsoftware.tools.jdbc.Condition.equal; +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.core.Constants.COMPANY_ID; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; @@ -25,7 +26,7 @@ public class SqliteDb implements ItemDb{ public Collection list(long companyId) throws UmbrellaException { try { var items = new HashSet(); - var rs = select("*").from(TABLE_ITEMS).where(COMPANY_ID, equal(companyId)).exec(db); + var rs = select(ALL).from(TABLE_ITEMS).where(COMPANY_ID, equal(companyId)).exec(db); while (rs.next()) items.add(Item.of(rs)); rs.close(); return items; 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 8a85ea3..150d6bb 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.project; import static de.srsoftware.tools.jdbc.Condition.*; +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.insertInto; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.core.Constants.*; @@ -37,7 +38,7 @@ public class SqliteDb implements ProjectDb { private Map addMembers(Map projects) throws SQLException, UmbrellaException { Object[] ids = projects.keySet().toArray(); - var rs = select("*").from(TABLE_PROJECT_USERS).where(PROJECT_ID,in(ids)).exec(db); + var rs = select(ALL).from(TABLE_PROJECT_USERS).where(PROJECT_ID,in(ids)).exec(db); while (rs.next()){ var userId = rs.getLong(USER_ID); var projectId = rs.getLong(PROJECT_ID); @@ -136,7 +137,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) @Override public Project load(long projectId) throws UmbrellaException { try { - var rs = select("*").from(TABLE_PROJECTS).where(ID, equal(projectId)).exec(db); + var rs = select(ALL).from(TABLE_PROJECTS).where(ID, equal(projectId)).exec(db); Project result = null; if (rs.next()) result = Project.of(rs); rs.close(); @@ -152,7 +153,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) public Map ofCompany(long companyId, boolean includeClosed) throws UmbrellaException { try { var projects = new HashMap(); - var query = select("*").from(TABLE_PROJECTS).where(COMPANY_ID, equal(companyId)); + var query = select(ALL).from(TABLE_PROJECTS).where(COMPANY_ID, equal(companyId)); if (!includeClosed) query = query.where(STATUS,lessThan(Project.Status.Complete.code())); var rs = query.exec(db); while (rs.next()){ @@ -172,7 +173,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) public Map ofUser(long userId, boolean includeClosed) throws UmbrellaException { try { var projects = new HashMap(); - var query = select("*").from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId)); + var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId)); if (!includeClosed) query = query.where(STATUS,lessThan(Project.Status.Complete.code())); var rs = query.exec(db); while (rs.next()){ diff --git a/task/src/main/java/de/srsoftware/umbrella/task/Constants.java b/task/src/main/java/de/srsoftware/umbrella/task/Constants.java index e23b008..5c5ab84 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/Constants.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/Constants.java @@ -8,5 +8,7 @@ public class Constants { public static final String CHILDREN = "children"; public static final String ESTIMATED_TIMES = "estimated_times"; public static final String TABLE_TASKS = "tasks"; - public static final String FIELD_TASKS = "tasks"; + public static final String TABLE_TASKS_USERS = "tasks_users"; + public static final String TASKS = "tasks"; + public static final String TASK_ID = "task_id"; } 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 b1935ad..bcd6c89 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java @@ -2,38 +2,63 @@ package de.srsoftware.umbrella.task; -import static de.srsoftware.tools.jdbc.Condition.in; +import static de.srsoftware.tools.jdbc.Condition.*; +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; -import static de.srsoftware.umbrella.core.Constants.PROJECT_ID; +import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; -import static de.srsoftware.umbrella.task.Constants.TABLE_TASKS; +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.Task; +import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.sql.Connection; import java.sql.SQLException; -import java.util.Collection; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; public class SqliteDb implements TaskDb { - + private static final System.Logger LOG = System.getLogger("TaskDb"); private final Connection db; public SqliteDb(Connection connection) { db = connection; } - public Collection listTasks(List projectIds) throws UmbrellaException { + public HashMap listTasks(List projectIds) throws UmbrellaException { try { - var rs = select("*").from(TABLE_TASKS).where(PROJECT_ID, in(projectIds.toArray())).exec(db); - var list = new HashSet(); - while (rs.next()) list.add(Task.of(rs)); + var tasks = new HashMap(); + var rs = select(ALL).from(TABLE_TASKS).where(PROJECT_ID, in(projectIds.toArray())).exec(db); + while (rs.next()){ + var task = Task.of(rs); + tasks.put(task.id(),task); + } rs.close(); - return list; + return tasks; } catch (SQLException e) { throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project ids"); } } + + public HashMap listRootTasks(UmbrellaUser user, Long projectId) { + try { + var tasks = new HashMap(); + var rs = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID) + .where(PROJECT_ID,equal(projectId)) + .where(USER_ID,equal(user.id())) + .where(PARENT_TASK_ID,isNull()) + .exec(db); + while (rs.next()){ + var task = Task.of(rs); + tasks.put(task.id(),task); + } + rs.close(); + return tasks; + } catch (SQLException e){ + LOG.log(WARNING,"Failed to load tasks for project (pid: {0}, user_id: {1}",projectId,user.id(),e); + throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project id"); + } + } } 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 7c57815..07f6000 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -2,13 +2,16 @@ package de.srsoftware.umbrella.task; import static de.srsoftware.tools.Optionals.is0; +import static de.srsoftware.tools.Optionals.isSet; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.Constants.COMPANY_ID; import static de.srsoftware.umbrella.core.Constants.PROJECT_ID; +import static de.srsoftware.umbrella.core.Paths.LIST; +import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED; +import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static de.srsoftware.umbrella.task.Constants.*; -import static java.util.stream.Collectors.toMap; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; @@ -57,6 +60,7 @@ public class TaskModule extends BaseHandler implements TaskService { var head = path.pop(); return switch (head) { case ESTIMATED_TIMES -> estimatedTimes(user.get(),ex); + case LIST -> postTaskList(user.get(),ex); default -> super.doGet(path,ex); }; } catch (UmbrellaException e){ @@ -71,16 +75,15 @@ public class TaskModule extends BaseHandler implements TaskService { var company = companies.get(companyId); if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name()); var projects = this.projects.listCompanyProjects(companyId,false); - var taskList = taskDb.listTasks(projects.stream().map(Project::id).toList()); - var map = taskList.stream().collect(toMap(Task::id, t -> t)); + var map = taskDb.listTasks(projects.stream().map(Project::id).toList()); var tree = new HashMap>(); - taskList.stream().filter(task -> !is0(task.estimatedTime())).forEach(task -> placeInTree(task,tree,map)); + map.values().stream().filter(task -> !is0(task.estimatedTime())).forEach(task -> placeInTree(task,tree,map)); var result = new ArrayList>(); projects.forEach(project -> { var projectMap = new HashMap<>(project.toMap()); var children = tree.values().stream().filter(root -> project.id() == (Long)root.get(PROJECT_ID)).toList(); if (!children.isEmpty()) { - projectMap.put(FIELD_TASKS, children); + projectMap.put(TASKS, children); result.add(projectMap); } }); @@ -88,13 +91,13 @@ public class TaskModule extends BaseHandler implements TaskService { } @Override - public Collection listCompanyTasks(long companyId) throws UmbrellaException { + public HashMap listCompanyTasks(long companyId) throws UmbrellaException { var projectList = projects.listCompanyProjects(companyId,false); return taskDb.listTasks(projectList.stream().map(Project::id).toList()); } @Override - public Collection listProjectTasks(long projectId) throws UmbrellaException { + public HashMap listProjectTasks(long projectId) throws UmbrellaException { return taskDb.listTasks(List.of(projectId)); } @@ -103,6 +106,7 @@ public class TaskModule extends BaseHandler implements TaskService { if (task.parentTaskId() != null){ Task parent = map.get(task.parentTaskId()); var trunk = placeInTree(parent,tree,map); + @SuppressWarnings("unchecked") ArrayList children = (ArrayList) trunk.computeIfAbsent(CHILDREN, k -> new ArrayList()); children.add(taskMap); return taskMap; @@ -111,6 +115,13 @@ public class TaskModule extends BaseHandler implements TaskService { return taskMap; } + private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException { + var json = json(ex); + var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number number ? number.longValue() : null; + if (isSet(projectId)) return sendContent(ex,mapValues(taskDb.listRootTasks(user,projectId))); + return sendEmptyResponse(HTTP_NOT_IMPLEMENTED,ex); + } + @Override public ProjectService projectService() { return projects; 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 7dffc93..d33736e 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.time; import static de.srsoftware.tools.jdbc.Condition.in; +import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.core.Constants.ID; import static de.srsoftware.umbrella.time.Constants.*; @@ -25,7 +26,7 @@ public class SqliteDb implements TimeDb { @Override public Collection