refactoring Events for better journal

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-01-10 01:10:24 +01:00
parent 0dd640de30
commit 10ea200a2e
9 changed files with 89 additions and 55 deletions

View File

@@ -1,35 +1,70 @@
/* © SRSoftware 2025 */ /* © SRSoftware 2025 */
package de.srsoftware.umbrella.messagebus.events; package de.srsoftware.umbrella.messagebus.events;
import static de.srsoftware.umbrella.core.Constants.USER; import static de.srsoftware.umbrella.core.Constants.*;
import static java.text.MessageFormat.format;
import static java.util.Optional.*;
import de.srsoftware.tools.Mappable; import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.json.JSONObject; import org.json.JSONObject;
public abstract class Event<Payload extends Mappable> { public abstract class Event<Payload extends Mappable> {
public enum EventType { public enum EventType {
CREATE, CREATE,
UPDATE, UPDATE,
DELETE; DELETE;
} }
private UmbrellaUser initiator;
private String module; private final UmbrellaUser initiator;
private Payload payload; private final String module;
private EventType eventType; private final Payload payload;
private final EventType eventType;
private final Map<String, Object> oldData;
public Event(UmbrellaUser initiator, String module, Payload payload, EventType type){ public Event(UmbrellaUser initiator, String module, Payload payload, EventType type){
this.initiator = initiator; this.initiator = initiator;
this.module = module; this.module = module;
this.payload = payload; this.payload = payload;
this.eventType = type; 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(); public abstract String describe();
private String diff(Map<String, Object> a, Map<String, Object> b){
// TODO: replace by better implementation
return format("{0}\n→\n{1}",dropMarkdown(a),dropMarkdown(b));
}
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(oldData,payload.toMap()));
}
public String eventType(){ public String eventType(){
return eventType.toString(); return eventType.toString();
} }

View File

@@ -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.Project;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.Map;
public class ProjectEvent extends Event<Project>{ public class ProjectEvent extends Event<Project>{
@@ -12,9 +13,13 @@ public class ProjectEvent extends Event<Project>{
super(initiator, PROJECT, project, type); super(initiator, PROJECT, project, type);
} }
public ProjectEvent(UmbrellaUser initiator, Project project, Map<String, Object> oldData){
super(initiator, PROJECT, project, oldData);
}
@Override @Override
public String describe() { public String describe() {
return "[TODO: ProjectEvent.describe]"; return diff().orElse("[TODO: ProjectEvent.describe]");
} }
@Override @Override

View File

@@ -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.Task;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.Map;
public class TaskEvent extends Event<Task>{ public class TaskEvent extends Event<Task>{
@@ -12,9 +13,13 @@ public class TaskEvent extends Event<Task>{
super(initiator, TASK, task, type); super(initiator, TASK, task, type);
} }
public TaskEvent(UmbrellaUser initiator, Task task, Map<String, Object> oldData){
super(initiator, TASK, task, oldData);
}
@Override @Override
public String describe() { public String describe() {
return "[TODO: TaskEvent.describe()]"; return diff().orElse("[TODO: TaskEvent.describe()]");
} }
@Override @Override

View File

@@ -27,7 +27,7 @@ public class Task implements Mappable {
private boolean noIndex, showClosed; private boolean noIndex, showClosed;
private final Map<Long, Member> members; private final Map<Long, Member> members;
private final Set<String> dirtyFields = new HashSet<>(); 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){ 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; this.id = id;
@@ -218,6 +218,16 @@ public class Task implements Mappable {
return status; return status;
} }
public Task tags(Collection<String> newValue){
tags.clear();
tags.addAll(newValue);
return this;
}
public Set<String> tags(){
return tags;
}
@Override @Override
public Map<String, Object> toMap() { public Map<String, Object> toMap() {
var map = new HashMap<String,Object>(); var map = new HashMap<String,Object>();
@@ -240,7 +250,7 @@ public class Task implements Mappable {
map.put(REQUIRED_TASKS_IDS,requiredTasksIds); map.put(REQUIRED_TASKS_IDS,requiredTasksIds);
map.put(SHOW_CLOSED,showClosed); map.put(SHOW_CLOSED,showClosed);
map.put(TOTAL_PRIO,totalPrio()); map.put(TOTAL_PRIO,totalPrio());
map.put(TAGS,tags);
return map; return map;
} }

View File

@@ -1,3 +1,4 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal; package de.srsoftware.umbrella.journal;
import de.srsoftware.umbrella.messagebus.events.Event; import de.srsoftware.umbrella.messagebus.events.Event;

View File

@@ -1,18 +1,17 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal; package de.srsoftware.umbrella.journal;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.model.Message;
import de.srsoftware.umbrella.messagebus.EventListener;
import de.srsoftware.umbrella.messagebus.MessageBus;
import de.srsoftware.umbrella.messagebus.events.Event;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
import static de.srsoftware.umbrella.journal.Constants.CONFIG_DATABASE; import static de.srsoftware.umbrella.journal.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static java.lang.System.Logger.Level.INFO; 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 { public class JournalModule extends BaseHandler implements EventListener {

View File

@@ -1,13 +1,6 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.journal; package de.srsoftware.umbrella.journal;
import de.srsoftware.tools.jdbc.Query;
import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.messagebus.events.Event;
import java.sql.Connection;
import java.sql.SQLException;
import static de.srsoftware.tools.jdbc.Query.insertInto; import static de.srsoftware.tools.jdbc.Query.insertInto;
import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
@@ -15,6 +8,11 @@ import static de.srsoftware.umbrella.journal.Constants.ERROR_WRITE_EVENT;
import static de.srsoftware.umbrella.journal.Constants.TABLE_JOURNAL; import static de.srsoftware.umbrella.journal.Constants.TABLE_JOURNAL;
import static java.text.MessageFormat.format; 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 class SqliteDb extends BaseDb implements JournalDb{
public SqliteDb(Connection connection) { public SqliteDb(Connection connection) {
super(connection); super(connection);
@@ -33,8 +31,8 @@ public class SqliteDb extends BaseDb implements JournalDb{
private void createJournalTable() { private void createJournalTable() {
var sql = """ var sql = """
CREATE TABLE IF NOT EXISTS {0} ( CREATE TABLE IF NOT EXISTS {0} (
{1} INT PRIMARY KEY, {1} INTEGER PRIMARY KEY,
{2} INT, {2} INTEGER,
{3} VARCHAR(255) NOT NULL, {3} VARCHAR(255) NOT NULL,
{4} VARCHAR(16) NOT NULL, {4} VARCHAR(16) NOT NULL,
{5} TEXT {5} TEXT

View File

@@ -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.core.model.Status.PREDEFINED;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; 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.CREATE;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE;
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE; import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; 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 { private boolean patchProject(HttpExchange ex, long projectId, UmbrellaUser user) throws IOException, UmbrellaException {
var project = loadMembers(projectDb.load(projectId)); var project = loadMembers(projectDb.load(projectId));
if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name()); if (!project.hasMember(user)) throw forbidden("You are not a member of {0}",project.name());
var old = project.toMap();
var json = json(ex); var json = json(ex);
if (json.has(DROP_MEMBER) && json.get(DROP_MEMBER) instanceof Number id) dropMember(project,id.longValue()); 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(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()); if (json.has(NEW_MEMBER) && json.get(NEW_MEMBER) instanceof Number num) addMember(project,num.longValue());
project = projectDb.save(project.patch(json), user); 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()); return sendContent(ex,project.toMap());
} }

View File

@@ -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.core.model.Permission.OWNER;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; 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.CREATE;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE;
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS; import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
import static de.srsoftware.umbrella.task.Constants.*; import static de.srsoftware.umbrella.task.Constants.*;
import static java.lang.System.Logger.Level.WARNING; import static java.lang.System.Logger.Level.WARNING;
@@ -42,22 +41,6 @@ import org.json.JSONObject;
public class TaskModule extends BaseHandler implements TaskService { 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; private final TaskDb taskDb;
public TaskModule(Configuration config) throws UmbrellaException { public TaskModule(Configuration config) throws UmbrellaException {
@@ -343,10 +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(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(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."); 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.");
var curr = taskDb.save(task.patch(json)).toMap(); task = taskDb.save(task.patch(json)).tags(tagService().getTags(TASK, taskId, user));
var diff = diff(old,curr); messageBus().dispatch(new TaskEvent(user, task, old));
var tagList = tagService().getTags(TASK, taskId, user);
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), UPDATE));
return sendContent(ex, task); return sendContent(ex, task);
} }
@@ -407,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())) tagList = tagService().getTags(PROJECT, projectId, user);
if (tagList != null && !tagList.isEmpty()) tagService().save(TASK, task.id(), null, tagList); if (tagList != null && !tagList.isEmpty()) tagService().save(TASK, task.id(), null, tagList);
task = loadMembers(task); task = loadMembers(task);
task.tags(tagList);
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), CREATE)); messageBus().dispatch(new TaskEvent(user, task, CREATE));
return sendContent(ex, task); return sendContent(ex, task);
} }
@@ -449,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) { 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));
} }
} }