Compare commits
10 Commits
73994d3a4e
...
module/jou
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dd640de30 | |||
| 81dc30359d | |||
| 6668e29923 | |||
| 1187956625 | |||
| fe0068f5ed | |||
| e92a4bedb9 | |||
| 11d14afb00 | |||
| 77e546bd4b | |||
| beb58bbc36 | |||
| a4fffbe91b |
@@ -41,6 +41,11 @@ jobs:
|
|||||||
docker push ${{ secrets.REGISTRY_PATH }}/umbrella:${{ gitea.ref_name }}
|
docker push ${{ secrets.REGISTRY_PATH }}/umbrella:${{ gitea.ref_name }}
|
||||||
docker push ${{ secrets.REGISTRY_PATH }}/umbrella:$TAG
|
docker push ${{ secrets.REGISTRY_PATH }}/umbrella:$TAG
|
||||||
|
|
||||||
|
- name: Restart vj.srsoftware.de
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: |
|
||||||
|
curl -X POST -H "Authorization: Bearer ${{ secrets.MAKE_BEARER }}" -d vj_start https://make.srsoftware.de/launch
|
||||||
|
|
||||||
Clean-Registry:
|
Clean-Registry:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ dependencies{
|
|||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
implementation(project(":documents"))
|
implementation(project(":documents"))
|
||||||
implementation(project(":files"))
|
implementation(project(":files"))
|
||||||
|
implementation(project(":journal"))
|
||||||
implementation(project(":legacy"))
|
implementation(project(":legacy"))
|
||||||
implementation(project(":markdown"))
|
implementation(project(":markdown"))
|
||||||
implementation(project(":messages"))
|
implementation(project(":messages"))
|
||||||
@@ -52,6 +53,7 @@ tasks.jar {
|
|||||||
":core:jar",
|
":core:jar",
|
||||||
":documents:jar",
|
":documents:jar",
|
||||||
":files:jar",
|
":files:jar",
|
||||||
|
":journal:jar",
|
||||||
":legacy:jar",
|
":legacy:jar",
|
||||||
":markdown:jar",
|
":markdown:jar",
|
||||||
":messages:jar",
|
":messages:jar",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import de.srsoftware.umbrella.core.Util;
|
|||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.documents.DocumentApi;
|
import de.srsoftware.umbrella.documents.DocumentApi;
|
||||||
import de.srsoftware.umbrella.files.FileModule;
|
import de.srsoftware.umbrella.files.FileModule;
|
||||||
|
import de.srsoftware.umbrella.journal.JournalModule;
|
||||||
import de.srsoftware.umbrella.legacy.*;
|
import de.srsoftware.umbrella.legacy.*;
|
||||||
import de.srsoftware.umbrella.markdown.MarkdownApi;
|
import de.srsoftware.umbrella.markdown.MarkdownApi;
|
||||||
import de.srsoftware.umbrella.message.MessageSystem;
|
import de.srsoftware.umbrella.message.MessageSystem;
|
||||||
@@ -64,6 +65,7 @@ public class Application {
|
|||||||
var server = HttpServer.create(new InetSocketAddress(port), 0);
|
var server = HttpServer.create(new InetSocketAddress(port), 0);
|
||||||
try {
|
try {
|
||||||
new Translations().bindPath("/api/translations").on(server);
|
new Translations().bindPath("/api/translations").on(server);
|
||||||
|
new JournalModule(config).bindPath("/api/journal").on(server);
|
||||||
new MessageApi().bindPath("/api/bus").on(server);
|
new MessageApi().bindPath("/api/bus").on(server);
|
||||||
new MessageSystem(config);
|
new MessageSystem(config);
|
||||||
new UserModule(config).bindPath("/api/user").on(server);
|
new UserModule(config).bindPath("/api/user").on(server);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class BookmarkApi extends BaseHandler implements BookmarkService {
|
|||||||
super();
|
super();
|
||||||
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
||||||
db = new SqliteDb(connect(dbFile));
|
db = new SqliteDb(connect(dbFile));
|
||||||
ModuleRegistry.add(this);
|
ModuleRegistry.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ package de.srsoftware.umbrella.messagebus;
|
|||||||
import de.srsoftware.umbrella.messagebus.events.Event;
|
import de.srsoftware.umbrella.messagebus.events.Event;
|
||||||
|
|
||||||
public interface EventListener {
|
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.net.InetSocketAddress;
|
||||||
import java.util.LinkedList;
|
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;
|
private final InetSocketAddress addr;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ public class EventQueue extends LinkedList<Event> implements AutoCloseable, Even
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
System.getLogger(addr.toString()).log(System.Logger.Level.INFO,"adding event to queue of {1}: {0}",event.eventType(),addr);
|
||||||
add(event);
|
add(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class MessageBus {
|
public class MessageBus {
|
||||||
private static MessageBus SINGLETON = new MessageBus();
|
private static final MessageBus SINGLETON = new MessageBus();
|
||||||
private Set<EventListener> listeners = new HashSet<>();
|
private final Set<EventListener> listeners = new HashSet<>();
|
||||||
|
|
||||||
private MessageBus(){}
|
private MessageBus(){}
|
||||||
|
|
||||||
|
|||||||
@@ -18,22 +18,28 @@ public abstract class Event<Payload extends Mappable> {
|
|||||||
}
|
}
|
||||||
private UmbrellaUser initiator;
|
private UmbrellaUser initiator;
|
||||||
|
|
||||||
private String realm;
|
private String module;
|
||||||
private Payload payload;
|
private Payload payload;
|
||||||
private EventType eventType;
|
private EventType eventType;
|
||||||
public Event(UmbrellaUser initiator, String realm, Payload payload, EventType type){
|
public Event(UmbrellaUser initiator, String module, Payload payload, EventType type){
|
||||||
this.initiator = initiator;
|
this.initiator = initiator;
|
||||||
this.realm = realm;
|
this.module = module;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.eventType = type;
|
this.eventType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract String describe();
|
||||||
|
|
||||||
public String eventType(){
|
public String eventType(){
|
||||||
return eventType.toString();
|
return eventType.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean isIntendedFor(UmbrellaUser user);
|
public abstract boolean isIntendedFor(UmbrellaUser user);
|
||||||
|
|
||||||
|
public UmbrellaUser initiator(){
|
||||||
|
return initiator;
|
||||||
|
}
|
||||||
|
|
||||||
public String json(){
|
public String json(){
|
||||||
Class<?> clazz = payload.getClass();
|
Class<?> clazz = payload.getClass();
|
||||||
{ // get the highest superclass that is not object
|
{ // get the highest superclass that is not object
|
||||||
@@ -48,6 +54,10 @@ public abstract class Event<Payload extends Mappable> {
|
|||||||
return new JSONObject(map).toString();
|
return new JSONObject(map).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String module(){
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
public Payload payload(){
|
public Payload payload(){
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ public class ProjectEvent extends Event<Project>{
|
|||||||
super(initiator, PROJECT, project, type);
|
super(initiator, PROJECT, project, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "[TODO: ProjectEvent.describe]";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isIntendedFor(UmbrellaUser user) {
|
public boolean isIntendedFor(UmbrellaUser user) {
|
||||||
for (var member : payload().members().values()){
|
for (var member : payload().members().values()){
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ public class TaskEvent extends Event<Task>{
|
|||||||
super(initiator, TASK, task, type);
|
super(initiator, TASK, task, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String describe() {
|
||||||
|
return "[TODO: TaskEvent.describe()]";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isIntendedFor(UmbrellaUser user) {
|
public boolean isIntendedFor(UmbrellaUser user) {
|
||||||
for (var member : payload().members().values()){
|
for (var member : payload().members().values()){
|
||||||
|
|||||||
@@ -75,5 +75,4 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ public class Constants {
|
|||||||
|
|
||||||
private Constants(){}
|
private Constants(){}
|
||||||
|
|
||||||
|
public static final String ACTION = "action";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static final String ADDRESS = "address";
|
public static final String ADDRESS = "address";
|
||||||
public static final String ALLOWED_STATES = "allowed_states";
|
public static final String ALLOWED_STATES = "allowed_states";
|
||||||
public static final String ATTACHMENTS = "attachments";
|
public static final String ATTACHMENTS = "attachments";
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import static java.lang.System.Logger.Level.ERROR;
|
|||||||
import static java.lang.System.Logger.Level.WARNING;
|
import static java.lang.System.Logger.Level.WARNING;
|
||||||
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
|
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
|
||||||
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
|
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
|
||||||
|
|
||||||
public class UmbrellaException extends RuntimeException{
|
public class UmbrellaException extends RuntimeException{
|
||||||
@@ -43,7 +44,12 @@ public class UmbrellaException extends RuntimeException{
|
|||||||
return new UmbrellaException(HTTP_FORBIDDEN,message,fills);
|
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);
|
return new UmbrellaException(HTTP_UNPROCESSABLE, ERROR_INVALID_FIELD, field, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
journal/build.gradle.kts
Normal file
7
journal/build.gradle.kts
Normal 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,7 @@
|
|||||||
|
package de.srsoftware.umbrella.journal;
|
||||||
|
|
||||||
|
import de.srsoftware.umbrella.messagebus.events.Event;
|
||||||
|
|
||||||
|
public interface JournalDb {
|
||||||
|
void logEvent(Event<?> event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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.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.INFO;
|
||||||
|
|
||||||
|
|
||||||
|
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,61 @@
|
|||||||
|
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.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;
|
||||||
|
|
||||||
|
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} INT PRIMARY KEY,
|
||||||
|
{2} INT,
|
||||||
|
{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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,3 +21,5 @@ include("translations")
|
|||||||
include("user")
|
include("user")
|
||||||
include("web")
|
include("web")
|
||||||
include("wiki")
|
include("wiki")
|
||||||
|
|
||||||
|
include("journal")
|
||||||
@@ -335,6 +335,7 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
|
|
||||||
private boolean patchTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
private boolean patchTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
||||||
var task = loadMembers(taskDb.load(taskId));
|
var task = loadMembers(taskDb.load(taskId));
|
||||||
|
var old = task.toMap();
|
||||||
var member = task.members().get(user.id());
|
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());
|
if (member == null || member.permission() == READ_ONLY) throw forbidden("You are not a allowed to edit {0}!", task.name());
|
||||||
var json = json(ex);
|
var json = json(ex);
|
||||||
@@ -342,7 +343,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.");
|
||||||
taskDb.save(task.patch(json));
|
var curr = taskDb.save(task.patch(json)).toMap();
|
||||||
|
var diff = diff(old,curr);
|
||||||
var tagList = tagService().getTags(TASK, taskId, user);
|
var tagList = tagService().getTags(TASK, taskId, user);
|
||||||
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), UPDATE));
|
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), UPDATE));
|
||||||
return sendContent(ex, task);
|
return sendContent(ex, task);
|
||||||
|
|||||||
Reference in New Issue
Block a user