implemented task editing right from the project list
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
/* © SRSoftware 2025 */
|
/* © SRSoftware 2025 */
|
||||||
package de.srsoftware.umbrella.core.model;
|
package de.srsoftware.umbrella.core.model;
|
||||||
|
|
||||||
import static de.srsoftware.tools.Optionals.getPath;
|
|
||||||
import static de.srsoftware.tools.Optionals.nullIfEmpty;
|
import static de.srsoftware.tools.Optionals.nullIfEmpty;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
|
import static de.srsoftware.umbrella.core.Util.LOG;
|
||||||
import static de.srsoftware.umbrella.core.Util.markdown;
|
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.invalidFieldException;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||||
|
import static java.lang.System.Logger.Level.WARNING;
|
||||||
|
|
||||||
import de.srsoftware.tools.Mappable;
|
import de.srsoftware.tools.Mappable;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -14,10 +15,74 @@ import org.json.JSONObject;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
|
public class Task implements Mappable {
|
||||||
|
public static final System.Logger LOG = System.getLogger(Task.class.getSimpleName());
|
||||||
|
private final long id, projectId;
|
||||||
|
private final Long parentTaskId;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private Status status;
|
||||||
|
private final Double estimatedTime;
|
||||||
|
private final LocalDate start, dueDate;
|
||||||
|
private boolean showClosed;
|
||||||
|
private final boolean noIndex;
|
||||||
|
private final Map<Long, Member> members;
|
||||||
|
private final Set<String> dirtyFields = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
public Task (long id, long projectId, Long parentTaskId, String name, String description, Status status, Double estimatedTime, LocalDate start, LocalDate dueDate, boolean showClosed, boolean noIndex, Map<Long,Member> members){
|
||||||
|
this.id = id;
|
||||||
|
this.projectId = projectId;
|
||||||
|
this.parentTaskId = parentTaskId;
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.status = status;
|
||||||
|
this.estimatedTime = estimatedTime;
|
||||||
|
this.start = start;
|
||||||
|
this.dueDate = dueDate;
|
||||||
|
this.showClosed = showClosed;
|
||||||
|
this.noIndex = noIndex;
|
||||||
|
this.members = members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clean() {
|
||||||
|
dirtyFields.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String description(){
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate dueDate(){
|
||||||
|
return dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double estimatedTime(){
|
||||||
|
return estimatedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long id(){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return !dirtyFields.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Long,Member> members(){
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean noIndex(){
|
||||||
|
return noIndex;
|
||||||
|
}
|
||||||
|
|
||||||
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<Long,Member> members) implements Mappable {
|
|
||||||
public static Task of(ResultSet rs) throws SQLException {
|
public static Task of(ResultSet rs) throws SQLException {
|
||||||
var estTime = rs.getDouble(EST_TIME);
|
var estTime = rs.getDouble(EST_TIME);
|
||||||
var parentTaskId = rs.getLong(PARENT_TASK_ID);
|
var parentTaskId = rs.getLong(PARENT_TASK_ID);
|
||||||
@@ -69,6 +134,43 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri
|
|||||||
return new Task(0,prjId.longValue(),parentTaskId,name,description,status,estimatedTime,startDate,dueDate,showClosed,noIndex,new HashMap<>());
|
return new Task(0,prjId.longValue(),parentTaskId,name,description,status,estimatedTime,startDate,dueDate,showClosed,noIndex,new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task patch(JSONObject json) {
|
||||||
|
for (var key : json.keySet()){
|
||||||
|
switch (key){
|
||||||
|
case DESCRIPTION: description = json.getString(key); break;
|
||||||
|
case NAME: name = json.getString(key); break;
|
||||||
|
case SHOW_CLOSED: showClosed = json.getBoolean(SHOW_CLOSED); break;
|
||||||
|
case STATUS: status = json.get(key) instanceof Number number ? Status.of(number.intValue()) : Status.valueOf(json.getString(key)); break;
|
||||||
|
default: {
|
||||||
|
key = null;
|
||||||
|
LOG.log(WARNING,"Tried to patch field ''{0}'' of task, which is not implemented!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key != null) dirtyFields.add(key);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long parentTaskId(){
|
||||||
|
return parentTaskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long projectId(){
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showClosed(){
|
||||||
|
return showClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate start(){
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status status(){
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> toMap() {
|
public Map<String, Object> toMap() {
|
||||||
var map = new HashMap<String,Object>();
|
var map = new HashMap<String,Object>();
|
||||||
@@ -95,4 +197,5 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri
|
|||||||
public boolean hasMember(UmbrellaUser user) {
|
public boolean hasMember(UmbrellaUser user) {
|
||||||
return members.containsKey(user.id());
|
return members.containsKey(user.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,17 @@
|
|||||||
import { activeField } from './field_sync.svelte.js';
|
import { activeField } from './field_sync.svelte.js';
|
||||||
import { t } from '../translations.svelte.js';
|
import { t } from '../translations.svelte.js';
|
||||||
|
|
||||||
let { editable = false, value = $bindable(null), onSet = (newVal) => {return true;} } = $props();
|
let {
|
||||||
|
editable = false,
|
||||||
|
onclick = evt => {},
|
||||||
|
onSet = newVal => {return true;},
|
||||||
|
type = 'div',
|
||||||
|
value = $bindable(null)
|
||||||
|
} = $props();
|
||||||
let editing = $state(false);
|
let editing = $state(false);
|
||||||
|
|
||||||
let editValue = value;
|
let editValue = value;
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
async function applyEdit(){
|
async function applyEdit(){
|
||||||
let success = await onSet(editValue);
|
let success = await onSet(editValue);
|
||||||
@@ -28,6 +35,35 @@
|
|||||||
if (ev.keyCode == 27) resetEdit();
|
if (ev.keyCode == 27) resetEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function measured(evt,duration){
|
||||||
|
if (duration < 500){
|
||||||
|
onclick(evt);
|
||||||
|
} else {
|
||||||
|
startEdit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmousedown(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
start = evt.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmouseup(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
measured(evt, evt.timeStamp - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ontouchstart(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
start = evt.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ontouchend(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
measured(evt, evt.timeStamp - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
activeField.subscribe((val) => resetEdit());
|
activeField.subscribe((val) => resetEdit());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -48,5 +84,5 @@
|
|||||||
{#if editable && editing}
|
{#if editable && editing}
|
||||||
<input bind:value={editValue} onkeyup={typed} autofocus />
|
<input bind:value={editValue} onkeyup={typed} autofocus />
|
||||||
{:else}
|
{:else}
|
||||||
<div ondblclick={startEdit} class={{editable}} title={t('double_click_to_edit')} >{value}</div>
|
<svelte:element this={type} {onmousedown} {onmouseup} {ontouchstart} {ontouchend} class={{editable}} title={t('double_click_to_edit')} >{value}</svelte:element>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
import { t } from '../translations.svelte.js';
|
import { t } from '../translations.svelte.js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
import { api } from '../urls.svelte.js';
|
||||||
|
|
||||||
import TaskList from './TaskList.svelte';
|
import TaskList from './TaskList.svelte';
|
||||||
|
import LineEditor from './LineEditor.svelte';
|
||||||
const router = useTinyRouter();
|
const router = useTinyRouter();
|
||||||
let { estimated_time, show_closed, task } = $props();
|
let { estimated_time, show_closed, task } = $props();
|
||||||
let children = $state(null);
|
let children = $state(null);
|
||||||
let error = $state(null);
|
let error = $state(null);
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
async function loadChildren(){
|
async function loadChildren(){
|
||||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/list`;
|
const url = api('task/list');
|
||||||
var data = {
|
var data = {
|
||||||
parent_task_id:+task.id,
|
parent_task_id:+task.id,
|
||||||
show_closed: show_closed
|
show_closed: show_closed
|
||||||
@@ -30,11 +32,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openTask(evt){
|
function openTask(){
|
||||||
evt.preventDefault();
|
|
||||||
console.log('openTask(…)',evt,task);
|
|
||||||
router.navigate(`/task/${task.id}/view`);
|
router.navigate(`/task/${task.id}/view`);
|
||||||
//location.href = `https://umbrella.srsoftware.de/task/${task.id}/view`;
|
}
|
||||||
|
|
||||||
|
async function patchTask(newName){
|
||||||
|
console.log('patchTask('+newName+')');
|
||||||
|
const url = api(`task/${task.id}`);
|
||||||
|
const resp = await fetch(url,{
|
||||||
|
credentials:'include',
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({name:newName})
|
||||||
|
});
|
||||||
|
let ok = resp.ok;
|
||||||
|
console.log({ok:ok});
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.estimated_time){
|
if (task.estimated_time){
|
||||||
@@ -46,9 +58,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li class="task {task.status.name.toLowerCase()}">
|
<li class="task {task.status.name.toLowerCase()}">
|
||||||
<span class="name" onclick={openTask}>
|
<LineEditor bind:value={task.name} onclick={openTask} editable={true} onSet={patchTask} type="span" />
|
||||||
{task.name}
|
|
||||||
</span>
|
|
||||||
{#if task.estimated_time}
|
{#if task.estimated_time}
|
||||||
<span class="estimated_time">({+task.estimated_time} h)</span>
|
<span class="estimated_time">({+task.estimated_time} h)</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
let {
|
let {
|
||||||
editable = true,
|
editable = true,
|
||||||
|
onclick = evt => {},
|
||||||
|
onSet = newVal => {return true;},
|
||||||
simple = false,
|
simple = false,
|
||||||
value = $bindable({source:null,rendered:null}),
|
type = 'div',
|
||||||
onSet = (newVal) => {}
|
value = $bindable({source:null,rendered:null})
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let editing = $state(false);
|
let editing = $state(false);
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
let editValue = $state({source:value.source,rendered:value.rendered});
|
let editValue = $state({source:value.source,rendered:value.rendered});
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
async function applyEdit(){
|
async function applyEdit(){
|
||||||
let success = await onSet(editValue.source);
|
let success = await onSet(editValue.source);
|
||||||
@@ -55,6 +58,34 @@
|
|||||||
timer = setTimeout(render,500);
|
timer = setTimeout(render,500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function measured(evt,duration){
|
||||||
|
if (duration < 500){
|
||||||
|
onclick(evt);
|
||||||
|
} else {
|
||||||
|
startEdit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmousedown(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
start = evt.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmouseup(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
measured(evt, evt.timeStamp - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ontouchstart(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
start = evt.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ontouchend(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
measured(evt, evt.timeStamp - start);
|
||||||
|
}
|
||||||
|
|
||||||
activeField.subscribe((val) => resetEdit());
|
activeField.subscribe((val) => resetEdit());
|
||||||
if (simple) startEdit();
|
if (simple) startEdit();
|
||||||
</script>
|
</script>
|
||||||
@@ -76,4 +107,4 @@
|
|||||||
{#if editing}
|
{#if editing}
|
||||||
<textarea bind:value={editValue.source} onkeyup={typed} autofocus={!simple}></textarea>
|
<textarea bind:value={editValue.source} onkeyup={typed} autofocus={!simple}></textarea>
|
||||||
{/if}
|
{/if}
|
||||||
<div ondblclick={startEdit} class={{editable}} title={t('double_click_to_edit')} >{@html editValue.rendered}</div>
|
<svelte:element this={type} {onmousedown} {onmouseup} {ontouchstart} {ontouchend} class={{editable}} title={t('double_click_to_edit')} >{@html editValue.rendered}</svelte:element>
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
import { activeField } from './field_sync.svelte.js';
|
import { activeField } from './field_sync.svelte.js';
|
||||||
import { t } from '../translations.svelte.js';
|
import { t } from '../translations.svelte.js';
|
||||||
|
|
||||||
let { editable = false, value = $bindable(null), onSet = (newVal) => {} } = $props();
|
let {
|
||||||
|
editable = false,
|
||||||
|
onclick = evt => {},
|
||||||
|
onSet = newVal => {return true;},
|
||||||
|
type = 'div',
|
||||||
|
value = $bindable(null)
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let editing = $state(false);
|
let editing = $state(false);
|
||||||
|
|
||||||
let editValue = $state(value);
|
let editValue = $state(value);
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
async function applyEdit(){
|
async function applyEdit(){
|
||||||
let success = await onSet(editValue);
|
let success = await onSet(editValue);
|
||||||
@@ -29,7 +35,34 @@
|
|||||||
if (ev.keyCode == 13 && ev.ctrlKey) applyEdit();
|
if (ev.keyCode == 13 && ev.ctrlKey) applyEdit();
|
||||||
if (ev.keyCode == 27) resetEdit();
|
if (ev.keyCode == 27) resetEdit();
|
||||||
}
|
}
|
||||||
console.log(value);
|
|
||||||
|
function measured(evt,duration){
|
||||||
|
if (duration < 500){
|
||||||
|
onclick(evt);
|
||||||
|
} else {
|
||||||
|
startEdit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmousedown(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
start = evt.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmouseup(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
measured(evt, evt.timeStamp - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ontouchstart(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
start = evt.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ontouchend(evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
measured(evt, evt.timeStamp - start);
|
||||||
|
}
|
||||||
|
|
||||||
activeField.subscribe((val) => resetEdit());
|
activeField.subscribe((val) => resetEdit());
|
||||||
</script>
|
</script>
|
||||||
@@ -52,10 +85,10 @@
|
|||||||
<textarea bind:value={editValue} onkeyup={typed} autofocus></textarea>
|
<textarea bind:value={editValue} onkeyup={typed} autofocus></textarea>
|
||||||
{:else}
|
{:else}
|
||||||
{#if value}
|
{#if value}
|
||||||
<div ondblclick={startEdit} class={{editable}} title={t('double_click_to_edit')} >
|
<svelte:element this={type} {onmousedown} {onmouseup} {ontouchstart} {ontouchend} class={{editable}} title={t('double_click_to_edit')} >
|
||||||
{#each value.split("\n") as line}
|
{#each value.split("\n") as line}
|
||||||
{line}<br/>
|
{line}<br/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</svelte:element>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -216,7 +216,14 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
if (taskId == null) throw new UmbrellaException("Failed to save task {0}",task.name());
|
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());
|
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");
|
if (task.isDirty()) {
|
||||||
|
update(TABLE_TASKS).set(PROJECT_ID,PARENT_TASK_ID,NAME,DESCRIPTION,STATUS,EST_TIME,START_DATE,DUE_DATE,SHOW_CLOSED,NO_INDEX)
|
||||||
|
.where(ID,equal(task.id())).prepare(db)
|
||||||
|
.apply(task.projectId(),task.parentTaskId(),task.name(),task.description(),task.status().code(),task.estimatedTime(),task.start(),task.dueDate(),task.showClosed(),task.noIndex())
|
||||||
|
.close();
|
||||||
|
task.clean();
|
||||||
|
}
|
||||||
|
return task;
|
||||||
} catch (SQLException e){
|
} catch (SQLException e){
|
||||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to save task {0}",task.name());
|
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to save task {0}",task.name());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import static de.srsoftware.umbrella.core.Paths.*;
|
|||||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED;
|
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED;
|
||||||
import static de.srsoftware.umbrella.core.Util.mapValues;
|
import static de.srsoftware.umbrella.core.Util.mapValues;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||||
|
import static de.srsoftware.umbrella.core.model.Permission.READ_ONLY;
|
||||||
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
||||||
import static de.srsoftware.umbrella.task.Constants.*;
|
import static de.srsoftware.umbrella.task.Constants.*;
|
||||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||||
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
import de.srsoftware.configuration.Configuration;
|
||||||
@@ -75,6 +77,33 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doOptions(Path path, HttpExchange ex) throws IOException {
|
||||||
|
addCors(ex);
|
||||||
|
return sendEmptyResponse(HTTP_OK,ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doPatch(Path path, HttpExchange ex) throws IOException {
|
||||||
|
addCors(ex);
|
||||||
|
try {
|
||||||
|
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
||||||
|
var user = users.loadUser(token);
|
||||||
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
|
var head = path.pop();
|
||||||
|
return switch (head) {
|
||||||
|
case null -> super.doGet(path,ex);
|
||||||
|
default -> {
|
||||||
|
var taskId = Long.parseLong(head);
|
||||||
|
head = path.pop();
|
||||||
|
yield head == null ? patchTask(ex,taskId,user.get()) : super.doGet(path,ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (UmbrellaException e){
|
||||||
|
return send(ex,e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean doPost(Path path, HttpExchange ex) throws IOException {
|
public boolean doPost(Path path, HttpExchange ex) throws IOException {
|
||||||
addCors(ex);
|
addCors(ex);
|
||||||
@@ -173,6 +202,15 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
return mappedTask;
|
return mappedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean patchTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
||||||
|
var task = loadMembers(taskDb.load(taskId));
|
||||||
|
var member = task.members().get(user.id());
|
||||||
|
if (member == null || member.permission() == READ_ONLY ) throw forbidden("You are not a allowed to edit {0}!",task.name());
|
||||||
|
taskDb.save(task.patch(json(ex)));
|
||||||
|
|
||||||
|
return sendContent(ex,task);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean postNewTask(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean postNewTask(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
var json = json(ex);
|
var json = json(ex);
|
||||||
if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid)) throw missingFieldException(PROJECT_ID);
|
if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid)) throw missingFieldException(PROJECT_ID);
|
||||||
@@ -181,7 +219,7 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
var project = projects.load(projectId);
|
var project = projects.load(projectId);
|
||||||
projects.loadMembers(List.of(project));
|
projects.loadMembers(List.of(project));
|
||||||
var member = project.members().get(user.id());
|
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");
|
if (member == null || member.permission() == READ_ONLY) throw forbidden("You are not allowed to create new tasks in this project");
|
||||||
Task task = Task.of(json);
|
Task task = Task.of(json);
|
||||||
task = taskDb.save(task);
|
task = taskDb.save(task);
|
||||||
for (var key : memberData.keySet()){
|
for (var key : memberData.keySet()){
|
||||||
|
|||||||
Reference in New Issue
Block a user