Compare commits
10 Commits
feature/tr
...
module/jou
| Author | SHA1 | Date | |
|---|---|---|---|
| ac126e9b80 | |||
| 3524bae1d8 | |||
| 214eb652e1 | |||
| 9d35952949 | |||
| c3b49cf032 | |||
| 10ea200a2e | |||
| 0dd640de30 | |||
| 81dc30359d | |||
| 6668e29923 | |||
| a4fffbe91b |
@@ -1,5 +1,5 @@
|
|||||||
FROM alpine:3.22 AS svelte_build
|
FROM alpine:3.22 AS svelte_build
|
||||||
RUN apk add npm
|
RUN apk add bash git npm
|
||||||
RUN adduser -Dh /home/svelte svelte
|
RUN adduser -Dh /home/svelte svelte
|
||||||
ADD . /home/svelte/Umbrella
|
ADD . /home/svelte/Umbrella
|
||||||
RUN chown -R svelte /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
|
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
|
ADD . /Umbrella
|
||||||
WORKDIR /Umbrella
|
WORKDIR /Umbrella
|
||||||
COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web
|
COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
LABEL Maintainer "Stephan Richter <s.richter@srsoftware.de>"
|
LABEL Maintainer "Stephan Richter <s.richter@srsoftware.de>"
|
||||||
|
ARG UID=1000
|
||||||
|
ARG GID=1000
|
||||||
RUN apk add bash npm
|
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
|
ADD script /opt
|
||||||
USER svelte
|
USER svelte
|
||||||
WORKDIR /home/svelte
|
WORKDIR /home/svelte
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
default: devel
|
default: devel
|
||||||
|
|
||||||
build: image
|
build: image
|
||||||
docker run --name svelte-build \
|
podman run --name svelte-build \
|
||||||
--rm \
|
--rm \
|
||||||
-v ../frontend:/home/svelte/frontend \
|
-v ../frontend:/home/svelte/frontend \
|
||||||
-ti svelte /opt/svelte-build
|
-ti svelte /opt/svelte-build
|
||||||
image:
|
image:
|
||||||
docker build -t svelte .
|
podman build --build-arg UID=$$(id -u) --build-arg GID=$$(id -g) -t svelte .
|
||||||
|
|
||||||
devel: image
|
devel: image
|
||||||
-docker rm -f svelte
|
-podman rm -f svelte
|
||||||
docker run --name svelte \
|
podman run --name svelte \
|
||||||
-v ../frontend:/home/svelte/frontend \
|
-v ../frontend:/home/svelte/frontend \
|
||||||
-p 5173:5173 \
|
-p 5173:5173 \
|
||||||
-ti svelte /opt/svelte-init
|
-ti svelte /opt/svelte-init
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ subprojects {
|
|||||||
implementation("de.srsoftware:tools.mime:1.1.4")
|
implementation("de.srsoftware:tools.mime:1.1.4")
|
||||||
implementation("de.srsoftware:tools.logging:1.3.2")
|
implementation("de.srsoftware:tools.logging:1.3.2")
|
||||||
implementation("de.srsoftware:tools.optionals:1.0.0")
|
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")
|
implementation("org.json:json:20240303")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(){}
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,75 @@
|
|||||||
/* © 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.util.Optional.*;
|
||||||
|
|
||||||
|
import de.srsoftware.tools.Diff;
|
||||||
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 realm;
|
private final UmbrellaUser initiator;
|
||||||
private Payload payload;
|
private final String module;
|
||||||
private EventType eventType;
|
private final Payload payload;
|
||||||
public Event(UmbrellaUser initiator, String realm, Payload payload, EventType type){
|
private final EventType eventType;
|
||||||
|
private final Map<String, Object> oldData;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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(){
|
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 +84,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +13,15 @@ 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
|
||||||
|
public String describe() {
|
||||||
|
return diff().orElse("[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()){
|
||||||
|
|||||||
@@ -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,6 +13,15 @@ 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
|
||||||
|
public String describe() {
|
||||||
|
return diff().orElse("[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()){
|
||||||
|
|||||||
@@ -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);
|
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";
|
||||||
@@ -163,4 +160,5 @@ public class Constants {
|
|||||||
public static final String VERSION = "version";
|
public static final String VERSION = "version";
|
||||||
public static final String VERSIONS = "versions";
|
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.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ public final class Document implements Mappable {
|
|||||||
case SENDER: if (json.get(key) instanceof JSONObject nested) sender.patch(nested); break;
|
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 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 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;
|
default: key = null;
|
||||||
}
|
}
|
||||||
if (key != null) dirtyFields.add(key);
|
if (key != null) dirtyFields.add(key);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
if (newValue != null) 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,38 +0,0 @@
|
|||||||
package de.srsoftware.umbrella.core.model;
|
|
||||||
|
|
||||||
import de.srsoftware.umbrella.core.ModuleRegistry;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class Translatable {
|
|
||||||
private final String message;
|
|
||||||
private Map<String, Object> fills;
|
|
||||||
private final HashMap<String,String> translated = new HashMap<>();
|
|
||||||
|
|
||||||
public Translatable(String message, Map<String,Object> fills){
|
|
||||||
this.fills = fills;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(String language){
|
|
||||||
var translation = translated.get(language);
|
|
||||||
if (translation == null){
|
|
||||||
var translatedFills = new HashMap<String,String>();
|
|
||||||
if (fills != null) {
|
|
||||||
for (var entry : fills.entrySet()) {
|
|
||||||
var o = entry.getValue();
|
|
||||||
var val = switch (o) {
|
|
||||||
case Translatable tr -> tr.translate(language);
|
|
||||||
case String s -> s;
|
|
||||||
default -> o.toString();
|
|
||||||
};
|
|
||||||
translatedFills.put(entry.getKey(), val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
translation = ModuleRegistry.translator().translate(language,message,translatedFills);
|
|
||||||
translated.put(language,translation);
|
|
||||||
}
|
|
||||||
return translation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import Login from "../../Components/Login.svelte";
|
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
import { api } from '../../urls.svelte';
|
import { api } from '../../urls.svelte';
|
||||||
@@ -47,4 +45,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Login />
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
import { api } from '../../urls.svelte';
|
import { api, eventStream } from '../../urls.svelte';
|
||||||
import { error, yikes } from '../../warn.svelte';
|
import { error, yikes } from '../../warn.svelte';
|
||||||
import { t } from '../../translations.svelte';
|
import { t } from '../../translations.svelte';
|
||||||
import { user } from '../../user.svelte';
|
import { user } from '../../user.svelte';
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
import TagList from '../tags/TagList.svelte';
|
import TagList from '../tags/TagList.svelte';
|
||||||
|
|
||||||
let detail = $state(false);
|
let detail = $state(false);
|
||||||
|
let eventSource = null;
|
||||||
let { key, version } = $props();
|
let { key, version } = $props();
|
||||||
let page = $state({});
|
let page = $state({});
|
||||||
let router = useTinyRouter();
|
let router = useTinyRouter();
|
||||||
@@ -27,6 +28,10 @@
|
|||||||
return patch({members:newMembers});
|
return patch({members:newMembers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectToBus(){
|
||||||
|
eventSource = eventStream(null,handleUpdateEvent,null);
|
||||||
|
}
|
||||||
|
|
||||||
async function dropMember(member){
|
async function dropMember(member){
|
||||||
var id = member.user.id;
|
var id = member.user.id;
|
||||||
let newMembers = JSON.parse(JSON.stringify(page.members));
|
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){
|
function nonMember(json){
|
||||||
return !page.members[json.id];
|
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){
|
if (res.ok){
|
||||||
let json = await res.json();
|
let json = await res.json();
|
||||||
json.versions.sort((a,b)=>b-a);
|
loadContent(json);
|
||||||
page = { ...json };
|
|
||||||
yikes();
|
yikes();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -88,7 +102,7 @@
|
|||||||
if (version) path += `/version/${version}`;
|
if (version) path += `/version/${version}`;
|
||||||
const url = api(path);
|
const url = api(path);
|
||||||
const res = await fetch(url,{credentials:'include'});
|
const res = await fetch(url,{credentials:'include'});
|
||||||
loadContent(res);
|
loadJson(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onclick(e){
|
function onclick(e){
|
||||||
@@ -105,7 +119,7 @@
|
|||||||
method:'PATCH',
|
method:'PATCH',
|
||||||
body:JSON.stringify(data)
|
body:JSON.stringify(data)
|
||||||
});
|
});
|
||||||
return loadContent(res);
|
return loadJson(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function patchGuestPermissions(ev){
|
async function patchGuestPermissions(ev){
|
||||||
@@ -130,6 +144,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$effect(loadPage);
|
$effect(loadPage);
|
||||||
|
onMount(connectToBus);
|
||||||
</script>
|
</script>
|
||||||
{#if page && page.versions}
|
{#if page && page.versions}
|
||||||
<div class="wiki page">
|
<div class="wiki page">
|
||||||
|
|||||||
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,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.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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ include("translations")
|
|||||||
include("user")
|
include("user")
|
||||||
include("web")
|
include("web")
|
||||||
include("wiki")
|
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.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 {
|
||||||
@@ -335,6 +318,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,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(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));
|
task = taskDb.save(task.patch(json)).tags(tagService().getTags(TASK, taskId, user));
|
||||||
var tagList = tagService().getTags(TASK, taskId, user);
|
messageBus().dispatch(new TaskEvent(user, task, old));
|
||||||
messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), UPDATE));
|
|
||||||
return sendContent(ex, task);
|
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())) 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ tasks.processResources {
|
|||||||
from("../frontend/dist") {
|
from("../frontend/dist") {
|
||||||
into("web")
|
into("web")
|
||||||
}
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
description = "Umbrella : Wiki"
|
description = "Umbrella : Wiki"
|
||||||
|
|
||||||
dependencies{
|
dependencies{
|
||||||
|
implementation(project(":bus"))
|
||||||
implementation(project(":core"))
|
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.exceptions.UmbrellaException.*;
|
||||||
import static de.srsoftware.umbrella.core.model.Permission.EDIT;
|
import static de.srsoftware.umbrella.core.model.Permission.EDIT;
|
||||||
import static de.srsoftware.umbrella.core.model.Permission.READ_ONLY;
|
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 static de.srsoftware.umbrella.wiki.Constants.*;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
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.api.WikiService;
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.*;
|
import de.srsoftware.umbrella.core.model.*;
|
||||||
|
import de.srsoftware.umbrella.messagebus.events.WikiEvent;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
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 {
|
private boolean patchPage(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
var id = path.pop();
|
var id = path.pop();
|
||||||
var page = loadPage(id, null);
|
var page = loadPage(id, null);
|
||||||
|
var old = page.toMap();
|
||||||
var member = page.members().get(user.id());
|
var member = page.members().get(user.id());
|
||||||
if (member == null || member.permission() != EDIT) throw forbidden("You are not allowed to edit {0}!",id);
|
if (member == null || member.permission() != EDIT) throw forbidden("You are not allowed to edit {0}!",id);
|
||||||
var json = json(ex);
|
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 {
|
private boolean postNewPage(String title, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
|
|||||||
Reference in New Issue
Block a user