refactoring Events for better journal
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user