Compare commits

..

3 Commits

17 changed files with 63 additions and 851 deletions

View File

@@ -1,15 +1,15 @@
FROM alpine:3.22 AS svelte_build FROM alpine:3.22 AS svelte_build
RUN apk add npm RUN apk add npm
RUN adduser -Dh /home/svelte svelte RUN adduser -Dh /home/svelte svelte
ADD frontend /home/svelte/Umbrella/frontend ADD . /home/svelte/Umbrella
RUN chown -R svelte /home/svelte/Umbrella RUN chown -R svelte /home/svelte/Umbrella
USER svelte USER svelte
WORKDIR /home/svelte/Umbrella/frontend WORKDIR /home/svelte/Umbrella/frontend
RUN npm install && npm run build RUN npm install && npm run build
FROM alpine:3.22 AS java_build FROM alpine AS java_build
RUN apk add gradle RUN apk add gradle fontconfig font-opensans openjdk21-jre
ADD . /Umbrella ADD . /Umbrella
WORKDIR /Umbrella WORKDIR /Umbrella
COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web
@@ -17,18 +17,14 @@ RUN gradle --no-daemon build
FROM alpine FROM alpine
RUN apk --no-cache add bash fontconfig font-opensans graphviz openjdk21-jre tzdata weasyprint \ RUN apk add bash fontconfig font-opensans graphviz openjdk21-jre weasyprint
&& adduser -D umbrella RUN adduser -D umbrella
COPY --from=java_build /Umbrella/backend/build/libs/backend.jar /home/umbrella/jar/
RUN chown -R umbrella /home/umbrella
ADD https://github.com/plantuml/plantuml/releases/download/v1.2025.10/plantuml-1.2025.10.jar /home/umbrella/plantuml.jar
WORKDIR /home/umbrella WORKDIR /home/umbrella
RUN chmod a+rx plantuml.jar
USER umbrella
RUN mkdir .config && ln -s /host/config.json .config/Umbrella.json
EXPOSE 80 EXPOSE 80
CMD java -jar jar/backend.jar CMD java -jar jar/backend.jar
ADD https://github.com/plantuml/plantuml/releases/download/v1.2025.10/plantuml-1.2025.10.jar /home/umbrella/plantuml.jar
COPY --from=java_build /Umbrella/backend/build/libs/backend.jar /home/umbrella/jar/
RUN mkdir .config \
&& ln -s /host/config.json .config/Umbrella.json \
&& chmod a+rx plantuml.jar \
&& chown -R umbrella . \
&& ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
USER umbrella

View File

@@ -1,5 +1,5 @@
FROM alpine:3.22 FROM alpine:3.22
LABEL Maintainer "Stephan Richter" LABEL Maintainer "Stephan Richter <s.richter@srsoftware.de>"
ARG UID=1000 ARG UID=1000
ARG GID=1000 ARG GID=1000
RUN apk add bash npm RUN apk add bash npm

View File

@@ -541,11 +541,6 @@ public class DocumentApi extends BaseHandler implements DocumentService {
var userCompanyIds = companyService().listCompaniesOf(user).keySet(); var userCompanyIds = companyService().listCompaniesOf(user).keySet();
var documents = db.find(userCompanyIds,keys,fulltext); var documents = db.find(userCompanyIds,keys,fulltext);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return sendContent(ex,mapValues(documents)); return sendContent(ex,mapValues(documents));
} }

View File

@@ -222,7 +222,7 @@
</div> </div>
<div>{t('state')}</div> <div>{t('state')}</div>
<div> <div>
<StateSelector selected={project.status} onchange={val => update({status:+val})} {project} /> <StateSelector selected={project.status} onchange={val => update({status:val})} {project} />
</div> </div>
{#if project.company} {#if project.company}
<div>{t('company')}</div> <div>{t('company')}</div>

View File

@@ -1,15 +1,14 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router'; import { useTinyRouter } from 'svelte-tiny-router';
import { api, get, post, target } from '../../urls.svelte.js'; import { api, post, target } from '../../urls.svelte.js';
import { error, warn, yikes } from '../../warn.svelte'; import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte.js'; import { t } from '../../translations.svelte.js';
import { display } from '../../time.svelte'; import { display } from '../../time.svelte';
import Bookmark from '../bookmark/Template.svelte'; import Bookmark from '../bookmark/Template.svelte';
const router = useTinyRouter(); const router = useTinyRouter();
let counter = 9;
let bookmarks = $state(null); let bookmarks = $state(null);
let companies = $state(null); let companies = $state(null);
let documents = $state(null); let documents = $state(null);
@@ -35,12 +34,12 @@
}); });
function doSearch(ignored){ function doSearch(ignored){
warn(t('searching…'));
let url = window.location.origin + window.location.pathname; let url = window.location.origin + window.location.pathname;
if (key) url += '?key=' + encodeURI(key); if (key) url += '?key=' + encodeURI(key);
window.history.replaceState(history.state, '', url); window.history.replaceState(history.state, '', url);
const data = { key : key, fulltext : fulltext }; const data = { key : key, fulltext : fulltext };
post(api('bookmark/search'),data).then(handleBookmarks); post(api('bookmark/search'),data).then(handleBookmarks);
post(api('company/search '),data).then(handleCompanies); post(api('company/search '),data).then(handleCompanies);
post(api('document/search'),data).then(handleDocuments); post(api('document/search'),data).then(handleDocuments);
@@ -52,22 +51,6 @@
post(api('wiki/search' ),data).then(handleWikiPages); post(api('wiki/search' ),data).then(handleWikiPages);
} }
async function getTitle(key,module,entity_id){
get(api(module+'/'+entity_id)).then(res => setTitle(res,key,module))
}
async function setTitle(resp,key,module){
if (resp.ok){
const json = await resp.json();
if (json.name) notes[key].title = t(module)+": "+json.name;
if (json.title) notes[key].title = t(module)+": "+json.title;
if (module == 'document'){
notes[key].title = t(json.type)+" "+json.number;
}
}
}
function onclick(e){ function onclick(e){
e.preventDefault(); e.preventDefault();
var target = e.target; var target = e.target;
@@ -78,7 +61,6 @@
} }
async function handleBookmarks(resp){ async function handleBookmarks(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
bookmarks = Object.keys(res).length ? res : null; bookmarks = Object.keys(res).length ? res : null;
@@ -88,7 +70,6 @@
} }
async function handleCompanies(resp){ async function handleCompanies(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const json = await resp.json(); const json = await resp.json();
companies = Object.keys(json).length ? json : null; companies = Object.keys(json).length ? json : null;
@@ -98,7 +79,6 @@
} }
async function handleDocuments(resp){ async function handleDocuments(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const json = await resp.json(); const json = await resp.json();
documents = Object.keys(json).length ? json : null; documents = Object.keys(json).length ? json : null;
@@ -108,25 +88,15 @@
} }
async function handleNotes(resp){ async function handleNotes(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const json = await resp.json(); const json = await resp.json();
if ( Object.keys(json).length ) { notes = Object.keys(json).length ? json : null;
for (let key of Object.keys(json)){
let module = json[key].module;
let entity_id = json[key].entity_id;
json[key].title = t(module)+' '+entity_id;
getTitle(key,module,entity_id);
}
notes = json;
} else notes = null;
} else { } else {
error(resp); error(resp);
} }
} }
async function handleProjects(resp){ async function handleProjects(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
projects = Object.keys(res).length ? res : null; projects = Object.keys(res).length ? res : null;
@@ -136,7 +106,6 @@
} }
async function handleStock(resp){ async function handleStock(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
stock = Object.keys(res).length ? res : null; stock = Object.keys(res).length ? res : null;
@@ -146,7 +115,6 @@
} }
async function handleTasks(resp){ async function handleTasks(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
tasks = Object.keys(res).length ? res : null; tasks = Object.keys(res).length ? res : null;
@@ -156,7 +124,6 @@
} }
async function handleTimes(resp){ async function handleTimes(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
times = Object.keys(res).length ? res : null; times = Object.keys(res).length ? res : null;
@@ -166,7 +133,6 @@
} }
async function handleWikiPages(resp){ async function handleWikiPages(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
pages = Object.keys(res).length ? res : null; pages = Object.keys(res).length ? res : null;
@@ -175,13 +141,6 @@
} }
} }
function quitOne(){
counter--;
if (counter > 0) {
warn(t('searching…')+" "+counter);
} else yikes();
}
$effect(() => doSearch(key)) $effect(() => doSearch(key))
</script> </script>
@@ -277,6 +236,23 @@
</ul> </ul>
</fieldset> </fieldset>
{/if} {/if}
{#if notes}
<fieldset>
<legend>
{t('notes')}
</legend>
<ul>
{#each Object.values(notes) as note}
<li>
<b>
<a href="/{note.module}/{note.entity_id}/view" {onclick} >{t(note.module)} {note.entity_id}:</a>
</b>
{@html target(note.text.rendered)}
</li>
{/each}
</ul>
</fieldset>
{/if}
{#if times} {#if times}
<fieldset> <fieldset>
<legend> <legend>
@@ -332,20 +308,3 @@
</ul> </ul>
</fieldset> </fieldset>
{/if} {/if}
{#if notes}
<fieldset>
<legend>
{t('notes')}
</legend>
<ul>
{#each Object.values(notes) as note}
<li>
<b>
<a href="/{note.module}/{note.entity_id}/view" {onclick} >{note.title}</a>
</b>
{@html target(note.text.rendered)}
</li>
{/each}
</ul>
</fieldset>
{/if}

View File

@@ -1,84 +0,0 @@
body{
color: orange;
background-color: black;
}
a{
color: orange;
}
.tasks .pending>a{
color: gray;
}
.completed>a,
.started>a{
color: #5bc500;
}
.canceled>a{
color: gray;
}
tr{
background-color: #52525270;
}
tr:nth-child(2n+1){
background-color: #a7a7a740;
}
fieldset {
border-color: orange;
}
.button,
input,
button {
background-color: orange;
color: #303030;
}
.hover{
background-color: black;
border-color: orange;
}
form.invoice textarea {
background-color: orange;
color: black;
}
.infos span{
background-color: #00ad00;
color: black;
}
.errors span{
background-color: #ff9847;
color: black;
}
.warnings span{
background-color: #ffff00;
color: black;
}
#announce{
background-color: black;
}
#main_menu .button:hover {
color: orange;
background-color: gray;
}
tr:hover td{
background-color: #160202;
}
code {
background-color: black;
color: orange;
}
.description img,
#preview img{
background: lightcyan;
}

View File

@@ -1,584 +0,0 @@
@font-face {
font-family: "awesome";
src: url("../fontawesome-webfont.woff");
}
*{
max-width:100%;
min-width:auto;
}
a{
text-decoration: none;
}
body{
font-family: sans-serif;
}
code {
display: inline-block;
padding: 5px;
margin: 5px 0;
}
textarea {
min-height: 60px;
width: 100%;
font-size: 16px;
}
textarea:hover{
min-height: 400px;
width: 100%;
}
input[type=text],input[type=email]{
width: 100%;
}
td.connectors form input{
width: 200px;
}
img#logo{
position: fixed;
top: 10px;
right: 10px;
z-index: -1;
}
blockquote{
font-style: italic;
}
#main_menu,
#main_menu form{
display: inline;
}
#main_menu .button {
font-size: 12px;
font-weight: normal;
margin-top: 7px;
vertical-align: bottom;
}
.hidden{
display: none !important;
}
.hover:hover .hidden{
display: inherit !important;
}
.emphasized{
font-weight: bold;
}
.add_positions,
.requirements{
max-height: 60px;
max-width: 66%;
overflow: hidden;
}
.add_positions:hover,
.requirements:hover{
max-height: 999999px;
max-width: 999999px;
}
.add_positions > ul > li{
max-height: 20px;
overflow: hidden;
padding-top: 20px;
}
.add_positions > ul > li:hover{
max-height: 999999px;
}
fieldset.add.document:hover {
max-height: unset;
}
fieldset.add.document {
max-height: 5px;
overflow: hidden;
}
fieldset.options label,
.add_positions label span,
.poll_status label,
.requirements label{
display: block;
}
.tasks .canceled a,
.children .inactive,
.requirements .inactive,
.tasks .inactive{
text-decoration: line-through;
}
.tasks .navi a{
display: inline-table;
}
blockquote a,
p a,
.description a{
text-decoration: underline;
}
table {
border-collapse: collapse;
}
tr > *{
padding: 5px 10px;
}
label.street:after,
label.location::after {
content: "\a ";
white-space: pre;
}
.poll.evaluate table tr:nth-child(n+2) th,
.poll.evaluate table td{
text-align: right;
}
.poll .disabled{
text-decoration: line-through;
}
.company div > fieldset,
.company > table{
margin-right: 10px;
float: left;
}
table.time h2{
margin-right: 60px;
}
.file label,
.document .dates label{
display: block
}
.document .header,
.document .tags{
clear: both;
}
fieldset {
border-radius: 10px;
border-width: 1px;
}
.contacts fieldset,
.document .customer,
.document .document_type,
.document .sender,
.document .court,
.document .dates,
.document .template,
fieldset.del_note,
fieldset.login_service_list,
fieldset.userlist,
fieldset.document.list{
float: left;
}
.document .dates label{
text-align: right;
}
.button,
input,
button {
border: 0 none;
font-weight: bold;
margin: 0 2px;
padding: 3px;
display: inline-block;
}
.change_state,
td.connectors button[type="submit"],
.prop_action,
input[type="submit"] {
clear: both;
float: right;
}
.symbol{
font-family: awesome;
font-size: 20px;
font-weight: normal;
}
#main_menu .symbol{
font-size: 14px;
}
.right{
float: right;
margin: 0 0 0 5px;
}
.tasks .project {
font-weight: bold;
}
.right-abs {
position: absolute;
right: 5px;
top: 5px;
}
.right-fix {
position: fixed;
right: 5px;
top: 5px;
}
.hover {
border-width: 1px;
border-style: solid;
border-radius: 5px;
max-height: 35px;
overflow: hidden;
z-index: 10;
}
.hover:hover {
max-height: unset;
}
.hover_h > a:nth-child(n+2){
display: none;
}
.hover_h:hover {
padding: 5px 0 20px;
}
.hover_h:hover > a:nth-child(n+2){
display: inherit;
}
form.document textarea {
font-weight: bold;
min-height: 90px;
width: 100%;
}
.pos_price,
form.document input.price,
form.document input.amount{
max-width: 60px;
text-align: right;
}
form.document .tax{
min-width: 70px;
}
form.document .tax input{
max-width: 40px;
text-align: right;
}
td > h1{
display: inline-block;
margin: 0 10px 0 0;
}
.center,
.infos,
.errors,
.warnings{
text-align: center;
}
.infos span,
.errors span,
.warnings span{
margin: 5px 0 0;
padding: 5px;
border-radius: 10px;
display: inline-block;
}
.tags span{
display: inline-block;
vertical-align: top;
}
.bookmark .share,
.bookmark .tags{
display: inline-block;
}
.bookmark form .share{
height: 20px;
overflow: hidden;
}
.bookmark form .share:hover{
height: initial;
}
fieldset.bookmark > fieldset{
max-height: 14px;
overflow: hidden;
}
fieldset.bookmark > fieldset.tags{
max-height: unset;
}
fieldset.bookmark > fieldset:hover{
max-height: unset;
}
.bookmark>fieldset>a{
word-wrap: break-word;
display: block;
min-width: auto;
}
.bookmark>fieldset>a.button{
display: inline-block;
}
.copytext{
position:fixed;
width: 100px;
left: -1000px;
}
img[src*="pos=right"] {
float:right;
margin: 5px 0 5px 5px;
}
img[src*="pos=left"] {
float:left;
margin: 5px 5px 5px 0;
}
img[src*="width=100"]{
max-width: 100px;
}
img[src*="width=200"]{
max-width: 200px;
}
img[src*="width=300"]{
max-width: 300px;
}
img[src*="width=400"]{
max-width: 400px;
}
img[src*="width=500"]{
max-width: 500px;
}
img[src*="width=600"]{
max-width: 600px;
}
img[src*="width=700"]{
max-width: 700px;
}
img[src*="width=800"]{
max-width: 800px;
}
img[src*="width=900"]{
max-width: 900px;
}
img[src*="width=50%"]{
max-width: 50%;
}
img[src*="width=33%"]{
max-width: 33%;
}
img[src$="width=25%"]{
max-width: 25%;
}
#announce{
position: fixed;
bottom: 0;
right: 0;
padding: 3px 5px 0;
border-top-left-radius: 7px;
}
fieldset.scrolling{
overflow: scroll;
max-height: 80%;
}
svg#gantt{
max-width: unset;
}
svg .row{
fill: none;
pointer-events: all;
}
svg .row:hover{
fill: #333333;
}
svg .duration{
fill:rgba(255,0,255,0.4);
stroke:none;
}
svg .schedule{
fill:none;
stroke-width:1;
stroke: yellow;
}
svg .start{
stroke-dasharray: 10,30,40;
}
svg .stop{
stroke-dasharray: 50,30;
}
svg text{
stroke: none;
fill: red;
}
div.search{
display: inline-block;
max-width: 150px;
}
div.search input,
div.search label{
display: none;
}
div.search:hover input,
div.search:hover label{
display: initial;
}
.note td.code {
width: 50%;
}
.note td.code textarea {
min-height: 200px;
}
.project-index td .users{
max-height: 30px;
overflow: hidden;
}
.project-index tr:hover td .users{
max-height: none;
}
.easylist fieldset a.button {
width: 80%;
padding: 30px;
text-align: center;
}
#preview,
#preview-source{
display: inline-block;
max-width: calc(50% - 20px);
vertical-align: bottom;
}
table #preview,
table #preview-source{
display: inherit;
max-width: unset;
vertical-align: inherit;
}
#preview.loading{
opacity: 0.2;
}
.completed > a::before {
content: " ✓";
}
@media (max-width:1199px) {
*[hide="12"]{
display: none;
}
table.document input{
max-width: 100px;
}
}
@media (max-width: 900px){
*[hide="9"]{
display: none;
}
table.document input{
max-width: 60px;
}
table.document .tax input,
table.document input.amount{
max-width: 25px;
}
}
@media (max-width: 800px){
*[hide="8"]{
display: none;
}
}
@media (max-width: 700px){
*[hide="7"]{
display: none;
}
fieldset.bookmark > fieldset{
max-height: 50px;
}
#preview,
#preview-source{
display: block;
max-width: unset;
}
}
@media (max-width: 600px){
*[hide="6"]{
display: none;
}
.easylist fieldset a.button {
width: 60%;
}
}
@media (max-width: 500px){
*[hide="5"]{
display: none;
}
}
@media (max-width: 400px){
*[hide="4"]{
display: none;
}
}
@media print{
fieldset.note span.right,
#logo, #main_menu {
display: none;
}
fieldset.process, fieldset.database{
page-break-after: always;
}
.export>fieldset{
border: 0 none;
}
}

View File

@@ -1,41 +0,0 @@
.arrow{
stroke: orange;
}
circle, ellipse, rect{
stroke: black;
}
ellipse,
circle.process{
fill: rgba(255,255,255,0.5);
}
rect.terminal{
fill: white;
}
circle.connector{
fill: rgba(0,0,0,0.05);
stroke: none;
}
circle.connector:hover{
fill: rgba(0,0,0,0.6);
}
#backdrop{
fill: transparent;
}
.arrow circle{
stroke: none;
fill: rgba(128,128,128,0.5);
}
.arrow text{
fill: black;
stroke: none;
}
.arrow:hover circle{
fill: rgba(255,0,0,0.3);
}
.process text{
fill: black;
}

View File

@@ -5,6 +5,7 @@ public class Constants {
public static final String AUTH = "mail.smtp.auth"; public static final String AUTH = "mail.smtp.auth";
public static final String CONFIG_DB = "umbrella.modules.message.database"; public static final String CONFIG_DB = "umbrella.modules.message.database";
public static final String CONFIG_SMTP_FROM = "umbrella.modules.message.smtp.from";
public static final String CONFIG_SMTP_HOST = "umbrella.modules.message.smtp.host"; public static final String CONFIG_SMTP_HOST = "umbrella.modules.message.smtp.host";
public static final String CONFIG_SMTP_PASS = "umbrella.modules.message.smtp.pass"; public static final String CONFIG_SMTP_PASS = "umbrella.modules.message.smtp.pass";
public static final String CONFIG_SMTP_PORT = "umbrella.modules.message.smtp.port"; public static final String CONFIG_SMTP_PORT = "umbrella.modules.message.smtp.port";

View File

@@ -3,6 +3,7 @@ package de.srsoftware.umbrella.message;
import static de.srsoftware.tools.PathHandler.CONTENT_TYPE; import static de.srsoftware.tools.PathHandler.CONTENT_TYPE;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.translator;
import static de.srsoftware.umbrella.core.constants.Constants.UTF8; import static de.srsoftware.umbrella.core.constants.Constants.UTF8;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingConfig; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingConfig;
import static de.srsoftware.umbrella.message.Constants.*; import static de.srsoftware.umbrella.message.Constants.*;
@@ -12,6 +13,7 @@ import de.srsoftware.configuration.Configuration;
import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.api.PostBox; import de.srsoftware.umbrella.core.api.PostBox;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.EmailAddress;
import de.srsoftware.umbrella.core.model.Envelope; import de.srsoftware.umbrella.core.model.Envelope;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import de.srsoftware.umbrella.core.model.User; import de.srsoftware.umbrella.core.model.User;
@@ -76,10 +78,10 @@ public class MessageSystem implements PostBox {
db = new SqliteMessageDb(connect(dbFile)); db = new SqliteMessageDb(connect(dbFile));
debugAddress = config.get(DEBUG_ADDREESS).map(Object::toString).orElse(null); debugAddress = config.get(DEBUG_ADDREESS).map(Object::toString).orElse(null);
port = config.get(CONFIG_SMTP_PORT,587); port = config.get(CONFIG_SMTP_PORT,587);
host = config.get(CONFIG_SMTP_HOST).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.host not configured!")); host = config.get(CONFIG_SMTP_HOST).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_HOST));
user = config.get(CONFIG_SMTP_USER).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.user not configured!")); user = config.get(CONFIG_SMTP_USER).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_USER));
pass = config.get(CONFIG_SMTP_PASS).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.pass not configured!")); pass = config.get(CONFIG_SMTP_PASS).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_PASS));
from = user; from = config.get(CONFIG_SMTP_FROM).map(Object::toString).orElseThrow(() -> missingConfig(CONFIG_SMTP_FROM));
ModuleRegistry.add(this); ModuleRegistry.add(this);
new SubmissionTask(8).schedule(); new SubmissionTask(8).schedule();
new SubmissionTask(10).schedule(); new SubmissionTask(10).schedule();
@@ -116,9 +118,9 @@ public class MessageSystem implements PostBox {
var date = new Date(); var date = new Date();
for (var receiver : dueRecipients){ for (var receiver : dueRecipients){
BiFunction<String,Map<String,String>,String> translateFunction = (text,fills) -> ModuleRegistry.translator().translate(receiver.language(),text,fills); BiFunction<String,Map<String,String>,String> translateFunction = (text,fills) -> translator().translate(receiver.language(),text,fills);
var fallbackSender = new User("Umbrella",new EmailAddress(from),null);
var combined = new CombinedMessage("Collected messages",translateFunction); var combined = new CombinedMessage("Collected messages",translateFunction,fallbackSender);
var envelopes = queue.stream().filter(env -> env.isFor(receiver)).toList(); var envelopes = queue.stream().filter(env -> env.isFor(receiver)).toList();
for (var envelope : envelopes) combined.merge(envelope.message()); for (var envelope : envelopes) combined.merge(envelope.message());

View File

@@ -5,9 +5,7 @@ import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.TRACE; import static java.lang.System.Logger.Level.TRACE;
import static java.text.MessageFormat.format; import static java.text.MessageFormat.format;
import de.srsoftware.umbrella.core.model.Attachment; import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.core.model.Message;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.*; import java.util.*;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@@ -16,15 +14,17 @@ public class CombinedMessage {
private final Set<Attachment> attachments = new HashSet<>(); private final Set<Attachment> attachments = new HashSet<>();
private final StringBuilder combinedBody = new StringBuilder(); private final StringBuilder combinedBody = new StringBuilder();
private final User fallbackSender;
private String combinedSubject = null; private String combinedSubject = null;
private final List<Message> mergedMessages = new ArrayList<>(); private final List<Message> mergedMessages = new ArrayList<>();
private final String subjectForCombinedMessage; private final String subjectForCombinedMessage;
private final BiFunction<String,Map<String,String>,String> translate; private final BiFunction<String,Map<String,String>,String> translate;
private UmbrellaUser sender = null; private User sender = null;
public CombinedMessage(String subjectForCombinedMessage, BiFunction<String, Map<String,String>,String> translateFunction){ public CombinedMessage(String subjectForCombinedMessage, BiFunction<String, Map<String,String>,String> translateFunction, User fallbackSender){
LOG.log(DEBUG,"Creating combined message…"); LOG.log(DEBUG,"Creating combined message…");
this.subjectForCombinedMessage = subjectForCombinedMessage; this.subjectForCombinedMessage = translateFunction.apply(subjectForCombinedMessage,null);
this.fallbackSender = fallbackSender;
translate = translateFunction; translate = translateFunction;
} }
@@ -39,12 +39,12 @@ public class CombinedMessage {
combinedSubject = subject; combinedSubject = subject;
break; break;
case 1: case 1:
combinedBody.insert(0,format("# {0}:\n# {1}:\n\n",sender,subject)); // insert sender and subject of first message right before the body of the first message if (!sender.equals(message.sender())) sender = fallbackSender;
combinedBody.insert(0,format("# {0} / {1}:\n\n",sender,subject)); // insert sender and subject of first message right before the body of the first message
combinedSubject = subjectForCombinedMessage; combinedSubject = subjectForCombinedMessage;
// no break here, we need to append the subject and content // no break here, we need to append the subject and content
default: default:
combinedBody.append("\n\n# ").append(message.sender()).append(":\n"); combinedBody.append("\n-----\n# ").append(message.sender()).append(" / ").append(subject).append(":\n\n");
combinedBody.append("# ").append(subject).append(":\n\n");
combinedBody.append(body); combinedBody.append(body);
} }
if (message.attachments() != null) attachments.addAll(message.attachments()); if (message.attachments() != null) attachments.addAll(message.attachments());
@@ -59,7 +59,7 @@ public class CombinedMessage {
return combinedBody.toString(); return combinedBody.toString();
} }
public UmbrellaUser sender() { public User sender() {
return sender; return sender;
} }

View File

@@ -132,23 +132,7 @@ public class StockModule extends BaseHandler implements StockService {
yield super.doGet(path,ex); yield super.doGet(path,ex);
} }
} }
case null -> super.doGet(path,ex); case null, default -> super.doGet(path,ex);
default -> {
try {
var id = Long.parseLong(head);
Item item = stockDb.loadItem(id);
Owner owner = item.owner().resolve();
if (owner instanceof Company company) {
if (!companyService().membership(company.id(),user.get().id())) throw forbidden("You are not allowed to access {0}",OBJECT);
}
if (owner instanceof UmbrellaUser u){
if (u.id() != user.get().id()) throw forbidden("You are not allowed to access {0}",OBJECT);
}
yield sendContent(ex,item);
} catch (NumberFormatException nfe){
yield super.doGet(path,ex);
}
}
}; };
} catch (UmbrellaException e){ } catch (UmbrellaException e){
return send(ex,e); return send(ex,e);

View File

@@ -39,7 +39,6 @@ public class SqliteDb extends BaseDb implements TagDB{
public SqliteDb(Connection tagDb, Connection bmDb) { public SqliteDb(Connection tagDb, Connection bmDb) {
super(tagDb); super(tagDb);
bookmarks = new de.srsoftware.umbrella.bookmarks.SqliteDb(bmDb); bookmarks = new de.srsoftware.umbrella.bookmarks.SqliteDb(bmDb);
createTables();
} }
@Override @Override

View File

@@ -278,7 +278,6 @@
"saved": "gespeichert", "saved": "gespeichert",
"save_object": "{object} speichern", "save_object": "{object} speichern",
"search": "Suche", "search": "Suche",
"searching…": "suche…",
"select_company" : "Wählen Sie eine ihrer Firmen:", "select_company" : "Wählen Sie eine ihrer Firmen:",
"select_customer": "Kunde auswählen", "select_customer": "Kunde auswählen",
"select_property": "Eigenschaft auswählen", "select_property": "Eigenschaft auswählen",

View File

@@ -278,7 +278,6 @@
"saved": "saved", "saved": "saved",
"save_object": "save {object}", "save_object": "save {object}",
"search": "search", "search": "search",
"searching…": "searhcing…",
"select_company" : "select on of you companies:", "select_company" : "select on of you companies:",
"select_customer": "select customer", "select_customer": "select customer",
"select_property": "select property", "select_property": "select property",

View File

@@ -9,5 +9,4 @@ tasks.processResources {
from("../frontend/dist") { from("../frontend/dist") {
into("web") into("web")
} }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }

View File

@@ -152,27 +152,15 @@ public class SqliteDb extends BaseDb implements WikiDb {
@Override @Override
public Map<Long, WikiPage> find(long userId, List<String> keys, boolean fulltext) { public Map<Long, WikiPage> find(long userId, List<String> keys, boolean fulltext) {
try { try {
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID,TABLE_PAGES_USERS,PAGE_ID).where(USER_ID,equal(userId));
for (var key : keys) query.where(TITLE,like("%"+key+"%"));
var rs = query.exec(db);
var map = new HashMap<Long,WikiPage>(); var map = new HashMap<Long,WikiPage>();
{ while (rs.next()) {
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID, TABLE_PAGES_USERS, PAGE_ID).where(USER_ID, equal(userId)); var page = WikiPage.of(rs);
for (var key : keys) query.where(TITLE, like("%" + key.replaceAll("[ÄäÖöÜüß]", "%") + "%")); map.put(page.id(),page);
var rs = query.exec(db);
while (rs.next()) {
var page = WikiPage.of(rs);
map.put(page.id(), page);
}
rs.close();
}
if (fulltext) {
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID, TABLE_PAGES_USERS, PAGE_ID).where(USER_ID, equal(userId));
for (var key : keys) query.where(CONTENT, like("%" + key.replaceAll("[ÄäÖöÜüß]", "%") + "%"));
var rs = query.exec(db);
while (rs.next()) {
var page = WikiPage.of(rs);
map.put(page.id(), page);
}
rs.close();
} }
rs.close();
return map; return map;
} catch (SQLException e) { } catch (SQLException e) {
throw failedToSearchDb(t(WIKI_PAGES)).causedBy(e); throw failedToSearchDb(t(WIKI_PAGES)).causedBy(e);