Browse Source

improved handling of null values when mapping entities to json

kanban
Stephan Richter 3 months ago
parent
commit
3d81ddd3c5
  1. 8
      core/src/main/java/de/srsoftware/umbrella/core/Util.java
  2. 3
      core/src/main/java/de/srsoftware/umbrella/core/model/Project.java
  3. 5
      core/src/main/java/de/srsoftware/umbrella/core/model/Task.java
  4. 5
      core/src/main/java/de/srsoftware/umbrella/core/model/Time.java
  5. 5
      documents/src/main/java/de/srsoftware/umbrella/documents/model/Document.java
  6. 4
      documents/src/main/java/de/srsoftware/umbrella/documents/model/Position.java
  7. 56
      frontend/src/routes/project/Create.svelte
  8. 4
      frontend/src/routes/task/Add.svelte
  9. 6
      frontend/src/routes/task/View.svelte
  10. 27
      frontend/src/unused/ClickInput.svelte
  11. 34
      frontend/src/unused/ClickSelect.svelte
  12. 31
      frontend/src/unused/EditableField.svelte
  13. 3
      items/src/main/java/de/srsoftware/umbrella/items/Item.java
  14. 7
      project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java
  15. 7
      web/src/main/resources/web/css/default.css

8
core/src/main/java/de/srsoftware/umbrella/core/Util.java

@ -48,7 +48,15 @@ public class Util {
return result; return result;
} }
public static HashMap<String, Object> mapMarkdown(String source){
var map = new HashMap<String,Object>();
map.put(SOURCE,source);
map.put(RENDERED,markdown(source));
return map;
}
public static String markdown(String source){ public static String markdown(String source){
if (source == null) return source;
try { try {
if (plantumlJar.exists()) { if (plantumlJar.exists()) {
var matcher = UML_PATTERN.matcher(source); var matcher = UML_PATTERN.matcher(source);

3
core/src/main/java/de/srsoftware/umbrella/core/model/Project.java

@ -3,6 +3,7 @@ package de.srsoftware.umbrella.core.model;
import static de.srsoftware.tools.Optionals.nullable; 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.Util.mapMarkdown;
import static de.srsoftware.umbrella.core.Util.markdown; import static de.srsoftware.umbrella.core.Util.markdown;
import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Mappable;
@ -95,7 +96,7 @@ public class Project implements Mappable {
} }
map.put(ID,id); map.put(ID,id);
map.put(NAME,name); map.put(NAME,name);
map.put(DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description))); map.put(DESCRIPTION,mapMarkdown(description));
map.put(STATUS,Map.of(STATUS_CODE,status.code(), NAME,status.name())); map.put(STATUS,Map.of(STATUS_CODE,status.code(), NAME,status.name()));
map.put(COMPANY_ID,companyId); map.put(COMPANY_ID,companyId);
map.put(SHOW_CLOSED,showClosed); map.put(SHOW_CLOSED,showClosed);

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

@ -3,8 +3,7 @@ package de.srsoftware.umbrella.core.model;
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.*;
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 static java.lang.System.Logger.Level.WARNING;
@ -182,7 +181,7 @@ public class Task implements Mappable {
map.put(PROJECT_ID, projectId); map.put(PROJECT_ID, projectId);
map.put(PARENT_TASK_ID, parentTaskId); map.put(PARENT_TASK_ID, parentTaskId);
map.put(NAME, name); map.put(NAME, name);
map.put(DESCRIPTION, Map.of(SOURCE,description,RENDERED,markdown(description))); map.put(DESCRIPTION, mapMarkdown(description));
map.put(STATUS, Map.of(NAME,status.name(),STATUS_CODE,status.code())); map.put(STATUS, Map.of(NAME,status.name(),STATUS_CODE,status.code()));
map.put(ESTIMATED_TIME, estimatedTime); map.put(ESTIMATED_TIME, estimatedTime);
map.put(START_DATE,start); map.put(START_DATE,start);

5
core/src/main/java/de/srsoftware/umbrella/core/model/Time.java

@ -2,8 +2,7 @@
package de.srsoftware.umbrella.core.model; package de.srsoftware.umbrella.core.model;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Util.dateTimeOf; import static de.srsoftware.umbrella.core.Util.*;
import static de.srsoftware.umbrella.core.Util.markdown;
import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Mappable;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -103,7 +102,7 @@ public class Time implements Mappable{
map.put(ID,id); map.put(ID,id);
map.put(USER_ID,userId); map.put(USER_ID,userId);
map.put(SUBJECT,subject); map.put(SUBJECT,subject);
map.put(DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description))); map.put(DESCRIPTION,mapMarkdown(description));
map.put(START_TIME,start.toString().replace("T"," ")); map.put(START_TIME,start.toString().replace("T"," "));
map.put(END_TIME,end.toString().replace("T"," ")); map.put(END_TIME,end.toString().replace("T"," "));
map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name())); map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name()));

5
documents/src/main/java/de/srsoftware/umbrella/documents/model/Document.java

@ -3,6 +3,7 @@ package de.srsoftware.umbrella.documents.model;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE;
import static de.srsoftware.umbrella.core.Util.mapMarkdown;
import static de.srsoftware.umbrella.core.Util.markdown; import static de.srsoftware.umbrella.core.Util.markdown;
import static de.srsoftware.umbrella.documents.Constants.*; import static de.srsoftware.umbrella.documents.Constants.*;
import static de.srsoftware.umbrella.documents.Constants.FIELD_CUSTOMER; import static de.srsoftware.umbrella.documents.Constants.FIELD_CUSTOMER;
@ -218,8 +219,8 @@ public final class Document implements Mappable {
map.put(DATE, date); map.put(DATE, date);
map.put(STATE, state.code); map.put(STATE, state.code);
map.put(FIELD_DELIVERY, delivery == null ? "" : delivery); map.put(FIELD_DELIVERY, delivery == null ? "" : delivery);
map.put(FIELD_HEAD, Map.of(SOURCE,head,RENDERED,markdown(head))); map.put(FIELD_HEAD, mapMarkdown(head));
map.put(FIELD_FOOTER, Map.of(SOURCE,footer,RENDERED,markdown(footer))); map.put(FIELD_FOOTER, mapMarkdown(footer));
map.put(FIELD_CURRENCY, currency); map.put(FIELD_CURRENCY, currency);
map.put(SENDER, sender.toMap()); map.put(SENDER, sender.toMap());
map.put(FIELD_CUSTOMER, customer.toMap()); map.put(FIELD_CUSTOMER, customer.toMap());

4
documents/src/main/java/de/srsoftware/umbrella/documents/model/Position.java

@ -3,6 +3,7 @@ package de.srsoftware.umbrella.documents.model;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Util.mapMarkdown;
import static de.srsoftware.umbrella.core.Util.markdown; import static de.srsoftware.umbrella.core.Util.markdown;
import static de.srsoftware.umbrella.documents.Constants.*; import static de.srsoftware.umbrella.documents.Constants.*;
@ -188,7 +189,6 @@ public final class Position implements Mappable {
} }
public Map<String, Object> renderToMap() { public Map<String, Object> renderToMap() {
var descr = Map.of(SOURCE,description,RENDERED,markdown(description));
var map = new HashMap<String, Object>(); var map = new HashMap<String, Object>();
map.put(FIELD_DOCUMENT_ID, docId); map.put(FIELD_DOCUMENT_ID, docId);
map.put(NUMBER, num); map.put(NUMBER, num);
@ -196,7 +196,7 @@ public final class Position implements Mappable {
map.put(FIELD_AMOUNT, amount); map.put(FIELD_AMOUNT, amount);
map.put(FIELD_UNIT, unit); map.put(FIELD_UNIT, unit);
map.put(TITLE, title); map.put(TITLE, title);
map.put(DESCRIPTION, descr); map.put(DESCRIPTION, mapMarkdown(description));
map.put(FIELD_UNIT_PRICE, unitPrice); map.put(FIELD_UNIT_PRICE, unitPrice);
map.put(FIELD_TAX, tax); map.put(FIELD_TAX, tax);
map.put(FIELD_TIME_ID, timeId); map.put(FIELD_TIME_ID, timeId);

56
frontend/src/routes/project/Create.svelte

@ -3,6 +3,7 @@
import { t } from '../../translations.svelte.js'; import { t } from '../../translations.svelte.js';
import CompanySelector from '../../Components/CompanySelector.svelte'; import CompanySelector from '../../Components/CompanySelector.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import Settings from './Settings.svelte'; import Settings from './Settings.svelte';
let showSettings = $state(false); let showSettings = $state(false);
let ready = $derived(!!project.name.trim()) let ready = $derived(!!project.name.trim())
@ -11,7 +12,7 @@
let project = $state({ let project = $state({
name:'', name:'',
description:'', description : { source : '', rendered : '' },
settings:{ settings:{
show_closed:false show_closed:false
} }
@ -53,21 +54,44 @@
</legend> </legend>
<fieldset> <fieldset>
<legend>{t('basic_data')}</legend> <legend>{t('basic_data')}</legend>
<label> <table>
<CompanySelector caption={t('no_company')} {onselect} /> <tbody>
{t('company_optional')} <tr>
</label> <th>
<label> {t('company_optional')}
<input type="text" bind:value={project.name}/> </th>
{t('Name')} <td>
</label> <CompanySelector caption={t('no_company')} {onselect} />
<label> </td>
<textarea bind:value={project.description}></textarea> </tr>
{t('description')} <tr>
</label> <th>
{#if !showSettings} {t('Name')}
<button onclick={() => showSettings = true}>{t('extended_settings')}</button> </th>
{/if} <td>
<input type="text" bind:value={project.name}/>
</td>
</tr>
<tr>
<th>
{t('description')}
</th>
<td>
<MarkdownEditor bind:value={project.description} simple={true} />
</td>
</tr>
{#if !showSettings}
<tr>
<th>
{t('settings')}
</th>
<td>
<button onclick={() => showSettings = true}>{t('extended_settings')}</button>
</td>
</tr>
{/if}
</tbody>
</table>
</fieldset> </fieldset>
{#if showSettings} {#if showSettings}
<Settings bind:settings={project.settings}/> <Settings bind:settings={project.settings}/>

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

@ -84,7 +84,9 @@
}); });
if (resp.ok) { if (resp.ok) {
task = await resp.json(); task = await resp.json();
router.navigate(`/task/${task.id}/view`); if (task.parent_task_id){
router.navigate(`/task/${task.parent_task_id}/view`);
} else router.navigate(`/task/${task.id}/view`);
error = null; error = null;
} else { } else {
error = await resp.text(); error = await resp.text();

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

@ -175,9 +175,11 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th>{t('subtasks')}</th> <th>
<td class="children"> {t('subtasks')}
<button onclick={addChild} >{t('add_subtask')}</button> <button onclick={addChild} >{t('add_subtask')}</button>
</th>
<td class="children">
{#if children} {#if children}
<TaskList tasks={children} {estimated_time} /> <TaskList tasks={children} {estimated_time} />
{/if} {/if}

27
frontend/src/unused/ClickInput.svelte

@ -1,27 +0,0 @@
<script>
import { t } from '../translations.svelte.js';
import { checkUser } from '../user.svelte.js';
let { key, onUpdate, value } = $props();
let input = $state(false);
function edit(){
input = true;
}
function check_key(evt){
if (evt.key === 'Enter'){
input = false;
let obj = {};
obj[key] = value;
onUpdate(obj);
}
}
</script>
{#if input}
<input type="text" bind:value onkeyup={check_key} />
{:else}
<span onclick={edit}>{value}</span>
{/if}

34
frontend/src/unused/ClickSelect.svelte

@ -1,34 +0,0 @@
<script>
import { t } from '../translations.svelte.js';
import { checkUser } from '../user.svelte.js';
let { fetchOptions, key, value, onUpdate } = $props();
let options = $state([]);
async function loadOptions(){
const resp = await fetchOptions();
const arr = await resp.json();
for (let entry of arr){
const value = entry.value;
const caption = entry.caption ? entry.caption : value;
options.push({caption:caption,value:value})
}
}
function propagate(){
let changeset = {}
changeset[key] = value;
onUpdate(changeset);
}
</script>
{#if options.length > 0}
<select bind:value onchange={propagate} >
{#each options as entry,i}
<option value={entry.value}>{entry.caption}</option>
{/each}
</select>
{:else}
<span onclick={loadOptions}>{value}</span>
{/if}

31
frontend/src/unused/EditableField.svelte

@ -1,31 +0,0 @@
<script>
import { t } from '../../translations.svelte.js';
import { checkUser } from '../../user.svelte.js';
let { key, onUpdate, value } = $props();
let input = $state(false);
function edit(){
input = true;
}
function check_key(evt){
if (evt.key === 'Enter'){
input = false;
let obj = {};
obj[key] = value;
onUpdate(obj);
}
}
</script>
<tr>
<th>{t(key)}</th>
{#if input}
<td >
<input type="text" bind:value onkeyup={check_key} />
</td>
{:else}
<td onclick={edit}>{value}</td>
{/if}
</tr>

3
items/src/main/java/de/srsoftware/umbrella/items/Item.java

@ -3,6 +3,7 @@ package de.srsoftware.umbrella.items;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Constants.CODE; import static de.srsoftware.umbrella.core.Constants.CODE;
import static de.srsoftware.umbrella.core.Util.mapMarkdown;
import static de.srsoftware.umbrella.core.Util.markdown; import static de.srsoftware.umbrella.core.Util.markdown;
import static de.srsoftware.umbrella.items.Constants.*; import static de.srsoftware.umbrella.items.Constants.*;
@ -31,7 +32,7 @@ public record Item(long id, long companyId, String code, String name, String des
ID,id, ID,id,
COMPANY_ID,companyId, COMPANY_ID,companyId,
CODE,code,NAME,name, CODE,code,NAME,name,
DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description)), DESCRIPTION,mapMarkdown(description),
UNIT,unit, UNIT,unit,
UNIT_PRICE,unitPrice, UNIT_PRICE,unitPrice,
TAX,tax); TAX,tax);

7
project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java

@ -216,7 +216,12 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean postProject(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException { private boolean postProject(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
var json = json(ex); var json = json(ex);
if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingFieldException(NAME); if (!(json.has(NAME) && json.get(NAME) instanceof String name)) throw missingFieldException(NAME);
var description = json.has(DESCRIPTION) && json.get(DESCRIPTION) instanceof String d ? d : null; String description = null;
if (json.has(DESCRIPTION)){
var desc = json.get(DESCRIPTION);
if (desc instanceof String d) description = d;
if (desc instanceof JSONObject nested && nested.has(SOURCE) && nested.get(SOURCE) instanceof String d) description = d;
}
Long companyId = null; Long companyId = null;
if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number number){ if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number number){
if (!companies.membership(number.longValue(), user.id())) throw forbidden("You are not a member of company {0}!",number); if (!companies.membership(number.longValue(), user.id())) throw forbidden("You are not a member of company {0}!",number);

7
web/src/main/resources/web/css/default.css

@ -7,6 +7,7 @@ a {
color: orange; color: orange;
} }
body { body {
font-family: sans;
background: black; background: black;
color: orange; color: orange;
margin: 0; margin: 0;
@ -113,4 +114,10 @@ td, tr{
padding: 10px; padding: 10px;
border: 1px solid orange; border: 1px solid orange;
border-radius: 5px; border-radius: 5px;
}
.project .name,
.task .name{
font-size: 32px;
font-weight: bold;
} }
Loading…
Cancel
Save