Compare commits

..

10 Commits

Author SHA1 Message Date
StephanRichter ac126e9b80 bugfix: Task.tags failed when null was passed
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-12 23:37:44 +01:00
StephanRichter 3524bae1d8 improved diff for journal
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-12 22:40:38 +01:00
StephanRichter 214eb652e1 implemented some events from wiki module
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-12 09:15:54 +01:00
StephanRichter 9d35952949 implemented event-based page-refresh for wiki pages 2026-01-10 22:19:29 +01:00
StephanRichter c3b49cf032 Merge branch 'main' into module/journal 2026-01-10 21:22:40 +01:00
StephanRichter 10ea200a2e refactoring Events for better journal
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-10 01:10:24 +01:00
StephanRichter 0dd640de30 working on preparing journal loggin
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-09 13:02:53 +01:00
StephanRichter 81dc30359d working on journal module
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-09 08:47:20 +01:00
StephanRichter 6668e29923 Merge branch 'main' into module/journal 2026-01-09 08:23:46 +01:00
StephanRichter a4fffbe91b preparing journal module 2025-12-22 15:12:25 +01:00
37 changed files with 330 additions and 792 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
FROM alpine:3.22 AS svelte_build
RUN apk add npm
RUN apk add bash git npm
RUN adduser -Dh /home/svelte svelte
ADD . /home/svelte/Umbrella
RUN chown -R svelte /home/svelte/Umbrella
@@ -9,7 +9,7 @@ RUN npm install && npm run build
FROM alpine AS java_build
RUN apk add gradle fontconfig font-opensans openjdk21-jre
RUN apk add bash git gradle fontconfig font-opensans openjdk21-jre
ADD . /Umbrella
WORKDIR /Umbrella
COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web
+4 -1
View File
@@ -1,7 +1,10 @@
FROM alpine:3.22
LABEL Maintainer "Stephan Richter <s.richter@srsoftware.de>"
ARG UID=1000
ARG GID=1000
RUN apk add bash npm
RUN adduser -Dh /home/svelte svelte
RUN set -x; addgroup -g $GID svelte
RUN adduser -u $UID -G svelte -Dh /home/svelte svelte
ADD script /opt
USER svelte
WORKDIR /home/svelte
-2
View File
@@ -3,7 +3,6 @@ default: devel
build: image
podman run --name svelte-build \
--rm \
--userns=keep-id:uid=$$(id -u),gid=$$(id -g) \
-v ../frontend:/home/svelte/frontend \
-ti svelte /opt/svelte-build
image:
@@ -12,7 +11,6 @@ image:
devel: image
-podman rm -f svelte
podman run --name svelte \
--userns=keep-id:uid=$$(id -u),gid=$$(id -g) \
-v ../frontend:/home/svelte/frontend \
-p 5173:5173 \
-ti svelte /opt/svelte-init
+2
View File
@@ -18,6 +18,7 @@ dependencies{
implementation(project(":core"))
implementation(project(":documents"))
implementation(project(":files"))
implementation(project(":journal"))
implementation(project(":legacy"))
implementation(project(":markdown"))
implementation(project(":messages"))
@@ -52,6 +53,7 @@ tasks.jar {
":core:jar",
":documents:jar",
":files:jar",
":journal:jar",
":legacy:jar",
":markdown:jar",
":messages:jar",
@@ -16,6 +16,7 @@ import de.srsoftware.umbrella.core.Util;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.documents.DocumentApi;
import de.srsoftware.umbrella.files.FileModule;
import de.srsoftware.umbrella.journal.JournalModule;
import de.srsoftware.umbrella.legacy.*;
import de.srsoftware.umbrella.markdown.MarkdownApi;
import de.srsoftware.umbrella.message.MessageSystem;
@@ -64,6 +65,7 @@ public class Application {
var server = HttpServer.create(new InetSocketAddress(port), 0);
try {
new Translations().bindPath("/api/translations").on(server);
new JournalModule(config).bindPath("/api/journal").on(server);
new MessageApi().bindPath("/api/bus").on(server);
new MessageSystem(config);
new UserModule(config).bindPath("/api/user").on(server);
@@ -34,7 +34,7 @@ public class BookmarkApi extends BaseHandler implements BookmarkService {
super();
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
db = new SqliteDb(connect(dbFile));
ModuleRegistry.add(this);
ModuleRegistry.add(this);
}
@Override
+1 -1
View File
@@ -46,7 +46,7 @@ subprojects {
implementation("de.srsoftware:tools.mime:1.1.4")
implementation("de.srsoftware:tools.logging:1.3.2")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:2.0.4")
implementation("de.srsoftware:tools.util:2.1.1")
implementation("org.json:json:20240303")
}
@@ -4,5 +4,5 @@ package de.srsoftware.umbrella.messagebus;
import de.srsoftware.umbrella.messagebus.events.Event;
public interface EventListener {
void onEvent(Event event);
void onEvent(Event<?> event);
}
@@ -7,7 +7,7 @@ import de.srsoftware.umbrella.messagebus.events.Event;
import java.net.InetSocketAddress;
import java.util.LinkedList;
public class EventQueue extends LinkedList<Event> implements AutoCloseable, EventListener {
public class EventQueue extends LinkedList<Event<?>> implements AutoCloseable, EventListener {
private final InetSocketAddress addr;
@@ -29,7 +29,7 @@ public class EventQueue extends LinkedList<Event> implements AutoCloseable, Even
}
@Override
public void onEvent(Event event) {
public void onEvent(Event<?> event) {
System.getLogger(addr.toString()).log(System.Logger.Level.INFO,"adding event to queue of {1}: {0}",event.eventType(),addr);
add(event);
}
@@ -6,8 +6,8 @@ import java.util.HashSet;
import java.util.Set;
public class MessageBus {
private static MessageBus SINGLETON = new MessageBus();
private Set<EventListener> listeners = new HashSet<>();
private static final MessageBus SINGLETON = new MessageBus();
private final Set<EventListener> listeners = new HashSet<>();
private MessageBus(){}
@@ -1,39 +1,75 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.messagebus.events;
import static de.srsoftware.umbrella.core.Constants.USER;
import static de.srsoftware.umbrella.core.Constants.*;
import static java.util.Optional.*;
import de.srsoftware.tools.Diff;
import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.json.JSONObject;
public abstract class Event<Payload extends Mappable> {
public enum EventType {
CREATE,
UPDATE,
DELETE;
}
private UmbrellaUser initiator;
private String realm;
private Payload payload;
private EventType eventType;
public Event(UmbrellaUser initiator, String realm, Payload payload, EventType type){
private final UmbrellaUser initiator;
private final String module;
private final Payload payload;
private final EventType eventType;
private final Map<String, Object> oldData;
public Event(UmbrellaUser initiator, String module, Payload payload, EventType type){
this.initiator = initiator;
this.realm = realm;
this.module = module;
this.payload = payload;
this.eventType = type;
this.oldData = null;
}
public Event(UmbrellaUser initiator, String module, Payload payload, Map<String, Object> oldData){
this.initiator = initiator;
this.module = module;
this.payload = payload;
this.eventType = EventType.UPDATE;
this.oldData = oldData;
}
public abstract String describe();
private Map<String, Object> dropMarkdown(Map<String, Object> map) {
var result = new HashMap<String, Object>();
for (var entry : map.entrySet()){
var v = entry.getValue();
if (v instanceof Map<?,?> m && m.containsKey(RENDERED) && m.get(SOURCE) instanceof String s) v=s;
result.put(entry.getKey(),v);
}
return result;
}
public Optional<String> diff(){
return oldData == null ? empty() : of(Diff.MapDiff.diff(dropMarkdown(oldData),dropMarkdown(payload.toMap())));
}
public String eventType(){
return eventType.toString();
}
public abstract boolean isIntendedFor(UmbrellaUser user);
public UmbrellaUser initiator(){
return initiator;
}
public String json(){
Class<?> clazz = payload.getClass();
{ // get the highest superclass that is not object
@@ -48,6 +84,10 @@ public abstract class Event<Payload extends Mappable> {
return new JSONObject(map).toString();
}
public String module(){
return module;
}
public Payload payload(){
return payload;
}
@@ -5,6 +5,7 @@ import static de.srsoftware.umbrella.core.Constants.PROJECT;
import de.srsoftware.umbrella.core.model.Project;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.Map;
public class ProjectEvent extends Event<Project>{
@@ -12,6 +13,15 @@ public class ProjectEvent extends Event<Project>{
super(initiator, PROJECT, project, type);
}
public ProjectEvent(UmbrellaUser initiator, Project project, Map<String, Object> oldData){
super(initiator, PROJECT, project, oldData);
}
@Override
public String describe() {
return diff().orElse("[TODO: ProjectEvent.describe]");
}
@Override
public boolean isIntendedFor(UmbrellaUser user) {
for (var member : payload().members().values()){
@@ -5,6 +5,7 @@ import static de.srsoftware.umbrella.core.Constants.TASK;
import de.srsoftware.umbrella.core.model.Task;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.Map;
public class TaskEvent extends Event<Task>{
@@ -12,6 +13,15 @@ public class TaskEvent extends Event<Task>{
super(initiator, TASK, task, type);
}
public TaskEvent(UmbrellaUser initiator, Task task, Map<String, Object> oldData){
super(initiator, TASK, task, oldData);
}
@Override
public String describe() {
return diff().orElse("[TODO: TaskEvent.describe()]");
}
@Override
public boolean isIntendedFor(UmbrellaUser user) {
for (var member : payload().members().values()){
@@ -0,0 +1,32 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.messagebus.events;
import static de.srsoftware.umbrella.core.Constants.WIKI;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import de.srsoftware.umbrella.core.model.WikiPage;
import java.util.Map;
public class WikiEvent extends Event<WikiPage>{
public WikiEvent(UmbrellaUser initiator, WikiPage page, EventType type){
super(initiator, WIKI, page, type);
}
public WikiEvent(UmbrellaUser initiator, WikiPage page, Map<String, Object> oldData){
super(initiator, WIKI, page, oldData);
}
@Override
public String describe() {
return diff().orElse("[TODO: WikiEvent.describe()]");
}
@Override
public boolean isIntendedFor(UmbrellaUser user) {
for (var member : payload().members().values()){
if (member.user().equals(user)) return true;
}
return false;
}
}
@@ -75,5 +75,4 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
throw new RuntimeException(e);
}
}
}
@@ -8,10 +8,7 @@ public class Constants {
private Constants(){}
public static final String ACTION = "action";
public static final String ADDRESS = "address";
public static final String ALLOWED_STATES = "allowed_states";
public static final String ATTACHMENTS = "attachments";
@@ -163,4 +160,5 @@ public class Constants {
public static final String VERSION = "version";
public static final String VERSIONS = "versions";
public static final String WIKI = "wiki";
}
@@ -8,6 +8,7 @@ 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.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.text.MessageFormat.format;
public class UmbrellaException extends RuntimeException{
@@ -43,7 +44,12 @@ public class UmbrellaException extends RuntimeException{
return new UmbrellaException(HTTP_FORBIDDEN,message,fills);
}
public static UmbrellaException invalidFieldException(String field,String expected){
@Override
public String getMessage() {
return format(super.getMessage(),fills);
}
public static UmbrellaException invalidFieldException(String field, String expected){
return new UmbrellaException(HTTP_UNPROCESSABLE, ERROR_INVALID_FIELD, field, expected);
}
@@ -207,7 +207,7 @@ public final class Document implements Mappable {
case SENDER: if (json.get(key) instanceof JSONObject nested) sender.patch(nested); break;
case STATE: state = State.of(json.getInt(key)).orElseThrow(() -> new UmbrellaException(HTTP_UNPROCESSABLE,"Invalid state")); break;
case POS: if (json.get(key) instanceof JSONObject nested) positions.patch(nested); break;
case TEMPLATE: if (json.get(key) instanceof String templateId) template = templateId; break;
case TEMPLATE_ID: if (json.get(key) instanceof String templateId) template = templateId; break;
default: key = null;
}
if (key != null) dirtyFields.add(key);
@@ -27,7 +27,7 @@ public class Task implements Mappable {
private boolean noIndex, showClosed;
private final Map<Long, Member> members;
private final Set<String> dirtyFields = new HashSet<>();
private final Set<String> tags = new HashSet<>();
public Task (long id, long projectId, Long parentTaskId, String name, String description, int status, Double estimatedTime, LocalDate start, LocalDate dueDate, boolean showClosed, boolean noIndex, Map<Long,Member> members, int priority){
this.id = id;
@@ -218,6 +218,16 @@ public class Task implements Mappable {
return status;
}
public Task tags(Collection<String> newValue){
tags.clear();
if (newValue != null) tags.addAll(newValue);
return this;
}
public Set<String> tags(){
return tags;
}
@Override
public Map<String, Object> toMap() {
var map = new HashMap<String,Object>();
@@ -240,7 +250,7 @@ public class Task implements Mappable {
map.put(REQUIRED_TASKS_IDS,requiredTasksIds);
map.put(SHOW_CLOSED,showClosed);
map.put(TOTAL_PRIO,totalPrio());
map.put(TAGS,tags);
return map;
}
-675
View File
@@ -27,278 +27,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"cpu": [
@@ -314,142 +42,6 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"license": "MIT",
@@ -488,216 +80,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz",
"integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz",
"integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz",
"integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz",
"integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz",
"integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz",
"integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz",
"integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz",
"integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz",
"integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz",
"integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz",
"integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz",
"integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz",
"integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz",
"integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz",
"integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.44.1",
"cpu": [
@@ -722,48 +104,6 @@
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz",
"integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz",
"integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.44.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz",
"integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sveltejs/acorn-typescript": {
"version": "1.0.5",
"license": "MIT",
@@ -929,21 +269,6 @@
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/is-reference": {
"version": "3.0.3",
"license": "MIT",
+33 -47
View File
@@ -2,14 +2,13 @@
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte';
import { error, warn, yikes } from '../../warn.svelte';
import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte';
import { user } from '../../user.svelte';
const image_extensions = ['jpg','jpeg','gif','png','svg','webp'];
const router = useTinyRouter();
let children = $state({});
let new_dir = $state(null);
let files = $state();
let parent = $state(false);
@@ -18,6 +17,23 @@
let delete_allowed = $state(false);
let available = $derived.by(isAvailable);
function isAvailable(){
if (!new_dir) return false;
if (children){
if (children.dirs) {
for (let key of Object.values(children.dirs)){
if (key == new_dir) return false;
}
}
if (children.files) {
for (let key of Object.values(children.files)){
if (key == new_dir) return false;
}
}
}
return true;
}
async function create_dir(ev){
ev.preventDefault();
ev.stopPropagation();
@@ -58,30 +74,13 @@
async function handleDirectory(res){
let json = await res.json();
children.dirs = json.dirs ? val_sort(json.dirs) : {};
children.files = json.files ? val_sort(json.files) : {};
children.dirs = json.dirs ? json.dirs : {};
children.files = json.files ? json.files : {};
children.title = json.title ? json.title : path;
delete_allowed = json.delete;
yikes();
}
function isAvailable(){
if (!new_dir) return false;
if (children){
if (children.dirs) {
for (let key of Object.values(children.dirs)){
if (key == new_dir) return false;
}
}
if (children.files) {
for (let key of Object.values(children.files)){
if (key == new_dir) return false;
}
}
}
return true;
}
function is_image(file){
let parts = file.toLowerCase().split('.');
let ext = parts.pop();
@@ -91,15 +90,12 @@
async function loadChildren(p){
p = p.substring(6);
if (p == '') p = '/';
children = { dirs : [], files : [], title : p};
children = { dirs : {}, files : {}, title : p};
path = p;
if (p == '/'){
children.dirs = [
{ path : `/user/${user.id}`, name : t('my files') },
{ path : '/project', name : t('projects')},
{ path : '/company', name : t('companies')},
]
children.dirs[`/user/${user.id}`] = t('my_files');
children.dirs['/project'] = t('projects')
children.dirs['/company'] = t('companies');
parent = false;
form = false;
} else {
@@ -118,12 +114,8 @@
function markdown(file){
let parts = file.split('/');
let path = `/api/files${file}`;
path = encodeURI(path);
let md = `![${parts.pop()}](${path})`;
let md = `![${parts.pop()}](/api/files${file})`;
navigator.clipboard.writeText(md);
warn(t('Markdown has been copied to clipboard!'));
setTimeout(yikes, 2500);
}
function onclick(ev){
@@ -161,12 +153,6 @@
return false;
}
function val_sort(map){
return Object.entries(map)
.map(item => ({name:item[1],path:item[0]}))
.sort((a,b) => a.name.localeCompare(b.name));
}
onMount(() => loadChildren(window.location.pathname));
</script>
@@ -180,12 +166,12 @@
</li>
{/if}
{#if children?.dirs}
{#each children.dirs as dir}
{#each Object.entries(children.dirs) as [k,v]}
<li class="dir">
<span class="symbol"></span>
<a href={'/files'+dir.path} {onclick}>{dir.name}</a>
<a href={'/files'+k} {onclick}>{v}</a>
{#if delete_allowed}
<button class="symbol" onclick={e => dropDir(`/api/files${dir.path}`,dir.name)}></button>
<button class="symbol" onclick={e => dropDir(`/api/files${k}`,v)}></button>
{/if}
</li>
{/each}
@@ -200,15 +186,15 @@
</li>
{/if}
{#if children.files}
{#each children.files as file}
{#each Object.entries(children.files) as [k,v]}
<li class="file">
<span class="symbol"></span>
<a href={`/api/files${file.path}`} target="_blank">{file.name}</a>
{#if is_image(file.path)}
<button class="symbol" title={'markdown_code'} onclick={e => markdown(file.path)}></button>
<a href={`/api/files${k}`} target="_blank">{v}</a>
{#if is_image(k)}
<button class="symbol" title={'markdown_code'} onclick={e => markdown(k)}></button>
{/if}
{#if delete_allowed}
<button class="symbol" title={t('delete_object',{'object':t('file')})} onclick={e => dropFile(`/api/files${file.path}`,file.name)}></button>
<button class="symbol" title={t('delete_object',{'object':t('file')})} onclick={e => dropFile(`/api/files${k}`,v)}></button>
{/if}
</li>
{/each}
@@ -1,6 +1,4 @@
<script>
import Login from "../../Components/Login.svelte";
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte';
@@ -47,4 +45,3 @@
</div>
</div>
{/if}
<Login />
+21 -6
View File
@@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte';
import { api, eventStream } from '../../urls.svelte';
import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte';
import { user } from '../../user.svelte';
@@ -13,6 +13,7 @@
import TagList from '../tags/TagList.svelte';
let detail = $state(false);
let eventSource = null;
let { key, version } = $props();
let page = $state({});
let router = useTinyRouter();
@@ -27,6 +28,10 @@
return patch({members:newMembers});
}
function connectToBus(){
eventSource = eventStream(null,handleUpdateEvent,null);
}
async function dropMember(member){
var id = member.user.id;
let newMembers = JSON.parse(JSON.stringify(page.members));
@@ -51,6 +56,11 @@
}
}
function handleUpdateEvent(evt){
let json = JSON.parse(evt.data);
if (json.wikipage) loadContent(json.wikipage);
}
function nonMember(json){
return !page.members[json.id];
}
@@ -70,11 +80,15 @@
}
}
async function loadContent(res){
function loadContent(json){
json.versions.sort((a,b)=>b-a);
page = { ...json };
}
async function loadJson(res){
if (res.ok){
let json = await res.json();
json.versions.sort((a,b)=>b-a);
page = { ...json };
loadContent(json);
yikes();
return true;
} else {
@@ -88,7 +102,7 @@
if (version) path += `/version/${version}`;
const url = api(path);
const res = await fetch(url,{credentials:'include'});
loadContent(res);
loadJson(res);
}
function onclick(e){
@@ -105,7 +119,7 @@
method:'PATCH',
body:JSON.stringify(data)
});
return loadContent(res);
return loadJson(res);
}
async function patchGuestPermissions(ev){
@@ -130,6 +144,7 @@
}
$effect(loadPage);
onMount(connectToBus);
</script>
{#if page && page.versions}
<div class="wiki page">
+1 -1
View File
@@ -15,5 +15,5 @@ export async function warn(msg){
export function yikes(){
messages.error = null;
messages.warning = null;
messages.warn = null;
}
+7
View File
@@ -0,0 +1,7 @@
description = "Umbrella : Journal"
dependencies{
implementation(project(":bus"))
implementation(project(":core"))
}
@@ -0,0 +1,9 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal;
public class Constants {
public static final String CONFIG_DATABASE = "umbrella.modules.journal.database";
public static final String ERROR_WRITE_EVENT = "Failed to write {0} event of {1} to journal!";
public static final String TABLE_JOURNAL = "journal";
}
@@ -0,0 +1,8 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal;
import de.srsoftware.umbrella.messagebus.events.Event;
public interface JournalDb {
void logEvent(Event<?> event);
}
@@ -0,0 +1,34 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
import static de.srsoftware.umbrella.journal.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static java.lang.System.Logger.Level.DEBUG;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.messagebus.EventListener;
import de.srsoftware.umbrella.messagebus.events.Event;
public class JournalModule extends BaseHandler implements EventListener {
private final JournalDb journalDb;
public JournalModule(Configuration config){
super();
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
journalDb = new SqliteDb(connect(dbFile));
ModuleRegistry.add(this);
messageBus().register(this);
}
@Override
public void onEvent(Event<?> event) {
LOG.log(DEBUG,"{0} @ {1} (by {2})",event.eventType(),event.module(),event.initiator().name());
journalDb.logEvent(event);
}
}
@@ -0,0 +1,59 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal;
import static de.srsoftware.tools.jdbc.Query.insertInto;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.journal.Constants.ERROR_WRITE_EVENT;
import static de.srsoftware.umbrella.journal.Constants.TABLE_JOURNAL;
import static java.text.MessageFormat.format;
import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.messagebus.events.Event;
import java.sql.Connection;
import java.sql.SQLException;
public class SqliteDb extends BaseDb implements JournalDb{
public SqliteDb(Connection connection) {
super(connection);
}
protected int createTables() {
int currentVersion = createSettingsTable();
switch (currentVersion){
case 0:
createJournalTable();
}
return setCurrentVersion(1);
}
private void createJournalTable() {
var sql = """
CREATE TABLE IF NOT EXISTS {0} (
{1} INTEGER PRIMARY KEY,
{2} INTEGER,
{3} VARCHAR(255) NOT NULL,
{4} VARCHAR(16) NOT NULL,
{5} TEXT
);
""";
sql = format(sql,TABLE_JOURNAL,ID,USER_ID,MODULE,ACTION,DESCRIPTION);
try {
db.prepareStatement(sql).execute();
} catch (SQLException e) {
throw databaseException(ERROR_FAILED_CREATE_TABLE,TABLE_JOURNAL);
}
}
@Override
public void logEvent(Event<?> event) {
try {
insertInto(TABLE_JOURNAL,USER_ID,MODULE,ACTION,DESCRIPTION)
.values(event.initiator().id(), event.module(), event.eventType(), event.describe())
.execute(db).close();
} catch (SQLException e) {
throw databaseException(ERROR_WRITE_EVENT,event.eventType(),event.initiator().name());
}
}
}
@@ -14,7 +14,6 @@ import static de.srsoftware.umbrella.core.model.Status.OPEN;
import static de.srsoftware.umbrella.core.model.Status.PREDEFINED;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE;
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
import static java.lang.Boolean.TRUE;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
@@ -210,13 +209,14 @@ public class ProjectModule extends BaseHandler implements ProjectService {
private boolean patchProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException {
var project = loadMembers(projectDb.load(projectId));
if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
var old = project.toMap();
var json = json(ex);
if (json.has(DROP_MEMBER) && json.get(DROP_MEMBER) instanceof Number id) dropMember(project,id.longValue());
if (json.has(MEMBERS) && json.get(MEMBERS) instanceof JSONObject memberJson) patchMembers(project,memberJson);
if (json.has(NEW_MEMBER) && json.get(NEW_MEMBER) instanceof Number num) addMember(project,num.longValue());
project = projectDb.save(project.patch(json), user);
messageBus().dispatch(new ProjectEvent(user,project, UPDATE));
messageBus().dispatch(new ProjectEvent(user,project, old));
return sendContent(ex,project.toMap());
}
+2
View File
@@ -21,3 +21,5 @@ include("translations")
include("user")
include("web")
include("wiki")
include("journal")
@@ -13,7 +13,6 @@ import static de.srsoftware.umbrella.core.model.Permission.*;
import static de.srsoftware.umbrella.core.model.Permission.OWNER;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE;
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
import static de.srsoftware.umbrella.task.Constants.*;
import static java.lang.System.Logger.Level.WARNING;
@@ -42,22 +41,6 @@ import org.json.JSONObject;
public class TaskModule extends BaseHandler implements TaskService {
private static class TaggedTask extends Task{
private final Collection<String> tags;
public TaggedTask(Task task, Collection<String> tags) {
super(task.id(), task.projectId(), task.parentTaskId(), task.name(), task.description(), task.status(), task.estimatedTime(), task.start(), task.dueDate(), task.showClosed(), task.noIndex(), task.members(), task.priority());
this.tags = tags;
}
@Override
public Map<String, Object> toMap() {
var map = super.toMap();
map.put(TAGS,tags);
return map;
}
}
private final TaskDb taskDb;
public TaskModule(Configuration config) throws UmbrellaException {
@@ -335,6 +318,7 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean patchTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
var task = loadMembers(taskDb.load(taskId));
var old = task.toMap();
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());
var json = json(ex);
@@ -342,9 +326,8 @@ public class TaskModule extends BaseHandler implements TaskService {
if (json.has(MEMBERS) && json.get(MEMBERS) instanceof JSONObject memberJson) patchMembers(task, memberJson);
if (json.has(NEW_MEMBER) && json.get(NEW_MEMBER) instanceof Number num) addMember(task, num.longValue());
if (json.has(PARENT_TASK_ID) && json.get(PARENT_TASK_ID) instanceof Number ptid && newParentIsSubtask(task, ptid.longValue())) throw forbidden("Task must not be sub-task of itself.");
taskDb.save(task.patch(json));
var tagList = tagService().getTags(TASK, taskId, user);
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), UPDATE));
task = taskDb.save(task.patch(json)).tags(tagService().getTags(TASK, taskId, user));
messageBus().dispatch(new TaskEvent(user, task, old));
return sendContent(ex, task);
}
@@ -405,8 +388,8 @@ public class TaskModule extends BaseHandler implements TaskService {
if ((tagList == null || tagList.isEmpty())) tagList = tagService().getTags(PROJECT, projectId, user);
if (tagList != null && !tagList.isEmpty()) tagService().save(TASK, task.id(), null, tagList);
task = loadMembers(task);
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), CREATE));
task.tags(tagList);
messageBus().dispatch(new TaskEvent(user, task, CREATE));
return sendContent(ex, task);
}
@@ -447,6 +430,6 @@ public class TaskModule extends BaseHandler implements TaskService {
}
private Map<Long, Task> addTags(Map<Long, Task> taskList, Map<Long, ? extends Collection<String>> tags) {
return taskList.values().stream().map(task -> new TaggedTask(task, tags.get(task.id()))).collect(Collectors.toMap(Task::id, t -> t));
return taskList.values().stream().map(task -> task.tags(tags.get(task.id()))).collect(Collectors.toMap(Task::id, t -> t));
}
}
-2
View File
@@ -173,7 +173,6 @@
"logout_user": "{user} abmelden",
"markdown_code": "Markdown-Code",
"Markdown has been copied to clipboard!": "Markdown wurde in die Zwischenablage kopiert!",
"markdown_supported": "Markdown & <a target=\"_blank\" href=\"https://plantuml.com\">Plantuml</a> nutzbar!",
"MANAGE_LOGIN_SERVICES": "Login-Services verwalten",
"member": "Mitarbeiter",
@@ -206,7 +205,6 @@
"month": "Monat",
"move_to_top": "nach ganz oben bewegen",
"must_not_be_empty": "darf nicht leer sein",
"my files": "Meine Dateien",
"name": "Name",
"net_price": "Nettopreis",
-2
View File
@@ -173,7 +173,6 @@
"logout_user": "logout {user}",
"markdown_code": "Markdown-Code",
"Markdown has been copied to clipboard!": "Markdown has been copied to clipboard!",
"markdown_supported": "Markdown & <a target=\"_blank\" href=\"https://plantuml.com\">Plantuml</a> supported!",
"MANAGE_LOGIN_SERVICES": "manage login services",
"member": "member",
@@ -206,7 +205,6 @@
"month": "month",
"move_to_top": "move to top level",
"must_not_be_empty": "must not be empty",
"my files": "my files",
"name": "Name",
"net_price": "net price",
+2 -1
View File
@@ -9,4 +9,5 @@ tasks.processResources {
from("../frontend/dist") {
into("web")
}
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
+1
View File
@@ -1,5 +1,6 @@
description = "Umbrella : Wiki"
dependencies{
implementation(project(":bus"))
implementation(project(":core"))
}
@@ -10,6 +10,7 @@ import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Permission.EDIT;
import static de.srsoftware.umbrella.core.model.Permission.READ_ONLY;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.wiki.Constants.*;
import com.sun.net.httpserver.HttpExchange;
@@ -20,6 +21,7 @@ import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.api.WikiService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.messagebus.events.WikiEvent;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
@@ -190,10 +192,13 @@ public class WikiModule extends BaseHandler implements WikiService {
private boolean patchPage(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
var id = path.pop();
var page = loadPage(id, null);
var old = page.toMap();
var member = page.members().get(user.id());
if (member == null || member.permission() != EDIT) throw forbidden("You are not allowed to edit {0}!",id);
var json = json(ex);
return sendContent(ex,wikiDb.save(page.patch(json, userService())));
page = wikiDb.save(page.patch(json, userService()));
messageBus().dispatch(new WikiEvent(user,page,old));
return sendContent(ex,page);
}
private boolean postNewPage(String title, UmbrellaUser user, HttpExchange ex) throws IOException {