Browse Source

improved task list

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
feature/brute_force_protection
Stephan Richter 3 months ago
parent
commit
84075b4634
  1. 3
      frontend/src/routes/project/List.svelte
  2. 53
      frontend/src/routes/task/Index.svelte
  3. 12
      legacy/src/main/java/de/srsoftware/umbrella/legacy/CompanyLegacy.java
  4. 5
      legacy/src/main/java/de/srsoftware/umbrella/legacy/ProjectLegacy.java
  5. 25
      task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java
  6. 3
      task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java
  7. 21
      task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java
  8. 1
      translations/src/main/resources/de.json

3
frontend/src/routes/project/List.svelte

@ -94,8 +94,7 @@
{/if} {/if}
</td> </td>
<td class="state" onclick={() => show(project.id)} > <td class="state" onclick={() => show(project.id)} >
{t("state_"+project.allowed_states[project.status]?.toLowerCase())}
{t("state_"+project.allowed_states[project.status].toLowerCase())}
</td> </td>
<td class="members" onclick={() => show(project.id)} > <td class="members" onclick={() => show(project.id)} >
{#each Object.entries(project.members) as [uid,member]} {#each Object.entries(project.members) as [uid,member]}

53
frontend/src/routes/task/Index.svelte

@ -9,6 +9,10 @@
let projects = $state({}); let projects = $state({});
let router = useTinyRouter(); let router = useTinyRouter();
let tasks = $state(null); let tasks = $state(null);
let bounds = $state({offset:0,limit:256});
let loading = $state(true);
let map = $state({});
let hidden = $state({});
async function changeState(tid,state){ async function changeState(tid,state){
const task = tasks[tid]; const task = tasks[tid];
@ -43,24 +47,37 @@
router.navigate(`/${module}/${id}/view`); router.navigate(`/${module}/${id}/view`);
} }
async function load(){ async function loadProject(pid){
let url = api('task'); const url = api(`project/${pid}`);
let resp = await fetch(url,{credentials:'include'}); const resp = await fetch(url,{credentials:'include'});
let project_ids = {};
if (resp.ok){ if (resp.ok){
tasks = await resp.json(); projects[pid] = await resp.json();
for (let task of (Object.values(tasks))) project_ids[task.project_id] = true;
} else { } else {
error = await resp.text(); error = await resp.text();
} }
for (let pid of Object.keys(project_ids)){ }
url = api(`project/${pid}`);
resp = await fetch(url,{credentials:'include'}); async function load(){
if (resp.ok){ const url = api(`task?offset=${bounds.offset}&limit=${bounds.limit}`);
projects[pid] = await resp.json(); const resp = await fetch(url,{credentials:'include'});
} else { if (resp.ok){
error = await resp.text(); let newTasks = await resp.json();
if (bounds.offset == 0) {
tasks = newTasks;
} else tasks = tasks.concat(newTasks);
loading = newTasks.length;
if (loading){
for (let task of newTasks) {
map[task.id] = task;
if (task.parent_task_id) hidden[task.parent_task_id] = true;
let pid = task.project_id;
if (pid && !projects[pid]) await loadProject(pid);
}
bounds.offset += bounds.limit;
load();
} }
} else {
error = await resp.text();
} }
} }
@ -80,7 +97,7 @@
</script> </script>
<fieldset> <fieldset>
<legend>{t('task_list')}</legend> <legend>{loading ? t('loading_object',{object:t('task_list')}) : t('task_list')}</legend>
{#if error} {#if error}
<soan class="error">{error}</soan> <soan class="error">{error}</soan>
{/if} {/if}
@ -97,14 +114,14 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each Object.entries(tasks) as [tid,task]} {#each tasks as task (task.id)}
{#if task.status < 60} {#if task.status > 10 && task.status < 60 && !task.no_index && projects[task.project_id]?.status < 60 && !hidden[task.id]}
<tr> <tr>
<td onclick={() => go('task',tid)}>{task.name}</td> <td onclick={() => go('task',task.id)}>{task.name}</td>
<td> <td>
<a href="#" onclick={() => go('project',task.project_id)}> {projects[task.project_id]?.name}</a> <a href="#" onclick={() => go('project',task.project_id)}> {projects[task.project_id]?.name}</a>
{#if task.parent_task_id} {#if task.parent_task_id}
: <a href="#" onclick={() => go('task',task.parent_task_id)}>{tasks[task.parent_task_id]?.name}</a> : <a href="#" onclick={() => go('task',task.parent_task_id)}>{map[task.parent_task_id]?.name}</a>
{/if} {/if}
</td> </td>
<td> <td>

12
legacy/src/main/java/de/srsoftware/umbrella/legacy/CompanyLegacy.java

@ -2,6 +2,11 @@
package de.srsoftware.umbrella.legacy; package de.srsoftware.umbrella.legacy;
import static de.srsoftware.tools.Optionals.nullable;
import static de.srsoftware.umbrella.core.Constants.TOKEN;
import static de.srsoftware.umbrella.core.Paths.JSON;
import static de.srsoftware.umbrella.core.Util.mapValues;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration; import de.srsoftware.configuration.Configuration;
import de.srsoftware.tools.Path; import de.srsoftware.tools.Path;
@ -10,17 +15,10 @@ import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import static de.srsoftware.tools.Optionals.nullable;
import static de.srsoftware.umbrella.core.Constants.TOKEN;
import static de.srsoftware.umbrella.core.Constants.USERS;
import static de.srsoftware.umbrella.core.Paths.JSON;
import static de.srsoftware.umbrella.core.Util.mapValues;
public class CompanyLegacy extends BaseHandler { public class CompanyLegacy extends BaseHandler {
private final ModuleRegistry registry; private final ModuleRegistry registry;
private final Configuration config; private final Configuration config;

5
legacy/src/main/java/de/srsoftware/umbrella/legacy/ProjectLegacy.java

@ -6,7 +6,6 @@ import static de.srsoftware.tools.Optionals.nullable;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Paths.JSON; import static de.srsoftware.umbrella.core.Paths.JSON;
import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration; import de.srsoftware.configuration.Configuration;
@ -15,13 +14,11 @@ import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import org.json.JSONObject;
public class ProjectLegacy extends BaseHandler { public class ProjectLegacy extends BaseHandler {
private final ModuleRegistry registry; private final ModuleRegistry registry;
private final Configuration config; private final Configuration config;

25
task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java

@ -7,7 +7,7 @@ import static de.srsoftware.tools.jdbc.Query.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.core.model.Status.OPEN; import static de.srsoftware.umbrella.core.model.Status.*;
import static de.srsoftware.umbrella.project.Constants.*; import static de.srsoftware.umbrella.project.Constants.*;
import static de.srsoftware.umbrella.task.Constants.*; import static de.srsoftware.umbrella.task.Constants.*;
import static java.lang.System.Logger.Level.*; import static java.lang.System.Logger.Level.*;
@ -20,9 +20,7 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*; import de.srsoftware.umbrella.core.model.*;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.Map;
public class SqliteDb extends BaseDb implements TaskDb { public class SqliteDb extends BaseDb implements TaskDb {
@ -158,7 +156,7 @@ CREATE TABLE IF NOT EXISTS {0} (
.where(PROJECT_ID,equal(projectId)) .where(PROJECT_ID,equal(projectId))
.where(USER_ID,equal(user.id())) .where(USER_ID,equal(user.id()))
.where(PARENT_TASK_ID,isNull()); .where(PARENT_TASK_ID,isNull());
if (!showClosed) query.where(STATUS,lessThan(Status.COMPLETE.code())); if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db); var rs = query.exec(db);
while (rs.next()){ while (rs.next()){
var task = Task.of(rs); var task = Task.of(rs);
@ -178,7 +176,7 @@ CREATE TABLE IF NOT EXISTS {0} (
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID) var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
.where(PARENT_TASK_ID,equal(parentTaskId)) .where(PARENT_TASK_ID,equal(parentTaskId))
.where(USER_ID,equal(user.id())); .where(USER_ID,equal(user.id()));
if (!showClosed) query.where(STATUS,lessThan(Status.COMPLETE.code())); if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db); var rs = query.exec(db);
while (rs.next()){ while (rs.next()){
var task = Task.of(rs); var task = Task.of(rs);
@ -197,7 +195,7 @@ CREATE TABLE IF NOT EXISTS {0} (
try { try {
var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId)); var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId));
if (parentTaskId != 0) query.where(PARENT_TASK_ID,equal(parentTaskId)); if (parentTaskId != 0) query.where(PARENT_TASK_ID,equal(parentTaskId));
if (noIndex) query.where(NO_INDEX,equal(false)); if (!noIndex) query.where(NO_INDEX,notIn(1));
var tasks = new HashMap<Long,Task>(); var tasks = new HashMap<Long,Task>();
var rs = query.exec(db); var rs = query.exec(db);
while (rs.next()){ while (rs.next()){
@ -212,14 +210,13 @@ CREATE TABLE IF NOT EXISTS {0} (
} }
@Override @Override
public HashMap<Long, Task> listUserTasks(long userId) { public List<Task> listUserTasks(long userId, Long limit, long offset, boolean showClosed) {
try { try {
var rs = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID).where(USER_ID,equal(userId)).exec(db); var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID).where(USER_ID,equal(userId));
var map = new HashMap<Long,Task>(); if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code()));
while (rs.next()) { var rs = query.sort("(CASE due_date WHEN \"\" THEN '9999-99-99' ELSE IFNULL(due_date,'9999-99-99') END), status COLLATE NOCASE").limit(limit).skip(offset).exec(db);
var task = Task.of(rs); var map = new ArrayList<Task>();
map.put(task.id(),task); while (rs.next()) map.add(Task.of(rs));
}
rs.close(); rs.close();
return map; return map;
} catch (SQLException e) { } catch (SQLException e) {

3
task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java

@ -8,6 +8,7 @@ import de.srsoftware.umbrella.core.model.Task;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public interface TaskDb { public interface TaskDb {
@ -19,7 +20,7 @@ public interface TaskDb {
HashMap<Long, Task> listProjectTasks(Long projectId, Long parentTaskId, boolean noIndex) throws UmbrellaException; HashMap<Long, Task> listProjectTasks(Long projectId, Long parentTaskId, boolean noIndex) throws UmbrellaException;
HashMap<Long, Task> listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed); HashMap<Long, Task> listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed);
HashMap<Long, Task> listTasks(Collection<Long> projectIds) throws UmbrellaException; HashMap<Long, Task> listTasks(Collection<Long> projectIds) throws UmbrellaException;
HashMap<Long, Task> listUserTasks(long userId); List<Task> listUserTasks(long userId, Long limit, long offset, boolean showClosed);
Task load(long taskId) throws UmbrellaException; Task load(long taskId) throws UmbrellaException;

21
task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java

@ -183,8 +183,25 @@ public class TaskModule extends BaseHandler implements TaskService {
} }
private boolean getUserTasks(UmbrellaUser user, HttpExchange ex) throws IOException { private boolean getUserTasks(UmbrellaUser user, HttpExchange ex) throws IOException {
var list = taskDb.listUserTasks(user.id()); long offset = 0;
return sendContent(ex,mapValues(list)); Long limit = null;
var params = queryParam(ex);
if (params.get(OFFSET) instanceof String o) try {
offset = Long.parseLong(o);
} catch (NumberFormatException e) {
throw invalidFieldException(OFFSET,"number");
}
if (params.get(LIMIT) instanceof String l) try {
limit = Long.parseLong(l);
} catch (NumberFormatException e) {
throw invalidFieldException(LIMIT,"number");
}
Set<Long> projectIds = registry.projectService().listUserProjects(user.id(), true).keySet();
var list = taskDb.listUserTasks(user.id(), limit, offset, false).stream()
.filter(task -> projectIds.contains(task.projectId())) // drop tasks assigned to project we are not member of
.map(Task::toMap)
.toList();
return sendContent(ex,list);
} }
@Override @Override

1
translations/src/main/resources/de.json

@ -103,6 +103,7 @@
"LIST_USERS": "Nutzer auflisten", "LIST_USERS": "Nutzer auflisten",
"loading": "lade…", "loading": "lade…",
"loading_data": "Daten werden geladen…", "loading_data": "Daten werden geladen…",
"loading_object": "lade {object}…",
"local_court": "Amtsgericht", "local_court": "Amtsgericht",
"login" : "Anmeldung", "login" : "Anmeldung",
"login_services": "Login-Services", "login_services": "Login-Services",

Loading…
Cancel
Save