Browse Source

implemented storing of tasks

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
kanban
Stephan Richter 3 months ago
parent
commit
d2776bd3b0
  1. 28
      core/src/main/java/de/srsoftware/umbrella/core/model/Task.java
  2. 9
      frontend/src/Components/MarkdownEditor.svelte
  3. 6
      frontend/src/routes/task/Add.svelte
  4. 9
      frontend/src/routes/task/View.svelte
  5. 33
      task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java
  6. 4
      task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java
  7. 22
      task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java

28
core/src/main/java/de/srsoftware/umbrella/core/model/Task.java

@ -1,11 +1,16 @@ @@ -1,11 +1,16 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model;
import static de.srsoftware.tools.Optionals.getPath;
import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Util.markdown;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
import de.srsoftware.tools.Mappable;
import org.json.JSONObject;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
@ -34,6 +39,29 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri @@ -34,6 +39,29 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri
);
}
public static Task of(JSONObject json){
if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number prjId)) throw missingFieldException(PROJECT_ID);
Long parentTaskId = json.has(PARENT_TASK_ID) && json.get(PARENT_TASK_ID) instanceof Number ptid ? ptid.longValue() : null;
if (!(json.has(NAME) && json.get(NAME) instanceof String name && !name.isBlank())) throw missingFieldException(NAME);
if (!json.has(DESCRIPTION)) throw missingFieldException(DESCRIPTION);
var description = switch (json.get(DESCRIPTION)){
case String d -> d;
case JSONObject j -> j.getString(SOURCE);
default -> throw invalidFieldException(json.get(DESCRIPTION).getClass().getSimpleName(),"String or JSON");
};
var status = Status.OPEN;
Double estimatedTime = null; // TODO: provide form field
LocalDate startDate = null;
LocalDate dueDate = null;
var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean sc ? sc : false;
var noIndex = json.has(NO_INDEX) && json.get(NO_INDEX) instanceof Boolean ni ? ni : false;
if (!(json.has(MEMBERS) && json.get(MEMBERS) instanceof JSONObject members)) throw missingFieldException(MEMBERS);
return new Task(0,prjId.longValue(),parentTaskId,name,description,status,estimatedTime,startDate,dueDate,showClosed,noIndex,new HashMap<>());
}
@Override
public Map<String, Object> toMap() {
var map = new HashMap<String,Object>();

9
frontend/src/Components/MarkdownEditor.svelte

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
let {
editable = true,
start_open = false,
simple = false,
value = $bindable({source:null,rendered:null}),
onSet = (newVal) => {}
} = $props();
@ -45,7 +45,10 @@ @@ -45,7 +45,10 @@
}
function typed(ev){
if (ev.keyCode == 13 && ev.ctrlKey) applyEdit();
if (simple) {
value.source = editValue.source;
value.rendered = editValue.rendered;
} else if (ev.keyCode == 13 && ev.ctrlKey) applyEdit();
if (ev.keyCode == 27) resetEdit();
if (timer) clearTimeout(timer);
@ -53,7 +56,7 @@ @@ -53,7 +56,7 @@
}
activeField.subscribe((val) => resetEdit());
if (start_open) startEdit();
if (simple) startEdit();
</script>
<style>

6
frontend/src/routes/task/Add.svelte

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
import { api } from '../../urls.svelte.js';
import { user } from '../../user.svelte.js';
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import MemberEditor from '../../Components/MemberEditor.svelte';
@ -15,6 +16,7 @@ @@ -15,6 +16,7 @@
description : { source : '', rendered : '' },
members : {}
});
let router = useTinyRouter();
function load(){
if (project_id) loadProject();
@ -34,7 +36,7 @@ @@ -34,7 +36,7 @@
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
project = await resp.json();
task.project_id = project_id;
task.project_id = +project_id;
task.members = JSON.parse(JSON.stringify(project.members)); // deep copy
error = null;
} else {
@ -96,7 +98,7 @@ @@ -96,7 +98,7 @@
{t('description')}
</th>
<td>
<MarkdownEditor bind:value={task.description} start_open={true}/>
<MarkdownEditor bind:value={task.description} simple={true}/>
</td>
</tr>
<tr>

9
frontend/src/routes/task/View.svelte

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
import { t } from '../../translations.svelte.js';
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte.js';
import TaskList from '../../Components/TaskList.svelte';
@ -21,7 +22,7 @@ @@ -21,7 +22,7 @@
}
async function loadChildren(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/list`;
const url = api('task/list');
var data = {
parent_task_id:+task.id,
show_closed: task.show_closed
@ -40,7 +41,7 @@ @@ -40,7 +41,7 @@
}
async function loadProject(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/${task.project_id}`;
const url = api(`project/${task.project_id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
project = await resp.json();
@ -51,7 +52,7 @@ @@ -51,7 +52,7 @@
}
async function loadTask(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/${id}`;
const url = api(`task/${id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
task = await resp.json();
@ -69,8 +70,6 @@ @@ -69,8 +70,6 @@
if (!project) return;
router.navigate(`/project/${project.id}/view`)
}
onMount(loadTask);
</script>
{#if error}

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

@ -3,14 +3,16 @@ package de.srsoftware.umbrella.task; @@ -3,14 +3,16 @@ package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.jdbc.Condition.*;
import static de.srsoftware.tools.jdbc.Query.*;
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_NOT_IMPLEMENTED;
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.tools.jdbc.Query;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import java.sql.Connection;
@ -110,4 +112,33 @@ public class SqliteDb implements TaskDb { @@ -110,4 +112,33 @@ public class SqliteDb implements TaskDb {
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load task from database");
}
}
@Override
public Task save(Task task) {
try {
if (task.id() == 0){ // new task
var rs = insertInto(TABLE_TASKS,PROJECT_ID,PARENT_TASK_ID,NAME,DESCRIPTION,STATUS,EST_TIME,START_DATE,DUE_DATE,SHOW_CLOSED,NO_INDEX)
.values(task.projectId(),task.parentTaskId(),task.name(),task.description(),task.status().code(),task.estimatedTime(),task.start(),task.dueDate(),task.showClosed(),task.noIndex())
.execute(db)
.getGeneratedKeys();
Long taskId = null;
if (rs.next()) taskId=rs.getLong(1);
rs.close();
if (taskId == null) throw new UmbrellaException("Failed to save task {0}",task.name());
return new Task(taskId,task.projectId(),task.parentTaskId(),task.name(),task.description(),task.status(),task.estimatedTime(),task.start(),task.dueDate(),task.showClosed(),task.noIndex(),task.members());
}
throw new UmbrellaException(HTTP_NOT_IMPLEMENTED,"updating task in SqliteDb.save(task) not implemented");
} catch (SQLException e){
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to save task {0}",task.name());
}
}
@Override
public void setMember(long taskId, long userId, Permission permission) {
try {
replaceInto(TABLE_TASKS_USERS,TASK_ID,USER_ID,PERMISSIONS).values(taskId,userId,permission.code()).execute(db).close();
} catch (SQLException e) {
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to store permissions");
}
}
}

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

@ -12,4 +12,8 @@ public interface TaskDb { @@ -12,4 +12,8 @@ public interface TaskDb {
Task load(long taskId) throws UmbrellaException;
Map<Long, Permission> getMembers(Task task);
Task save(Task task);
void setMember(long taskId, long userId, Permission permission);
}

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

@ -8,10 +8,10 @@ import static de.srsoftware.umbrella.core.Constants.*; @@ -8,10 +8,10 @@ import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Paths.*;
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.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
import static de.srsoftware.umbrella.task.Constants.*;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration;
@ -27,6 +27,8 @@ import de.srsoftware.umbrella.core.model.*; @@ -27,6 +27,8 @@ import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.core.model.Task;
import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import org.json.JSONObject;
import java.io.IOException;
import java.util.*;
@ -174,12 +176,26 @@ public class TaskModule extends BaseHandler implements TaskService { @@ -174,12 +176,26 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean postNewTask(UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid)) throw missingFieldException(PROJECT_ID);
if (!(json.has(MEMBERS) && json.get(MEMBERS) instanceof JSONObject memberData)) throw missingFieldException(MEMBERS);
long projectId = pid.longValue();
var project = projects.load(projectId);
projects.loadMembers(List.of(project));
var member = project.members().get(user.id());
if (member == null || member.permission() == Permission.READ_ONLY) throw forbidden("You are not allowed to create new tasks in this project");
Task task = Task.of(json);
return taskDb.save(task);
task = taskDb.save(task);
for (var key : memberData.keySet()){
long userId = Long.parseLong(key);
if (!(memberData.get(key) instanceof JSONObject nested)) throw invalidFieldException("members."+userId,"JSON");
if (!(nested.has(PERMISSION) && nested.get(PERMISSION) instanceof JSONObject permission)) throw invalidFieldException("members."+userId+".permission","JSON");
if (!(permission.has(CODE) && permission.get(CODE) instanceof Number code)) throw invalidFieldException("members."+userId+".permission.code","int");
if (!project.members().containsKey(userId)) {
String username = nested.has(USER) && nested.get(USER) instanceof JSONObject userData && userData.get(NAME) instanceof String n ? n : key;
throw new UmbrellaException(HTTP_BAD_REQUEST,"User {0} is no member of the leading project and cannot be assigned to this task",username);
}
taskDb.setMember(task.id(),userId,Permission.of(code.intValue()));
}
return sendContent(ex,task);
}
private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException {

Loading…
Cancel
Save