From 5f3d112cdbc0c9a6358d516cfe56c28deb3276f2 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Thu, 10 Jul 2025 22:47:09 +0200 Subject: [PATCH] implemented display of document positions --- .../umbrella/backend/Application.java | 4 + .../srsoftware/umbrella/core/BaseHandler.java | 6 +- .../de/srsoftware/umbrella/core/Util.java | 49 +++++- .../core/exceptions/UmbrellaException.java | 9 +- .../umbrella/documents/Constants.java | 2 +- .../umbrella/documents/DocumentApi.java | 36 +++-- .../umbrella/documents/DocumentDb.java | 8 +- .../umbrella/documents/SqliteDb.java | 18 ++- .../umbrella/documents/model/Template.java | 10 +- frontend/src/routes/document/Position.svelte | 26 +++ .../src/routes/document/PositionList.svelte | 29 ++++ .../routes/document/TemplateSelector.svelte | 35 +++++ frontend/src/routes/document/View.svelte | 10 +- .../srsoftware/umbrella/user/UserModule.java | 148 ++++++++---------- 14 files changed, 263 insertions(+), 127 deletions(-) create mode 100644 frontend/src/routes/document/Position.svelte create mode 100644 frontend/src/routes/document/PositionList.svelte create mode 100644 frontend/src/routes/document/TemplateSelector.svelte diff --git a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java index c58a1b2..a463dea 100644 --- a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java +++ b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java @@ -9,6 +9,7 @@ import com.sun.net.httpserver.HttpServer; import de.srsoftware.configuration.JsonConfig; import de.srsoftware.tools.ColorLogger; import de.srsoftware.umbrella.company.CompanyModule; +import de.srsoftware.umbrella.core.Util; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.documents.DocumentApi; import de.srsoftware.umbrella.legacy.LegacyApi; @@ -17,6 +18,7 @@ import de.srsoftware.umbrella.message.MessageSystem; import de.srsoftware.umbrella.translations.Translations; import de.srsoftware.umbrella.user.UserModule; import de.srsoftware.umbrella.web.WebHandler; +import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.Executors; @@ -45,6 +47,8 @@ public class Application { var port = config.get("umbrella.http.port", 8080); var threads = config.get("umbrella.threads", 16); + config.get("umbrella.plantuml").map(Object::toString).map(File::new).filter(File::exists).ifPresent(Util::setPlantUmlJar); + var translationModule = new Translations(); var messageSystem = new MessageSystem(translationModule,config); var server = HttpServer.create(new InetSocketAddress(port), 0); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java b/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java index 3a6dd2b..851749a 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java @@ -32,10 +32,6 @@ public abstract class BaseHandler extends PathHandler { return ex; } - public boolean forbidden(HttpExchange ex) throws IOException { - return sendEmptyResponse(HTTP_FORBIDDEN,ex); - } - public record Page(String mime, byte[] bytes){} public boolean load(Path path, HttpExchange ex) throws IOException { @@ -73,7 +69,7 @@ public abstract class BaseHandler extends PathHandler { } public boolean unauthorized(HttpExchange ex) throws IOException { - return sendEmptyResponse(HTTP_FORBIDDEN,ex); + return sendEmptyResponse(HTTP_UNAUTHORIZED,ex); } public boolean notImplemented(HttpExchange ex,String message,Object clazz) throws IOException{ diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Util.java b/core/src/main/java/de/srsoftware/umbrella/core/Util.java index 1b93738..5bcb277 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Util.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Util.java @@ -8,18 +8,23 @@ import static java.lang.System.Logger.Level.*; import static java.lang.System.Logger.Level.WARNING; import static java.nio.charset.StandardCharsets.UTF_8; +import com.xrbpowered.jparsedown.JParsedown; import de.srsoftware.tools.Query; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +import java.io.*; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.util.Map; +import java.util.regex.Pattern; import org.json.JSONObject; public class Util { public static final System.Logger LOG = System.getLogger("Util"); + private static final Pattern UML_PATTERN = Pattern.compile("@start(\\w+)(.*)@end(\\1)",Pattern.DOTALL); + private static File plantumlJar = null; + private static final JParsedown MARKDOWN = new JParsedown(); + private Util(){} public static System.Logger.Level mapLogLevel(String lbl) { @@ -32,9 +37,38 @@ public class Util { }; } - public static String markdown(String code){ - LOG.log(ERROR,"{0}.markdown(…) not implemented",Util.class.getCanonicalName()); - return code; + public static String markdown(String source){ + try { + if (plantumlJar.exists()) { + var matcher = UML_PATTERN.matcher(source); + if (matcher.find()) { + var uml = matcher.group(0).trim(); + var start = matcher.start(0); + var end = matcher.end(0); + + ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", plantumlJar.getAbsolutePath(), "-tsvg", "-pipe"); + var ignored = processBuilder.redirectErrorStream(); + var process = processBuilder.start(); + try (OutputStream os = process.getOutputStream()) { + os.write(uml.getBytes(UTF_8)); + os.flush(); + } + + try (InputStream is = process.getInputStream()) { + byte[] out = is.readAllBytes(); + var svg = new String(out, UTF_8); + source = source.substring(0, start) + svg + source.substring(end); + } + + } + } + return MARKDOWN.text(source); + } catch (Exception e){ + if (LOG.isLoggable(TRACE)){ + LOG.log(TRACE,"Failed to render markdown, input was: \n{0}",source); + } else LOG.log(WARNING,"Failed to render markdown. Enable TRACE log level for details."); + return source; + } } public static HttpURLConnection open(URL url) throws IOException { @@ -101,4 +135,9 @@ public class Util { throw new UmbrellaException(500,"Request to {0} failed!",target).causedBy(e); } } + + public static void setPlantUmlJar(File file){ + LOG.log(INFO,"Using plantuml @ {0}",file.getAbsolutePath()); + plantumlJar = file; + } } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/exceptions/UmbrellaException.java b/core/src/main/java/de/srsoftware/umbrella/core/exceptions/UmbrellaException.java index e79e7ab..7fcefcf 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/exceptions/UmbrellaException.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/exceptions/UmbrellaException.java @@ -5,6 +5,8 @@ import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE; import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.WARNING; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.text.MessageFormat.format; @@ -25,10 +27,15 @@ public class UmbrellaException extends Exception{ return this; } - public static UmbrellaException databaseException(String message, Object fills) { + public static UmbrellaException databaseException(String message, Object... fills) { + System.getLogger("Configuration").log(WARNING,message,fills); return new UmbrellaException(HTTP_SERVER_ERROR,message,fills); } + public static UmbrellaException forbidden(String message, Object... fills) { + return new UmbrellaException(HTTP_FORBIDDEN,message,fills); + } + public static UmbrellaException invalidFieldException(String field,String expected){ return new UmbrellaException(HTTP_UNPROCESSABLE, ERROR_INVALID_FIELD, field, expected); } diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java b/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java index c11a5a5..4eff8e2 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/Constants.java @@ -90,5 +90,5 @@ public class Constants { public static final String TABLE_POSITIONS = "document_positions"; public static final String TABLE_PRICES = "customer_prices"; public static final String TABLE_TEMPLATES = "templates"; - + public static final String TEMPLATES = "templates"; } diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java index 2d58edd..8575897 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java @@ -6,6 +6,7 @@ import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Paths.LIST; import static de.srsoftware.umbrella.core.Util.request; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static de.srsoftware.umbrella.documents.Constants.*; import static de.srsoftware.umbrella.documents.model.Document.State.NEW; @@ -28,11 +29,9 @@ import de.srsoftware.umbrella.documents.model.*; import java.io.IOException; import java.time.LocalDate; import java.util.HashMap; -import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.json.JSONArray; import org.json.JSONObject; @@ -75,10 +74,7 @@ public class DocumentApi extends BaseHandler { private boolean deleteDocument(HttpExchange ex, long docId, UmbrellaUser user) throws IOException, UmbrellaException { var doc = db.loadDoc(docId); var companyId = doc.companyId(); - var members = companies.getMembers(companyId); - var isMember = false; - for (var member : members) isMember |= user.equals(member); - if (!isMember) throw new UmbrellaException(HTTP_FORBIDDEN,"You are mot a member of company {0}",doc.companyId()); + if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",doc.companyId()); if (doc.state() != NEW) throw new UmbrellaException(HTTP_BAD_REQUEST,"This document has already been sent"); return sendContent(ex,db.deleteDoc(docId)); } @@ -99,7 +95,8 @@ public class DocumentApi extends BaseHandler { case null -> super.doGet(path,ex); default -> { try { - yield getDocument(ex,Long.parseLong(head),user.get()); + var docId = Long.parseLong(head); + yield getDocument(ex,docId,user.get()); } catch (NumberFormatException ignored) {} yield super.doGet(path,ex); } @@ -123,7 +120,8 @@ public class DocumentApi extends BaseHandler { if (user.isEmpty()) return unauthorized(ex); var head = path.pop(); return switch (head){ - case LIST -> listDocuments(ex,user.get(),token.orElse(null)); + case LIST -> listCompaniesDocuments(ex,user.get(),token.orElse(null)); + case TEMPLATES -> postTemplateList(ex,user.get()); case null -> postDocument(ex,user.get()); default -> super.doPost(path,ex); }; @@ -145,6 +143,8 @@ public class DocumentApi extends BaseHandler { return sendContent(ex,map); } + + private boolean getDocTypes(HttpExchange ex) throws UmbrellaException, IOException { var types = db.listTypes(); var map = types.values().stream().collect(Collectors.toMap(Type::id, Type::name)); @@ -155,10 +155,7 @@ public class DocumentApi extends BaseHandler { var doc = db.loadDoc(docId); var companyId = doc.companyId(); var company = companies.get(companyId); - var members = companies.getMembers(companyId); - var isMember = false; - for (var member : members) isMember |= user.equals(member); - if (!isMember) return sendContent(ex,HTTP_FORBIDDEN,"You are mot a member of company "+doc.companyId()); + if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name()); var sep = company.decimalSeparator(); if (sep != null) doc.setDecimalSeparator(sep); doc.setCompanyName(company.name()); @@ -173,13 +170,13 @@ public class DocumentApi extends BaseHandler { return new JSONArray(s); } - private boolean listDocuments(HttpExchange ex, UmbrellaUser user, Token token) throws UmbrellaException { + private boolean listCompaniesDocuments(HttpExchange ex, UmbrellaUser user, Token token) throws UmbrellaException { try { var json = json(ex); if (!json.has(COMPANY)) throw missingFieldException(COMPANY); long companyId = json.getLong(COMPANY); var company = companies.get(companyId); - if (!companies.membership(companyId,user.id())) throw new UmbrellaException(HTTP_FORBIDDEN,"You are mot a member of company {0}",company); + if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company); var docs = db.listDocs(companyId); var map = new HashMap(); for (var entry : docs.entrySet()) map.put(entry.getKey(),entry.getValue().summary()); @@ -196,7 +193,7 @@ public class DocumentApi extends BaseHandler { if (!senderData.has(FIELD_COMPANY) || !(senderData.get(FIELD_COMPANY) instanceof Number companyId)) throw missingFieldException(FIELD_COMPANY); var company = companies.get(companyId.longValue()); - if (!companies.membership(companyId.longValue(),user.id())) throw new UmbrellaException(HTTP_FORBIDDEN,"You are mot a member of company {0}",company); + if (!companies.membership(companyId.longValue(),user.id())) throw forbidden("You are mot a member of company {0}",company); if (!json.has(FIELD_CUSTOMER) || !(json.get(FIELD_CUSTOMER) instanceof JSONObject customerData)) throw missingFieldException(FIELD_CUSTOMER); if (!json.has(FIELD_TYPE) || !(json.get(FIELD_TYPE) instanceof Number docTypeId)) throw missingFieldException(FIELD_TYPE); @@ -217,4 +214,13 @@ public class DocumentApi extends BaseHandler { db.step(companySettings); return sendContent(ex,saved.toMap()); } + + private boolean postTemplateList(HttpExchange ex, UmbrellaUser user) throws UmbrellaException, IOException { + var json = json(ex); + if (!(json.has(COMPANY) && json.get(COMPANY) instanceof Number companyId)) throw missingFieldException(COMPANY); + var company = companies.get(companyId.longValue()); + if (!companies.membership(companyId.longValue(),user.id())) throw forbidden("You are not a member of {0}",company.name()); + var templates = db.getCompanyTemplates(companyId.longValue()); + return sendContent(ex,templates.stream().map(Template::toMap)); + } } diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentDb.java b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentDb.java index d87fa08..259d25b 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentDb.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentDb.java @@ -3,10 +3,8 @@ package de.srsoftware.umbrella.documents; import de.srsoftware.tools.Pair; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import de.srsoftware.umbrella.documents.model.CompanySettings; -import de.srsoftware.umbrella.documents.model.CustomerSettings; -import de.srsoftware.umbrella.documents.model.Document; -import de.srsoftware.umbrella.documents.model.Type; +import de.srsoftware.umbrella.documents.model.*; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -52,4 +50,6 @@ public interface DocumentDb { void step(CompanySettings settings); Pair switchPositions(long docId, Pair longPair) throws UmbrellaException; + + Collection