diff --git a/build.gradle b/build.gradle index c13c146..7d28c4c 100644 --- a/build.gradle +++ b/build.gradle @@ -82,6 +82,7 @@ dependencies { implementation 'io.springfox:springfox-swagger-ui:2.9.2' implementation 'com.github.javafaker:javafaker:1.0.2' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.3' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/mysql/db/entrypoint/schema.sql b/mysql/db/entrypoint/schema.sql index 2b059c6..a97d31e 100644 --- a/mysql/db/entrypoint/schema.sql +++ b/mysql/db/entrypoint/schema.sql @@ -1,15 +1,15 @@ CREATE TABLE event ( - event_id INT PRIMARY KEY AUTO_INCREMENT, - group_id VARCHAR(36) NOT NULL, - user_id VARCHAR(50), - event_type VARCHAR(36), + event_id INT PRIMARY KEY AUTO_INCREMENT, + group_id VARCHAR(36) NOT NULL, + user_id VARCHAR(50) NOT NULL, + event_type VARCHAR(32) NOT NULL, event_payload JSON ); CREATE TABLE invite ( - invite_id INT PRIMARY KEY AUTO_INCREMENT, - group_id VARCHAR(36) NOT NULL, + invite_id INT PRIMARY KEY AUTO_INCREMENT, + group_id VARCHAR(36) NOT NULL, invite_link VARCHAR(36) NOT NULL ); diff --git a/src/main/java/mops/gruppen2/aspect/LogAspect.java b/src/main/java/mops/gruppen2/aspect/LogAspect.java index 64a3a13..bad5b06 100644 --- a/src/main/java/mops/gruppen2/aspect/LogAspect.java +++ b/src/main/java/mops/gruppen2/aspect/LogAspect.java @@ -13,7 +13,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @Log4j2 -@Profile("dev") +@Profile({"dev", "docker"}) @Aspect @Component public class LogAspect { @@ -36,12 +36,12 @@ public class LogAspect { @Before("@annotation(mops.gruppen2.aspect.annotation.Trace)") - public void logCustom(JoinPoint joinPoint) { + public static void logCustom(JoinPoint joinPoint) { log.trace(((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Trace.class).value()); } @Before("@annotation(mops.gruppen2.aspect.annotation.TraceMethodCall) || logMethodCalls()") - public void logMethodCall(JoinPoint joinPoint) { + public static void logMethodCall(JoinPoint joinPoint) { log.trace("Methodenaufruf: {} ({})", joinPoint.getSignature().getName(), joinPoint.getSourceLocation().getWithinType().getName().replace("mops.gruppen2.", "")); @@ -50,7 +50,7 @@ public class LogAspect { } @Around("@annotation(mops.gruppen2.aspect.annotation.TraceExecutionTime)") - public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + public static Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); joinPoint.proceed(); long stop = System.currentTimeMillis(); diff --git a/src/main/java/mops/gruppen2/config/WebConfig.java b/src/main/java/mops/gruppen2/config/WebConfig.java new file mode 100644 index 0000000..ab5607e --- /dev/null +++ b/src/main/java/mops/gruppen2/config/WebConfig.java @@ -0,0 +1,15 @@ +package mops.gruppen2.config; + +import mops.gruppen2.config.converter.StringToLimitConverter; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new StringToLimitConverter()); + } +} diff --git a/src/main/java/mops/gruppen2/config/converter/StringToLimitConverter.java b/src/main/java/mops/gruppen2/config/converter/StringToLimitConverter.java new file mode 100644 index 0000000..bd12c49 --- /dev/null +++ b/src/main/java/mops/gruppen2/config/converter/StringToLimitConverter.java @@ -0,0 +1,12 @@ +package mops.gruppen2.config.converter; + +import mops.gruppen2.domain.model.group.wrapper.Limit; +import org.springframework.core.convert.converter.Converter; + +public class StringToLimitConverter implements Converter { + + @Override + public Limit convert(String value) { + return new Limit(Long.parseLong(value)); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java b/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java new file mode 100644 index 0000000..f492037 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java @@ -0,0 +1,44 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.GroupFullException; +import mops.gruppen2.domain.exception.IdMismatchException; +import mops.gruppen2.domain.exception.UserAlreadyExistsException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.User; + +/** + * Fügt einen einzelnen Nutzer einer Gruppe hinzu. + */ +@Log4j2 +@Value +@AllArgsConstructor +public class AddMemberEvent extends Event { + + @JsonProperty("user") + User user; + + public AddMemberEvent(Group group, String exec, String target, User user) throws IdMismatchException { + super(group.getId(), exec, target); + this.user = user; + + if (!target.equals(user.getId())) { + throw new IdMismatchException("Der User passt nicht zur angegebenen userid."); + } + } + + @Override + protected void applyEvent(Group group) throws UserAlreadyExistsException, GroupFullException { + group.addMember(target, user); + + log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers()); + } + + @Override + public String getType() { + return EventType.ADDMEMBER.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java b/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java deleted file mode 100644 index ce2d766..0000000 --- a/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -package mops.gruppen2.domain.event; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Value; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Role; -import mops.gruppen2.domain.model.User; - -/** - * Fügt einen einzelnen Nutzer einer Gruppe hinzu. - */ - -@Log4j2 -@Value -@AllArgsConstructor -public class AddUserEvent extends Event { - - @JsonProperty("givenname") - String givenname; - - @JsonProperty("familyname") - String familyname; - - @JsonProperty("email") - String email; - - public AddUserEvent(Group group, User user) { - super(group.getGroupid(), user.getUserid()); - givenname = user.getGivenname(); - familyname = user.getFamilyname(); - email = user.getEmail(); - } - - @Override - protected void applyEvent(Group group) throws EventException { - ValidationHelper.throwIfMember(group, new User(userid)); - ValidationHelper.throwIfGroupFull(group); - - group.getMembers().put(userid, new User(userid, givenname, familyname, email)); - group.getRoles().put(userid, Role.MEMBER); - - log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers()); - log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles()); - } -} diff --git a/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java index ac381d9..0d763e7 100644 --- a/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java @@ -4,10 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Value; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.exception.BadArgumentException; +import mops.gruppen2.domain.model.group.Group; +import java.time.LocalDateTime; import java.util.UUID; @Log4j2 @@ -15,24 +15,30 @@ import java.util.UUID; @AllArgsConstructor// Value generiert den allArgsConstrucot nur, wenn keiner explizit angegeben ist public class CreateGroupEvent extends Event { - @JsonProperty("parent") - UUID parent; + @JsonProperty("date") + LocalDateTime date; - @JsonProperty("type") - Type type; - - public CreateGroupEvent(UUID groupId, User user, UUID parent, Type type) { - super(groupId, user.getUserid()); - this.parent = parent; - this.type = type; + public CreateGroupEvent(UUID groupId, String exec, LocalDateTime date) { + super(groupId, exec, null); + this.date = date; } @Override - protected void applyEvent(Group group) { - group.setGroupid(groupid); - group.setParent(parent); - group.setType(type); + protected void applyEvent(Group group) throws BadArgumentException { + group.setId(groupid); + group.setCreator(exec); + group.setCreationDate(date); log.trace("\t\t\t\t\tNeue Gruppe: {}", group.toString()); } + + @Override + public String getType() { + return EventType.CREATEGROUP.toString(); + } + + @Override + public String toString() { + return "(" + version + "," + groupid + "," + date + ")"; + } } diff --git a/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java deleted file mode 100644 index a2f79be..0000000 --- a/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -package mops.gruppen2.domain.event; - -import lombok.AllArgsConstructor; -import lombok.Value; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.User; - -@Log4j2 -@Value -@AllArgsConstructor -public class DeleteGroupEvent extends Event { - - public DeleteGroupEvent(Group group, User user) { - super(group.getGroupid(), user.getUserid()); - } - - @Override - protected void applyEvent(Group group) { - group.getRoles().clear(); - group.getMembers().clear(); - group.setTitle(null); - group.setDescription(null); - group.setType(null); - group.setParent(null); - group.setLimit(null); - - log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group); - } -} diff --git a/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java b/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java deleted file mode 100644 index eb7d3fe..0000000 --- a/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java +++ /dev/null @@ -1,33 +0,0 @@ -package mops.gruppen2.domain.event; - -import lombok.AllArgsConstructor; -import lombok.Value; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.User; - -/** - * Entfernt ein einzelnes Mitglied einer Gruppe. - */ -@Log4j2 -@Value -@AllArgsConstructor -public class DeleteUserEvent extends Event { - - public DeleteUserEvent(Group group, User user) { - super(group.getGroupid(), user.getUserid()); - } - - @Override - protected void applyEvent(Group group) throws EventException { - ValidationHelper.throwIfNoMember(group, new User(userid)); - - group.getMembers().remove(userid); - group.getRoles().remove(userid); - - log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers()); - log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles()); - } -} diff --git a/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java new file mode 100644 index 0000000..78a1b45 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java @@ -0,0 +1,29 @@ +package mops.gruppen2.domain.event; + +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; + +@Log4j2 +@Value +@AllArgsConstructor +public class DestroyGroupEvent extends Event { + + public DestroyGroupEvent(Group group, String exec) { + super(group.getId(), exec, null); + } + + @Override + protected void applyEvent(Group group) throws NoAccessException { + group.destroy(exec); + + log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group.toString()); + } + + @Override + public String getType() { + return EventType.DESTROYGROUP.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/Event.java b/src/main/java/mops/gruppen2/domain/event/Event.java index 3fbf498..be97a44 100644 --- a/src/main/java/mops/gruppen2/domain/event/Event.java +++ b/src/main/java/mops/gruppen2/domain/event/Event.java @@ -1,59 +1,92 @@ package mops.gruppen2.domain.event; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.BadArgumentException; import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.exception.GroupIdMismatchException; -import mops.gruppen2.domain.model.Group; +import mops.gruppen2.domain.exception.IdMismatchException; +import mops.gruppen2.domain.model.group.Group; import java.util.UUID; @Log4j2 -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({@JsonSubTypes.Type(value = AddUserEvent.class, name = "AddUserEvent"), - @JsonSubTypes.Type(value = CreateGroupEvent.class, name = "CreateGroupEvent"), - @JsonSubTypes.Type(value = DeleteUserEvent.class, name = "DeleteUserEvent"), - @JsonSubTypes.Type(value = UpdateGroupDescriptionEvent.class, name = "UpdateGroupDescriptionEvent"), - @JsonSubTypes.Type(value = UpdateGroupTitleEvent.class, name = "UpdateGroupTitleEvent"), - @JsonSubTypes.Type(value = UpdateRoleEvent.class, name = "UpdateRoleEvent"), - @JsonSubTypes.Type(value = DeleteGroupEvent.class, name = "DeleteGroupEvent"), - @JsonSubTypes.Type(value = UpdateUserLimitEvent.class, name = "UpdateUserLimitEvent")}) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "class") +@JsonSubTypes({@JsonSubTypes.Type(value = AddMemberEvent.class, name = "ADDMEMBER"), + @JsonSubTypes.Type(value = CreateGroupEvent.class, name = "CREATEGROUP"), + @JsonSubTypes.Type(value = DestroyGroupEvent.class, name = "DESTROYGROUP"), + @JsonSubTypes.Type(value = KickMemberEvent.class, name = "KICKMEMBER"), + @JsonSubTypes.Type(value = SetDescriptionEvent.class, name = "SETDESCRIPTION"), + @JsonSubTypes.Type(value = SetInviteLinkEvent.class, name = "SETLINK"), + @JsonSubTypes.Type(value = SetLimitEvent.class, name = "SETLIMIT"), + @JsonSubTypes.Type(value = SetParentEvent.class, name = "SETPARENT"), + @JsonSubTypes.Type(value = SetTitleEvent.class, name = "SETTITLE"), + @JsonSubTypes.Type(value = SetTypeEvent.class, name = "SETTYPE"), + @JsonSubTypes.Type(value = UpdateRoleEvent.class, name = "UPDATEROLE")}) @Getter -@AllArgsConstructor @NoArgsConstructor // Lombok needs a default constructor in the base class public abstract class Event { + @JsonProperty("groupid") protected UUID groupid; - @JsonProperty("userid") - protected String userid; + @JsonProperty("version") + protected long version; + + @JsonProperty("exec") + protected String exec; + + @JsonProperty("target") + protected String target; + + public Event(UUID groupid, String exec, String target) { + this.groupid = groupid; + this.exec = exec; + this.target = target; + } + + public void init(long version) { + if (this.version != 0) { + throw new BadArgumentException("Event wurde schon initialisiert. (" + getType() + ")"); + } + + log.trace("Event wurde initialisiert. (" + getType() + "," + version + ")"); + + this.version = version; + } public Group apply(Group group) throws EventException { - checkGroupIdMatch(group.getGroupid()); + log.trace("Event wird angewendet:\t{}", this); - log.trace("Event angewendet:\t{}", this); + if (version == 0) { + throw new BadArgumentException("Event wurde nicht initialisiert."); + } + checkGroupIdMatch(group.getId()); + group.update(version); applyEvent(group); return group; } - private void checkGroupIdMatch(UUID groupId) { + private void checkGroupIdMatch(UUID groupId) throws IdMismatchException { // CreateGroupEvents müssen die Id erst initialisieren if (this instanceof CreateGroupEvent) { return; } if (!groupid.equals(groupId)) { - throw new GroupIdMismatchException(getClass().toString()); + throw new IdMismatchException("Das Event gehört zu einer anderen Gruppe"); } } protected abstract void applyEvent(Group group) throws EventException; + + @JsonIgnore + public abstract String getType(); } diff --git a/src/main/java/mops/gruppen2/domain/event/EventType.java b/src/main/java/mops/gruppen2/domain/event/EventType.java new file mode 100644 index 0000000..a444326 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/EventType.java @@ -0,0 +1,15 @@ +package mops.gruppen2.domain.event; + +public enum EventType { + ADDMEMBER, + CREATEGROUP, + DESTROYGROUP, + KICKMEMBER, + SETDESCRIPTION, + SETLINK, + SETLIMIT, + SETPARENT, + SETTITLE, + SETTYPE, + UPDATEROLE +} diff --git a/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java b/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java new file mode 100644 index 0000000..cc3ad27 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java @@ -0,0 +1,33 @@ +package mops.gruppen2.domain.event; + +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.LastAdminException; +import mops.gruppen2.domain.exception.UserNotFoundException; +import mops.gruppen2.domain.model.group.Group; + +/** + * Entfernt ein einzelnes Mitglied einer Gruppe. + */ +@Log4j2 +@Value +@AllArgsConstructor +public class KickMemberEvent extends Event { + + public KickMemberEvent(Group group, String exec, String target) { + super(group.getId(), exec, target); + } + + @Override + protected void applyEvent(Group group) throws UserNotFoundException, LastAdminException { + group.kickMember(target); + + log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers()); + } + + @Override + public String getType() { + return EventType.KICKMEMBER.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/SetDescriptionEvent.java b/src/main/java/mops/gruppen2/domain/event/SetDescriptionEvent.java new file mode 100644 index 0000000..976dedd --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/SetDescriptionEvent.java @@ -0,0 +1,40 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Description; + +import javax.validation.Valid; + +/** + * Ändert nur die Gruppenbeschreibung. + */ +@Log4j2 +@Value +@AllArgsConstructor +public class SetDescriptionEvent extends Event { + + @JsonProperty("desc") + Description description; + + public SetDescriptionEvent(Group group, String exec, @Valid Description description) { + super(group.getId(), exec, null); + this.description = description; + } + + @Override + protected void applyEvent(Group group) throws NoAccessException { + group.setDescription(exec, description); + + log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription()); + } + + @Override + public String getType() { + return EventType.SETDESCRIPTION.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/SetInviteLinkEvent.java b/src/main/java/mops/gruppen2/domain/event/SetInviteLinkEvent.java new file mode 100644 index 0000000..25f2af0 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/SetInviteLinkEvent.java @@ -0,0 +1,37 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Link; + +import javax.validation.Valid; + +@Log4j2 +@Value +@AllArgsConstructor +public class SetInviteLinkEvent extends Event { + + @JsonProperty("link") + Link link; + + public SetInviteLinkEvent(Group group, String exec, @Valid Link link) { + super(group.getId(), exec, null); + this.link = link; + } + + @Override + protected void applyEvent(Group group) throws NoAccessException { + group.setLink(exec, link); + + log.trace("\t\t\t\t\tNeuer Link: {}", group.getLink()); + } + + @Override + public String getType() { + return EventType.SETLINK.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/SetLimitEvent.java b/src/main/java/mops/gruppen2/domain/event/SetLimitEvent.java new file mode 100644 index 0000000..d160435 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/SetLimitEvent.java @@ -0,0 +1,38 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.BadArgumentException; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Limit; + +import javax.validation.Valid; + +@Log4j2 +@Value +@AllArgsConstructor +public class SetLimitEvent extends Event { + + @JsonProperty("limit") + Limit limit; + + public SetLimitEvent(Group group, String exec, @Valid Limit limit) { + super(group.getId(), exec, null); + this.limit = limit; + } + + @Override + protected void applyEvent(Group group) throws BadArgumentException, NoAccessException { + group.setLimit(exec, limit); + + log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getLimit()); + } + + @Override + public String getType() { + return EventType.SETLIMIT.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/SetParentEvent.java b/src/main/java/mops/gruppen2/domain/event/SetParentEvent.java new file mode 100644 index 0000000..736cc89 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/SetParentEvent.java @@ -0,0 +1,37 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Parent; + +import javax.validation.Valid; + +@Log4j2 +@Value +@AllArgsConstructor +public class SetParentEvent extends Event { + + @JsonProperty("parent") + Parent parent; + + public SetParentEvent(Group group, String exec, @Valid Parent parent) { + super(group.getId(), exec, null); + this.parent = parent; + } + + @Override + protected void applyEvent(Group group) throws NoAccessException { + group.setParent(exec, parent); + + log.trace("\t\t\t\t\tNeues Parent: {}", group.getParent()); + } + + @Override + public String getType() { + return EventType.SETPARENT.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/SetTitleEvent.java b/src/main/java/mops/gruppen2/domain/event/SetTitleEvent.java new file mode 100644 index 0000000..17e6599 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/SetTitleEvent.java @@ -0,0 +1,41 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Title; + +import javax.validation.Valid; + +/** + * Ändert nur den Gruppentitel. + */ +@Log4j2 +@Value +@AllArgsConstructor +public class SetTitleEvent extends Event { + + @JsonProperty("title") + Title title; + + public SetTitleEvent(Group group, String exec, @Valid Title title) { + super(group.getId(), exec, null); + this.title = title; + } + + @Override + protected void applyEvent(Group group) throws NoAccessException { + group.setTitle(exec, title); + + log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle()); + } + + @Override + public String getType() { + return EventType.SETTITLE.toString(); + } + +} diff --git a/src/main/java/mops/gruppen2/domain/event/SetTypeEvent.java b/src/main/java/mops/gruppen2/domain/event/SetTypeEvent.java new file mode 100644 index 0000000..e87c8ee --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/SetTypeEvent.java @@ -0,0 +1,36 @@ +package mops.gruppen2.domain.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.EventException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; + +import javax.validation.Valid; + +@Log4j2 +@Value +@AllArgsConstructor +public class SetTypeEvent extends Event { + + @JsonProperty("type") + Type type; + + public SetTypeEvent(Group group, String exec, @Valid Type type) { + super(group.getId(), exec, null); + + this.type = type; + } + + @Override + protected void applyEvent(Group group) throws EventException { + group.setType(exec, type); + } + + @Override + public String getType() { + return EventType.SETTYPE.toString(); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java deleted file mode 100644 index b5d5dc4..0000000 --- a/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java +++ /dev/null @@ -1,33 +0,0 @@ -package mops.gruppen2.domain.event; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Value; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.model.Description; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.User; - -/** - * Ändert nur die Gruppenbeschreibung. - */ -@Log4j2 -@Value -@AllArgsConstructor -public class UpdateGroupDescriptionEvent extends Event { - - @JsonProperty("desc") - Description description; - - public UpdateGroupDescriptionEvent(Group group, User user, Description description) { - super(group.getGroupid(), user.getUserid()); - this.description = description; - } - - @Override - protected void applyEvent(Group group) { - group.setDescription(description); - - log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription()); - } -} diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java deleted file mode 100644 index 96e469f..0000000 --- a/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java +++ /dev/null @@ -1,34 +0,0 @@ -package mops.gruppen2.domain.event; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Value; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Title; -import mops.gruppen2.domain.model.User; - -/** - * Ändert nur den Gruppentitel. - */ -@Log4j2 -@Value -@AllArgsConstructor -public class UpdateGroupTitleEvent extends Event { - - @JsonProperty("title") - Title title; - - public UpdateGroupTitleEvent(Group group, User user, Title title) { - super(group.getGroupid(), user.getUserid()); - this.title = title; - } - - @Override - protected void applyEvent(Group group) { - group.setTitle(title); - - log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle()); - } - -} diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java index 48d0b3c..0164d51 100644 --- a/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java @@ -4,11 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Value; import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.LastAdminException; import mops.gruppen2.domain.exception.UserNotFoundException; -import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Role; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Role; /** * Aktualisiert die Gruppenrolle eines Teilnehmers. @@ -21,18 +20,21 @@ public class UpdateRoleEvent extends Event { @JsonProperty("role") Role role; - public UpdateRoleEvent(Group group, User user, Role tole) { - super(group.getGroupid(), user.getUserid()); - role = tole; + public UpdateRoleEvent(Group group, String exec, String target, Role role) { + super(group.getId(), exec, target); + this.role = role; } @Override - protected void applyEvent(Group group) throws UserNotFoundException { - ValidationHelper.throwIfNoMember(group, new User(userid)); + protected void applyEvent(Group group) throws UserNotFoundException, LastAdminException { + group.memberPutRole(target, role); - group.getRoles().put(userid, role); + log.trace("\t\t\t\t\tNeue Admin: {}", group.getAdmins()); + } - log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles()); + @Override + public String getType() { + return EventType.UPDATEROLE.toString(); } } diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateUserLimitEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateUserLimitEvent.java deleted file mode 100644 index f3fdd56..0000000 --- a/src/main/java/mops/gruppen2/domain/event/UpdateUserLimitEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -package mops.gruppen2.domain.event; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Value; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.exception.BadParameterException; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Limit; -import mops.gruppen2.domain.model.User; - -@Log4j2 -@Value -@AllArgsConstructor -public class UpdateUserLimitEvent extends Event { - - @JsonProperty("limit") - Limit limit; - - public UpdateUserLimitEvent(Group group, User user, Limit limit) { - super(group.getGroupid(), user.getUserid()); - this.limit = limit; - } - - @Override - protected void applyEvent(Group group) throws EventException { - if (limit.getUserLimit() < group.getMembers().size()) { - throw new BadParameterException("Teilnehmerlimit zu klein."); - } - - group.setLimit(limit); - - log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getLimit()); - } -} diff --git a/src/main/java/mops/gruppen2/domain/exception/BadArgumentException.java b/src/main/java/mops/gruppen2/domain/exception/BadArgumentException.java new file mode 100644 index 0000000..425a6d1 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/exception/BadArgumentException.java @@ -0,0 +1,12 @@ +package mops.gruppen2.domain.exception; + +import org.springframework.http.HttpStatus; + +public class BadArgumentException extends EventException { + + private static final long serialVersionUID = -6757742013238625595L; + + public BadArgumentException(String info) { + super(HttpStatus.BAD_REQUEST, "Fehlerhafter Parameter.", info); + } +} diff --git a/src/main/java/mops/gruppen2/domain/exception/BadParameterException.java b/src/main/java/mops/gruppen2/domain/exception/BadParameterException.java deleted file mode 100644 index 0d41a10..0000000 --- a/src/main/java/mops/gruppen2/domain/exception/BadParameterException.java +++ /dev/null @@ -1,12 +0,0 @@ -package mops.gruppen2.domain.exception; - -import org.springframework.http.HttpStatus; - -public class BadParameterException extends EventException { - - private static final long serialVersionUID = -6757742013238625595L; - - public BadParameterException(String info) { - super(HttpStatus.BAD_REQUEST, "Fehlerhafter Parameter angegeben!", info); - } -} diff --git a/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java b/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java index fcd1d0f..4281ac2 100644 --- a/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java +++ b/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java @@ -7,7 +7,7 @@ public class BadPayloadException extends EventException { private static final long serialVersionUID = -3978242017847155629L; public BadPayloadException(String info) { - super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Payload konnte nicht übersetzt werden!", info); + super(HttpStatus.INTERNAL_SERVER_ERROR, "Payload konnte nicht übersetzt werden.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/GroupFullException.java b/src/main/java/mops/gruppen2/domain/exception/GroupFullException.java index 53bf3bc..5f2d4c6 100644 --- a/src/main/java/mops/gruppen2/domain/exception/GroupFullException.java +++ b/src/main/java/mops/gruppen2/domain/exception/GroupFullException.java @@ -7,7 +7,7 @@ public class GroupFullException extends EventException { private static final long serialVersionUID = -4011141160467668713L; public GroupFullException(String info) { - super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Gruppe hat die maximale Mitgliederanzahl bereits erreicht!", info); + super(HttpStatus.INTERNAL_SERVER_ERROR, "Gruppe hat maximale Teilnehmeranzahl bereits erreicht.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/GroupIdMismatchException.java b/src/main/java/mops/gruppen2/domain/exception/GroupIdMismatchException.java deleted file mode 100644 index d762f87..0000000 --- a/src/main/java/mops/gruppen2/domain/exception/GroupIdMismatchException.java +++ /dev/null @@ -1,13 +0,0 @@ -package mops.gruppen2.domain.exception; - -import org.springframework.http.HttpStatus; - -public class GroupIdMismatchException extends EventException { - - private static final long serialVersionUID = 7944077617758922089L; - - public GroupIdMismatchException(String info) { - super(HttpStatus.INTERNAL_SERVER_ERROR, "Falsche Gruppe für Event.", info); - } -} - diff --git a/src/main/java/mops/gruppen2/domain/exception/GroupNotFoundException.java b/src/main/java/mops/gruppen2/domain/exception/GroupNotFoundException.java index 86bcb0f..5a10585 100644 --- a/src/main/java/mops/gruppen2/domain/exception/GroupNotFoundException.java +++ b/src/main/java/mops/gruppen2/domain/exception/GroupNotFoundException.java @@ -7,7 +7,7 @@ public class GroupNotFoundException extends EventException { private static final long serialVersionUID = -4738218416842951106L; public GroupNotFoundException(String info) { - super(HttpStatus.NOT_FOUND, "Die Gruppe existiert nicht oder wurde gelöscht.", info); + super(HttpStatus.NOT_FOUND, "Gruppe existiert nicht oder wurde gelöscht.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/IdMismatchException.java b/src/main/java/mops/gruppen2/domain/exception/IdMismatchException.java new file mode 100644 index 0000000..1200f8b --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/exception/IdMismatchException.java @@ -0,0 +1,13 @@ +package mops.gruppen2.domain.exception; + +import org.springframework.http.HttpStatus; + +public class IdMismatchException extends EventException { + + private static final long serialVersionUID = 7944077617758922089L; + + public IdMismatchException(String info) { + super(HttpStatus.INTERNAL_SERVER_ERROR, "Ids stimmen nicht überein.", info); + } +} + diff --git a/src/main/java/mops/gruppen2/domain/exception/InvalidInviteException.java b/src/main/java/mops/gruppen2/domain/exception/InvalidInviteException.java index a0f74b3..2f9b19e 100644 --- a/src/main/java/mops/gruppen2/domain/exception/InvalidInviteException.java +++ b/src/main/java/mops/gruppen2/domain/exception/InvalidInviteException.java @@ -7,7 +7,7 @@ public class InvalidInviteException extends EventException { private static final long serialVersionUID = 2643001101459427944L; public InvalidInviteException(String info) { - super(HttpStatus.NOT_FOUND, "Der Einladungslink ist ungültig oder die Gruppe wurde gelöscht.", info); + super(HttpStatus.NOT_FOUND, "Einladungslink ist ungültig oder Gruppe wurde gelöscht.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/LastAdminException.java b/src/main/java/mops/gruppen2/domain/exception/LastAdminException.java new file mode 100644 index 0000000..a6af0ea --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/exception/LastAdminException.java @@ -0,0 +1,12 @@ +package mops.gruppen2.domain.exception; + +import org.springframework.http.HttpStatus; + +public class LastAdminException extends EventException { + + private static final long serialVersionUID = 9059481382346544288L; + + public LastAdminException(String info) { + super(HttpStatus.INTERNAL_SERVER_ERROR, "Gruppe braucht mindestens einen Admin.", info); + } +} diff --git a/src/main/java/mops/gruppen2/domain/exception/NoAccessException.java b/src/main/java/mops/gruppen2/domain/exception/NoAccessException.java index 292f42f..27c569b 100644 --- a/src/main/java/mops/gruppen2/domain/exception/NoAccessException.java +++ b/src/main/java/mops/gruppen2/domain/exception/NoAccessException.java @@ -7,7 +7,7 @@ public class NoAccessException extends EventException { private static final long serialVersionUID = 1696988497122834654L; public NoAccessException(String info) { - super(HttpStatus.FORBIDDEN, "Hier hast du keinen Zugriff.", info); + super(HttpStatus.FORBIDDEN, "Kein Zugriff.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/NoAdminAfterActionException.java b/src/main/java/mops/gruppen2/domain/exception/NoAdminAfterActionException.java deleted file mode 100644 index 07a2afb..0000000 --- a/src/main/java/mops/gruppen2/domain/exception/NoAdminAfterActionException.java +++ /dev/null @@ -1,12 +0,0 @@ -package mops.gruppen2.domain.exception; - -import org.springframework.http.HttpStatus; - -public class NoAdminAfterActionException extends EventException { - - private static final long serialVersionUID = 9059481382346544288L; - - public NoAdminAfterActionException(String info) { - super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Gruppe braucht einen Admin.", info); - } -} diff --git a/src/main/java/mops/gruppen2/domain/exception/PageNotFoundException.java b/src/main/java/mops/gruppen2/domain/exception/PageNotFoundException.java index a54d2e6..0871621 100644 --- a/src/main/java/mops/gruppen2/domain/exception/PageNotFoundException.java +++ b/src/main/java/mops/gruppen2/domain/exception/PageNotFoundException.java @@ -7,7 +7,7 @@ public class PageNotFoundException extends EventException { private static final long serialVersionUID = 2374509005158710104L; public PageNotFoundException(String info) { - super(HttpStatus.NOT_FOUND, "Die Seite wurde nicht gefunden!", info); + super(HttpStatus.NOT_FOUND, "Seite wurde nicht gefunden.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java b/src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java index bb1d338..d0288cd 100644 --- a/src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java +++ b/src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java @@ -7,7 +7,7 @@ public class UserAlreadyExistsException extends EventException { private static final long serialVersionUID = -8150634358760194625L; public UserAlreadyExistsException(String info) { - super(HttpStatus.INTERNAL_SERVER_ERROR, "Der User existiert bereits.", info); + super(HttpStatus.INTERNAL_SERVER_ERROR, "User existiert bereits.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java b/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java index 43c90e9..e1273df 100644 --- a/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java +++ b/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java @@ -7,7 +7,7 @@ public class UserNotFoundException extends EventException { private static final long serialVersionUID = 8347442921199785291L; public UserNotFoundException(String info) { - super(HttpStatus.NOT_FOUND, "Der User existiert nicht.", info); + super(HttpStatus.NOT_FOUND, "User existiert nicht.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/exception/WrongFileException.java b/src/main/java/mops/gruppen2/domain/exception/WrongFileException.java index 63dbee6..45fcaa7 100644 --- a/src/main/java/mops/gruppen2/domain/exception/WrongFileException.java +++ b/src/main/java/mops/gruppen2/domain/exception/WrongFileException.java @@ -7,7 +7,7 @@ public class WrongFileException extends EventException { private static final long serialVersionUID = -166192514348555116L; public WrongFileException(String info) { - super(HttpStatus.BAD_REQUEST, "Die Datei ist keine valide CSV-Datei!", info); + super(HttpStatus.BAD_REQUEST, "Datei ist keine valide CSV-Datei.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/helper/APIHelper.java b/src/main/java/mops/gruppen2/domain/helper/APIHelper.java index 5e7d04f..5da7990 100644 --- a/src/main/java/mops/gruppen2/domain/helper/APIHelper.java +++ b/src/main/java/mops/gruppen2/domain/helper/APIHelper.java @@ -1,16 +1,18 @@ package mops.gruppen2.domain.helper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.model.Group; +import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.web.api.GroupRequestWrapper; import java.util.List; +//TODO: sinnvolles format @Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class APIHelper { - private APIHelper() {} - public static GroupRequestWrapper wrap(long status, List groupList) { return new GroupRequestWrapper(status, groupList); } diff --git a/src/main/java/mops/gruppen2/domain/helper/CommonHelper.java b/src/main/java/mops/gruppen2/domain/helper/CommonHelper.java new file mode 100644 index 0000000..ce01dc7 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/helper/CommonHelper.java @@ -0,0 +1,35 @@ +package mops.gruppen2.domain.helper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.event.EventType; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class CommonHelper { + + public static String[] eventTypesToString(EventType... types) { + String[] stringtypes = new String[types.length]; + + for (int i = 0; i < types.length; i++) { + stringtypes[i] = types[i].toString(); + } + + return stringtypes; + } + + public static List uuidsToString(List ids) { + return ids.stream() + .map(UUID::toString) + .collect(Collectors.toList()); + } + + public static boolean uuidIsEmpty(UUID uuid) { + return "00000000-0000-0000-0000-000000000000".equals(uuid.toString()); + } +} diff --git a/src/main/java/mops/gruppen2/domain/helper/CsvHelper.java b/src/main/java/mops/gruppen2/domain/helper/CsvHelper.java index 3af7920..5a43b85 100644 --- a/src/main/java/mops/gruppen2/domain/helper/CsvHelper.java +++ b/src/main/java/mops/gruppen2/domain/helper/CsvHelper.java @@ -3,10 +3,12 @@ package mops.gruppen2.domain.helper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.exception.WrongFileException; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.User; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -16,10 +18,9 @@ import java.util.List; import java.util.stream.Collectors; @Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class CsvHelper { - private CsvHelper() {} - public static List readCsvFile(MultipartFile file) throws EventException { if (file == null || file.isEmpty()) { return Collections.emptyList(); diff --git a/src/main/java/mops/gruppen2/domain/helper/IdHelper.java b/src/main/java/mops/gruppen2/domain/helper/IdHelper.java deleted file mode 100644 index e8e4f32..0000000 --- a/src/main/java/mops/gruppen2/domain/helper/IdHelper.java +++ /dev/null @@ -1,49 +0,0 @@ -package mops.gruppen2.domain.helper; - -import lombok.extern.log4j.Log4j2; - -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@Log4j2 -public final class IdHelper { - - private IdHelper() {} - - public static List stringsToUUID(List groupIds) { - return groupIds.stream() - .map(IdHelper::stringToUUID) - .collect(Collectors.toList()); - } - - /** - * Wandelt einen String in eine UUID um. - * Dabei wird eine "leere" UUID generiert, falls der String leer ist. - * - * @param groupId Id als String - * - * @return Id als UUID - */ - public static UUID stringToUUID(String groupId) { - return groupId.isEmpty() ? emptyUUID() : UUID.fromString(groupId); - } - - public static List uuidsToString(List groupIds) { - return groupIds.stream() - .map(UUID::toString) - .collect(Collectors.toList()); - } - - public static String uuidToString(UUID groupId) { - return groupId.toString(); - } - - public static boolean isEmpty(UUID id) { - return id == null || emptyUUID().equals(id); - } - - public static UUID emptyUUID() { - return UUID.fromString("00000000-0000-0000-0000-000000000000"); - } -} diff --git a/src/main/java/mops/gruppen2/domain/helper/JsonHelper.java b/src/main/java/mops/gruppen2/domain/helper/JsonHelper.java index 93ea49b..614e929 100644 --- a/src/main/java/mops/gruppen2/domain/helper/JsonHelper.java +++ b/src/main/java/mops/gruppen2/domain/helper/JsonHelper.java @@ -2,6 +2,9 @@ package mops.gruppen2.domain.helper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.event.Event; @@ -9,10 +12,9 @@ import mops.gruppen2.domain.event.Event; * Übersetzt JSON-Event-Payloads zu Java-Event-Repräsentationen und zurück. */ @Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class JsonHelper { - private JsonHelper() {} - /** * Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload. * @@ -24,7 +26,7 @@ public final class JsonHelper { */ public static String serializeEvent(Event event) throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); String payload = mapper.writeValueAsString(event); log.trace(payload); return payload; @@ -40,7 +42,9 @@ public final class JsonHelper { * @throws JsonProcessingException Bei JSON Fehler */ public static Event deserializeEvent(String json) throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(json, Event.class); + ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + Event event = mapper.readValue(json, Event.class); + log.trace(event); + return event; } } diff --git a/src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java b/src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java index 98e17ee..02cdc2e 100644 --- a/src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java +++ b/src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java @@ -1,118 +1,105 @@ package mops.gruppen2.domain.helper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.exception.BadParameterException; +import mops.gruppen2.domain.exception.BadArgumentException; import mops.gruppen2.domain.exception.GroupFullException; +import mops.gruppen2.domain.exception.LastAdminException; import mops.gruppen2.domain.exception.NoAccessException; -import mops.gruppen2.domain.exception.NoAdminAfterActionException; import mops.gruppen2.domain.exception.UserAlreadyExistsException; import mops.gruppen2.domain.exception.UserNotFoundException; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; -import mops.gruppen2.web.form.CreateForm; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; -import static mops.gruppen2.domain.model.Role.ADMIN; - @Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ValidationHelper { - private ValidationHelper() {} - - - // ######################################## CHECK ############################################ - - /** * Überprüft, ob ein User in einer Gruppe teilnimmt. */ - public static boolean checkIfMember(Group group, User user) { - return group.getMembers().containsKey(user.getUserid()); + public static boolean checkIfMember(Group group, String userid) { + return group.isMember(userid); } - public static boolean checkIfLastMember(User user, Group group) { - return checkIfMember(group, user) && group.getMembers().size() == 1; + public static boolean checkIfLastMember(Group group, String userid) { + return checkIfMember(group, userid) && group.size() == 1; } /** * Überprüft, ob eine Gruppe voll ist. */ public static boolean checkIfGroupFull(Group group) { - return group.getMembers().size() >= group.getLimit().getUserLimit(); + return group.isFull(); } /** * Überprüft, ob eine Gruppe leer ist. */ public static boolean checkIfGroupEmpty(Group group) { - return group.getMembers().isEmpty(); + return group.isEmpty(); } /** * Überprüft, ob ein User in einer Gruppe Admin ist. */ - public static boolean checkIfAdmin(Group group, User user) { - if (checkIfMember(group, user)) { - return group.getRoles().get(user.getUserid()) == ADMIN; + public static boolean checkIfAdmin(Group group, String userid) { + if (checkIfMember(group, userid)) { + return group.isAdmin(userid); } return false; } - public static boolean checkIfLastAdmin(User user, Group group) { - return checkIfAdmin(group, user) && group.getRoles().values().stream() - .filter(role -> role == ADMIN) - .count() == 1; + public static boolean checkIfLastAdmin(Group group, String userid) { + return checkIfAdmin(group, userid) && group.getAdmins().size() == 1; } // ######################################## THROW ############################################ - public static void throwIfMember(Group group, User user) { - if (checkIfMember(group, user)) { - log.error("Benutzer {} ist schon in Gruppe {}", user, group); - throw new UserAlreadyExistsException(user.toString()); + public static void throwIfMember(Group group, String userid) throws UserAlreadyExistsException { + if (checkIfMember(group, userid)) { + log.error("Benutzer {} ist schon in Gruppe {}", userid, group); + throw new UserAlreadyExistsException(userid); } } - public static void throwIfNoMember(Group group, User user) { - if (!checkIfMember(group, user)) { - log.error("Benutzer {} ist nicht in Gruppe {}!", user, group); - throw new UserNotFoundException(user.toString()); + public static void throwIfNoMember(Group group, String userid) throws UserNotFoundException { + if (!checkIfMember(group, userid)) { + log.error("Benutzer {} ist nicht in Gruppe {}!", userid, group); + throw new UserNotFoundException(userid); } } - public static void throwIfNoAdmin(Group group, User user) { - if (!checkIfAdmin(group, user)) { - log.error("User {} ist kein Admin in Gruppe {}!", user, group); - throw new NoAccessException(group.toString()); + public static void throwIfNoAdmin(Group group, String userid) throws NoAccessException { + if (!checkIfAdmin(group, userid)) { + log.error("User {} ist kein Admin in Gruppe {}!", userid, group); + throw new NoAccessException(group.getId().toString()); } } /** * Schmeißt keine Exception, wenn der User der letzte User ist. */ - public static void throwIfLastAdmin(User user, Group group) { - if (!checkIfLastMember(user, group) && checkIfLastAdmin(user, group)) { - throw new NoAdminAfterActionException("Du bist letzter Admin!"); + public static void throwIfLastAdmin(Group group, String userid) throws LastAdminException { + if (!checkIfLastMember(group, userid) && checkIfLastAdmin(group, userid)) { + throw new LastAdminException("Du bist letzter Admin!"); } } - public static void throwIfGroupFull(Group group) { + public static void throwIfGroupFull(Group group) throws GroupFullException { if (checkIfGroupFull(group)) { log.error("Die Gruppe {} ist voll!", group); - throw new GroupFullException(group.toString()); + throw new GroupFullException(group.getId().toString()); } } - - // ##################################### VALIDATE FIELDS ##################################### - - public static void validateCreateForm(KeycloakAuthenticationToken token, CreateForm form) { - if (!token.getAccount().getRoles().contains("orga") - && form.getType() == Type.LECTURE) { - throw new BadParameterException("Eine Veranstaltung kann nur von ORGA erstellt werden."); + public static void validateCreateForm(KeycloakAuthenticationToken token, Type type) { + if (!token.getAccount().getRoles().contains("orga") && type == Type.LECTURE) { + throw new BadArgumentException("Nur Orga kann Veranstaltungen erstellen."); } } } diff --git a/src/main/java/mops/gruppen2/domain/model/Group.java b/src/main/java/mops/gruppen2/domain/model/Group.java deleted file mode 100644 index bd84a2d..0000000 --- a/src/main/java/mops/gruppen2/domain/model/Group.java +++ /dev/null @@ -1,39 +0,0 @@ -package mops.gruppen2.domain.model; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * Repräsentiert den aggregierten Zustand einer Gruppe. - */ -@Getter -@Setter -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@ToString -public class Group { - - @EqualsAndHashCode.Include - private UUID groupid; - - @ToString.Exclude - private UUID parent; - - private Type type; - - private Title title; - private Description description; - - @ToString.Exclude - private Limit limit = new Limit(1); // Add initial user - - @ToString.Exclude - private final Map members = new HashMap<>(); - @ToString.Exclude - private final Map roles = new HashMap<>(); -} diff --git a/src/main/java/mops/gruppen2/domain/model/Role.java b/src/main/java/mops/gruppen2/domain/model/Role.java deleted file mode 100644 index 1b66370..0000000 --- a/src/main/java/mops/gruppen2/domain/model/Role.java +++ /dev/null @@ -1,10 +0,0 @@ -package mops.gruppen2.domain.model; - -public enum Role { - ADMIN, - MEMBER; - - public Role toggle() { - return this == ADMIN ? MEMBER : ADMIN; - } -} diff --git a/src/main/java/mops/gruppen2/domain/model/group/Group.java b/src/main/java/mops/gruppen2/domain/model/group/Group.java new file mode 100644 index 0000000..bdd1fba --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/Group.java @@ -0,0 +1,293 @@ +package mops.gruppen2.domain.model.group; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.BadArgumentException; +import mops.gruppen2.domain.exception.GroupFullException; +import mops.gruppen2.domain.exception.IdMismatchException; +import mops.gruppen2.domain.exception.LastAdminException; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.exception.UserAlreadyExistsException; +import mops.gruppen2.domain.exception.UserNotFoundException; +import mops.gruppen2.domain.helper.ValidationHelper; +import mops.gruppen2.domain.model.group.wrapper.Body; +import mops.gruppen2.domain.model.group.wrapper.Description; +import mops.gruppen2.domain.model.group.wrapper.Limit; +import mops.gruppen2.domain.model.group.wrapper.Link; +import mops.gruppen2.domain.model.group.wrapper.Parent; +import mops.gruppen2.domain.model.group.wrapper.Title; + +import javax.validation.Valid; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * Repräsentiert den aggregierten Zustand einer Gruppe. + * + *

+ * Muss beim Start gesetzt werden: groupid, meta + */ +@Log4j2 +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString(onlyExplicitlyIncluded = true) +public class Group { + + // Metainformationen + @EqualsAndHashCode.Include + @ToString.Include + private UUID groupid; + + @Getter + private Type type = Type.PRIVATE; + + private Parent parent = Parent.EMPTY(); + + private Limit limit = Limit.DEFAULT(); // Add initial user + + private Link link = Link.RANDOM(); + + @ToString.Include + private GroupMeta meta = GroupMeta.EMPTY(); + + private GroupOptions options = GroupOptions.DEFAULT(); + + //@ToString.Exclude + //private LocalDateTime age; + + // Inhalt + private Title title; + + private Description description; + + private Body body; + + // Integrationen + + // Teilnehmer + private Map memberships = new HashMap<>(); + + + // ####################################### Members ########################################### + + public List getMembers() { + return SortHelper.sortByMemberRole(new ArrayList<>(memberships.values())).stream() + .map(Membership::getUser) + .collect(Collectors.toList()); + } + + public List getRegulars() { + return memberships.values().stream() + .map(Membership::getUser) + .filter(member -> isRegular(member.getId())) + .collect(Collectors.toList()); + } + + public List getAdmins() { + return memberships.values().stream() + .map(Membership::getUser) + .filter(member -> isAdmin(member.getId())) + .collect(Collectors.toList()); + } + + public Role getRole(String userid) { + return memberships.get(userid).getRole(); + } + + public void addMember(String target, User user) throws UserAlreadyExistsException, GroupFullException { + ValidationHelper.throwIfMember(this, target); + ValidationHelper.throwIfGroupFull(this); + + memberships.put(target, new Membership(user, Role.REGULAR)); + } + + public void kickMember(String target) throws UserNotFoundException, LastAdminException { + ValidationHelper.throwIfNoMember(this, target); + ValidationHelper.throwIfLastAdmin(this, target); + + memberships.remove(target); + } + + public void memberPutRole(String target, Role role) throws UserNotFoundException, LastAdminException { + ValidationHelper.throwIfNoMember(this, target); + if (role == Role.REGULAR) { + ValidationHelper.throwIfLastAdmin(this, target); + } + + memberships.put(target, memberships.get(target).setRole(role)); + } + + public boolean isMember(String target) { + return memberships.containsKey(target); + } + + public boolean isAdmin(String target) throws UserNotFoundException { + ValidationHelper.throwIfNoMember(this, target); + + return memberships.get(target).getRole() == Role.ADMIN; + } + + public boolean isRegular(String target) throws UserNotFoundException { + ValidationHelper.throwIfNoMember(this, target); + + return memberships.get(target).getRole() == Role.REGULAR; + } + + + // ######################################### Getters ######################################### + + + public UUID getId() { + return groupid; + } + + public UUID getParent() { + return parent.getGroupid(); + } + + public long getLimit() { + return limit.getValue(); + } + + public String getTitle() { + return title.toString(); + } + + public String getDescription() { + return description.getValue(); + } + + public String getLink() { + return link.getValue(); + } + + public String creator() { + return meta.getCreator(); + } + + public long version() { + return meta.getVersion(); + } + + public LocalDateTime creationDate() { + return meta.getCreationDate(); + } + + public int size() { + return memberships.size(); + } + + public boolean isFull() { + return size() >= limit.getValue(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public boolean isPublic() { + return type == Type.PUBLIC; + } + + public boolean isPrivate() { + return type == Type.PRIVATE; + } + + public boolean isLecture() { + return type == Type.LECTURE; + } + + + // ######################################## Setters ########################################## + + + public void setId(UUID groupid) throws BadArgumentException { + if (this.groupid != null) { + throw new BadArgumentException("GruppenId bereits gesetzt."); + } + + this.groupid = groupid; + } + + public void setType(String exec, Type type) throws NoAccessException { + ValidationHelper.throwIfNoAdmin(this, exec); + + this.type = type; + } + + public void setTitle(String exec, @Valid Title title) throws NoAccessException { + ValidationHelper.throwIfNoAdmin(this, exec); + + this.title = title; + } + + public void setDescription(String exec, @Valid Description description) throws NoAccessException { + ValidationHelper.throwIfNoAdmin(this, exec); + + this.description = description; + } + + public void setLimit(String exec, @Valid Limit limit) throws NoAccessException, BadArgumentException { + ValidationHelper.throwIfNoAdmin(this, exec); + + if (size() > limit.getValue()) { + throw new BadArgumentException("Das Userlimit ist zu klein für die Gruppe."); + } + + this.limit = limit; + } + + public void setParent(String exec, @Valid Parent parent) throws NoAccessException { + ValidationHelper.throwIfNoAdmin(this, exec); + + this.parent = parent; + } + + public void setLink(String exec, @Valid Link link) throws NoAccessException { + ValidationHelper.throwIfNoAdmin(this, exec); + + this.link = link; + } + + public void update(long version) throws IdMismatchException { + meta = meta.setVersion(version); + } + + public void setCreator(String target) throws BadArgumentException { + meta = meta.setCreator(target); + } + + public void setCreationDate(LocalDateTime date) throws BadArgumentException { + meta = meta.setCreationDate(date); + } + + + // ######################################### Util ############################################ + + + public void destroy(String userid) throws NoAccessException { + ValidationHelper.throwIfNoAdmin(this, userid); + + groupid = null; + parent = null; + type = null; + title = null; + description = null; + limit = null; + memberships = null; + link = null; + meta = null; + options = null; + body = null; + } + + public String format() { + return title + " " + description; + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java b/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java new file mode 100644 index 0000000..de60300 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java @@ -0,0 +1,47 @@ +package mops.gruppen2.domain.model.group; + +import lombok.ToString; +import lombok.Value; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.exception.BadArgumentException; +import mops.gruppen2.domain.exception.IdMismatchException; + +import java.time.LocalDateTime; + +@Log4j2 +@Value +@ToString +public class GroupMeta { + + long version; + String creator; + LocalDateTime creationDate; + + public GroupMeta setVersion(long version) throws IdMismatchException { + if (this.version >= version) { + throw new IdMismatchException("Die Gruppe ist bereits auf einem neueren Stand."); + } + + return new GroupMeta(version, creator, creationDate); + } + + public GroupMeta setCreator(String userid) throws BadArgumentException { + if (creator != null) { + throw new BadArgumentException("Gruppe hat schon einen Ersteller."); + } + + return new GroupMeta(version, userid, creationDate); + } + + public GroupMeta setCreationDate(LocalDateTime date) throws BadArgumentException { + if (creationDate != null) { + throw new BadArgumentException("Gruppe hat schon ein Erstellungsdatum."); + } + + return new GroupMeta(version, creator, date); + } + + public static GroupMeta EMPTY() { + return new GroupMeta(0, null, null); + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java b/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java new file mode 100644 index 0000000..88d069a --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java @@ -0,0 +1,36 @@ +package mops.gruppen2.domain.model.group; + +import lombok.Value; + +//TODO: doooooodododo +@Value +class GroupOptions { + + // Gruppe + boolean showClearname; + boolean hasBody; + boolean isLeavable; + boolean hasLink; + + String customLogo; + String customBackground; + String customTitle; + + // Integrations + boolean hasMaterialIntegration; + boolean hasTermineIntegration; + boolean hasPortfolioIntegration; + + static GroupOptions DEFAULT() { + return new GroupOptions(true, + false, + true, + false, + null, + null, + null, + true, + true, + true); + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/group/Membership.java b/src/main/java/mops/gruppen2/domain/model/group/Membership.java new file mode 100644 index 0000000..7a730ce --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/Membership.java @@ -0,0 +1,23 @@ +package mops.gruppen2.domain.model.group; + +import lombok.EqualsAndHashCode; +import lombok.Value; + +@Value +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class Membership { + + User user; + Role role; + + // LocalDateTime age; + + @Override + public String toString() { + return user.format() + ": " + role; + } + + public Membership setRole(Role role) { + return new Membership(user, role); + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/group/Role.java b/src/main/java/mops/gruppen2/domain/model/group/Role.java new file mode 100644 index 0000000..0b7c4d5 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/Role.java @@ -0,0 +1,10 @@ +package mops.gruppen2.domain.model.group; + +public enum Role { + ADMIN, + REGULAR; + + public Role toggle() { + return this == ADMIN ? REGULAR : ADMIN; + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/group/SortHelper.java b/src/main/java/mops/gruppen2/domain/model/group/SortHelper.java new file mode 100644 index 0000000..f1f2e36 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/SortHelper.java @@ -0,0 +1,45 @@ +package mops.gruppen2.domain.model.group; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class SortHelper { + + /** + * Sortiert die übergebene Liste an Gruppen, sodass Veranstaltungen am Anfang der Liste sind. + * + * @param groups Die Liste von Gruppen die sortiert werden soll + */ + public static List sortByGroupType(List groups) { + groups.sort((Group g1, Group g2) -> { + if (g1.getType() == Type.LECTURE) { + return -1; + } + if (g2.getType() == Type.LECTURE) { + return 1; + } + + return 0; + }); + + return groups; + } + + public static List sortByMemberRole(List memberships) { + memberships.sort((Membership m1, Membership m2) -> { + if (m1.getRole() == Role.ADMIN) { + return -1; + } + if (m2.getRole() == Role.ADMIN) { + return 1; + } + + return 0; + }); + + return memberships; + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/Type.java b/src/main/java/mops/gruppen2/domain/model/group/Type.java similarity index 58% rename from src/main/java/mops/gruppen2/domain/model/Type.java rename to src/main/java/mops/gruppen2/domain/model/group/Type.java index 7a0740a..eae882e 100644 --- a/src/main/java/mops/gruppen2/domain/model/Type.java +++ b/src/main/java/mops/gruppen2/domain/model/group/Type.java @@ -1,4 +1,4 @@ -package mops.gruppen2.domain.model; +package mops.gruppen2.domain.model.group; public enum Type { PUBLIC, diff --git a/src/main/java/mops/gruppen2/domain/model/User.java b/src/main/java/mops/gruppen2/domain/model/group/User.java similarity index 69% rename from src/main/java/mops/gruppen2/domain/model/User.java rename to src/main/java/mops/gruppen2/domain/model/group/User.java index 98f60d1..07ec8f1 100644 --- a/src/main/java/mops/gruppen2/domain/model/User.java +++ b/src/main/java/mops/gruppen2/domain/model/group/User.java @@ -1,26 +1,32 @@ -package mops.gruppen2.domain.model; +package mops.gruppen2.domain.model.group; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; -import lombok.ToString; +import lombok.Getter; import lombok.Value; +import lombok.extern.log4j.Log4j2; import org.keycloak.KeycloakPrincipal; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +@Log4j2 @Value @AllArgsConstructor -@ToString public class User { @EqualsAndHashCode.Include + @Getter(AccessLevel.NONE) + @JsonProperty("id") String userid; + @JsonProperty("givenname") String givenname; - @ToString.Exclude + @JsonProperty("familyname") String familyname; - @ToString.Exclude + @JsonProperty("mail") String email; public User(KeycloakAuthenticationToken token) { @@ -42,4 +48,12 @@ public class User { familyname = ""; email = ""; } + + public String getId() { + return userid; + } + + public String format() { + return givenname + " " + familyname; + } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/wrapper/Body.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Body.java new file mode 100644 index 0000000..791b527 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Body.java @@ -0,0 +1,8 @@ +package mops.gruppen2.domain.model.group.wrapper; + +import lombok.Value; + +//TODO: do it +@Value +public class Body { +} diff --git a/src/main/java/mops/gruppen2/domain/model/Description.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Description.java similarity index 56% rename from src/main/java/mops/gruppen2/domain/model/Description.java rename to src/main/java/mops/gruppen2/domain/model/group/wrapper/Description.java index 7db3a30..c37c29a 100644 --- a/src/main/java/mops/gruppen2/domain/model/Description.java +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Description.java @@ -1,21 +1,21 @@ -package mops.gruppen2.domain.model; +package mops.gruppen2.domain.model.group.wrapper; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; -import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @Value public class Description { - @NotBlank + @NotNull @Size(min = 4, max = 512) - @JsonProperty("desc") - String groupDescription; + @JsonProperty("value") + String value; @Override public String toString() { - return groupDescription; + return value; } } diff --git a/src/main/java/mops/gruppen2/domain/model/Limit.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Limit.java similarity index 51% rename from src/main/java/mops/gruppen2/domain/model/Limit.java rename to src/main/java/mops/gruppen2/domain/model/group/wrapper/Limit.java index 60c0e08..b06d69e 100644 --- a/src/main/java/mops/gruppen2/domain/model/Limit.java +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Limit.java @@ -1,21 +1,27 @@ -package mops.gruppen2.domain.model; +package mops.gruppen2.domain.model.group.wrapper; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; import javax.validation.constraints.Max; import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; @Value public class Limit { + @NotNull @Min(1) @Max(999_999) - @JsonProperty("limit") - long userLimit; + @JsonProperty("value") + long value; + + public static Limit DEFAULT() { + return new Limit(1); + } @Override public String toString() { - return String.valueOf(userLimit); + return String.valueOf(value); } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/wrapper/Link.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Link.java new file mode 100644 index 0000000..a79db9a --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Link.java @@ -0,0 +1,21 @@ +package mops.gruppen2.domain.model.group.wrapper; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.UUID; + +@Value +public class Link { + + @NotNull + @Size(min = 36, max = 36) + @JsonProperty("value") + String value; + + public static Link RANDOM() { + return new Link(UUID.randomUUID().toString()); + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java new file mode 100644 index 0000000..aaa93d0 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java @@ -0,0 +1,34 @@ +package mops.gruppen2.domain.model.group.wrapper; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; +import mops.gruppen2.domain.helper.CommonHelper; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.beans.ConstructorProperties; +import java.util.UUID; + +@Value +public class Parent { + + @NotNull + @JsonProperty("id") + UUID groupid; + + @ConstructorProperties("id") + public Parent(@NotBlank @Size(min = 36, max = 36) String parentid) { + groupid = UUID.fromString(parentid); + } + + public static Parent EMPTY() { + return new Parent("00000000-0000-0000-0000-000000000000"); + } + + @JsonIgnore + public boolean isEmpty() { + return CommonHelper.uuidIsEmpty(groupid); + } +} diff --git a/src/main/java/mops/gruppen2/domain/model/Title.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Title.java similarity index 57% rename from src/main/java/mops/gruppen2/domain/model/Title.java rename to src/main/java/mops/gruppen2/domain/model/group/wrapper/Title.java index 6c410df..2bcc8ee 100644 --- a/src/main/java/mops/gruppen2/domain/model/Title.java +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Title.java @@ -1,21 +1,21 @@ -package mops.gruppen2.domain.model; +package mops.gruppen2.domain.model.group.wrapper; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Value; -import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @Value public class Title { - @NotBlank + @NotNull @Size(min = 4, max = 128) - @JsonProperty("title") - String groupTitle; + @JsonProperty("value") + String value; @Override public String toString() { - return groupTitle; + return value; } } diff --git a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java index a2b8eaa..4ee3328 100644 --- a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java +++ b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java @@ -1,35 +1,40 @@ package mops.gruppen2.domain.service; import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.event.AddUserEvent; +import mops.gruppen2.aspect.annotation.TraceMethodCalls; +import mops.gruppen2.domain.event.AddMemberEvent; import mops.gruppen2.domain.event.CreateGroupEvent; import mops.gruppen2.domain.event.Event; +import mops.gruppen2.domain.event.EventType; import mops.gruppen2.domain.exception.BadPayloadException; -import mops.gruppen2.domain.helper.IdHelper; +import mops.gruppen2.domain.exception.InvalidInviteException; import mops.gruppen2.domain.helper.JsonHelper; -import mops.gruppen2.domain.model.User; import mops.gruppen2.persistance.EventRepository; import mops.gruppen2.persistance.dto.EventDTO; import org.springframework.stereotype.Service; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -@Service +import static mops.gruppen2.domain.event.EventType.CREATEGROUP; +import static mops.gruppen2.domain.event.EventType.DESTROYGROUP; +import static mops.gruppen2.domain.event.EventType.SETLINK; +import static mops.gruppen2.domain.helper.CommonHelper.eventTypesToString; +import static mops.gruppen2.domain.helper.CommonHelper.uuidsToString; + @Log4j2 +@RequiredArgsConstructor +@Service +@TraceMethodCalls public class EventStoreService { private final EventRepository eventStore; - public EventStoreService(EventRepository eventStore) { - this.eventStore = eventStore; - } - //########################################### SAVE ########################################### @@ -67,7 +72,7 @@ public class EventStoreService { //########################################### DTOs ########################################### - static List getDTOsFromEvents(List events) { + private static List getDTOsFromEvents(List events) { return events.stream() .map(EventStoreService::getDTOFromEvent) .collect(Collectors.toList()); @@ -80,13 +85,15 @@ public class EventStoreService { * * @return EventDTO (Neues DTO) */ - static EventDTO getDTOFromEvent(Event event) { + private static EventDTO getDTOFromEvent(Event event) { try { String payload = JsonHelper.serializeEvent(event); return new EventDTO(null, event.getGroupid().toString(), - event.getUserid(), - getEventType(event), + event.getVersion(), + event.getExec(), + event.getTarget(), + event.getType(), payload); } catch (JsonProcessingException e) { log.error("Event ({}) konnte nicht serialisiert werden!", event, e); @@ -116,19 +123,6 @@ public class EventStoreService { } } - /** - * Gibt den Eventtyp als String wieder. - * - * @param event Event dessen Typ abgefragt werden soll - * - * @return Der Name des Typs des Events - */ - private static String getEventType(Event event) { - int lastDot = event.getClass().getName().lastIndexOf('.'); - - return event.getClass().getName().substring(lastDot + 1); - } - // ######################################## QUERIES ########################################## @@ -176,8 +170,8 @@ public class EventStoreService { * @return GruppenIds (UUID) als Liste */ List findExistingGroupIds() { - List createEvents = findLatestEventsFromGroupsByType("CreateGroupEvent", - "DeleteGroupEvent"); + List createEvents = findLatestEventsFromGroupsByType(CREATEGROUP, + DESTROYGROUP); return createEvents.stream() .filter(event -> event instanceof CreateGroupEvent) @@ -188,11 +182,18 @@ public class EventStoreService { /** * Liefert Gruppen-Ids von existierenden (ungelöschten) Gruppen, in welchen der User teilnimmt. * + *

+ * Vorgang: + * Finde für jede Gruppe das letzte Add- oder Kick-Event, welches den User betrifft + * Finde für jede Gruppe das letzte Destroy-Event + * Entferne alle alle Events von Gruppen, welche ein Destroy-Event haben + * Gebe die Gruppen zurück, auf welche sich die Add-Events beziehen + * * @return GruppenIds (UUID) als Liste */ - public List findExistingUserGroups(User user) { - List userEvents = findLatestEventsFromGroupsByUser(user); - List deletedIds = findLatestEventsFromGroupsByType("DeleteGroupEvent") + public List findExistingUserGroups(String userid) { + List userEvents = findLatestEventsFromGroupsByUser(userid); + List deletedIds = findLatestEventsFromGroupsByType(DESTROYGROUP) .stream() .map(Event::getGroupid) .collect(Collectors.toList()); @@ -200,11 +201,25 @@ public class EventStoreService { userEvents.removeIf(event -> deletedIds.contains(event.getGroupid())); return userEvents.stream() - .filter(event -> event instanceof AddUserEvent) + .filter(event -> event instanceof AddMemberEvent) .map(Event::getGroupid) .collect(Collectors.toList()); } + public UUID findGroupByLink(String link) { + List groupEvents = findEventsByType(eventTypesToString(SETLINK)); + + if (groupEvents.size() > 1) { + throw new InvalidInviteException("Es existieren mehrere Gruppen mit demselben Link."); + } + + if (groupEvents.isEmpty()) { + throw new InvalidInviteException("Link nicht gefunden."); + } + + return groupEvents.get(0).getGroupid(); + } + // #################################### SIMPLE QUERIES ####################################### @@ -219,32 +234,32 @@ public class EventStoreService { return eventStore.findMaxEventId(); } catch (NullPointerException e) { log.debug("Keine Events vorhanden!"); - return 0; + return 1; } } List findEventsByType(String... types) { - return getEventsFromDTOs(eventStore.findEventDTOsByType(Arrays.asList(types))); + return getEventsFromDTOs(eventStore.findEventDTOsByType(types)); } List findEventsByType(String type) { - return getEventsFromDTOs(eventStore.findEventDTOsByType(Collections.singletonList(type))); + return getEventsFromDTOs(eventStore.findEventDTOsByType(type)); } List findEventsByGroupAndType(List groupIds, String... types) { - return getEventsFromDTOs(eventStore.findEventDTOsByGroupAndType(Arrays.asList(types), - IdHelper.uuidsToString(groupIds))); + return getEventsFromDTOs(eventStore.findEventDTOsByGroupAndType(uuidsToString(groupIds), + types)); } /** * Sucht zu jeder Gruppe das letzte Add- oder DeleteUserEvent heraus, welches den übergebenen User betrifft. * - * @param user User, zu welchem die Events gesucht werden + * @param userid User, zu welchem die Events gesucht werden * * @return Eine Liste von einem Add- oder DeleteUserEvent pro Gruppe */ - private List findLatestEventsFromGroupsByUser(User user) { - return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(user.getUserid())); + private List findLatestEventsFromGroupsByUser(String userid) { + return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(userid)); } @@ -255,7 +270,7 @@ public class EventStoreService { * * @return Eine Liste von einem Event pro Gruppe */ - private List findLatestEventsFromGroupsByType(String... types) { - return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByType(Arrays.asList(types))); + private List findLatestEventsFromGroupsByType(EventType... types) { + return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByType(eventTypesToString(types))); } } diff --git a/src/main/java/mops/gruppen2/domain/service/GroupService.java b/src/main/java/mops/gruppen2/domain/service/GroupService.java index 4fd0494..237db1d 100644 --- a/src/main/java/mops/gruppen2/domain/service/GroupService.java +++ b/src/main/java/mops/gruppen2/domain/service/GroupService.java @@ -1,26 +1,33 @@ package mops.gruppen2.domain.service; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.event.AddUserEvent; +import mops.gruppen2.domain.event.AddMemberEvent; import mops.gruppen2.domain.event.CreateGroupEvent; -import mops.gruppen2.domain.event.DeleteGroupEvent; -import mops.gruppen2.domain.event.DeleteUserEvent; +import mops.gruppen2.domain.event.DestroyGroupEvent; import mops.gruppen2.domain.event.Event; -import mops.gruppen2.domain.event.UpdateGroupDescriptionEvent; -import mops.gruppen2.domain.event.UpdateGroupTitleEvent; +import mops.gruppen2.domain.event.KickMemberEvent; +import mops.gruppen2.domain.event.SetDescriptionEvent; +import mops.gruppen2.domain.event.SetInviteLinkEvent; +import mops.gruppen2.domain.event.SetLimitEvent; +import mops.gruppen2.domain.event.SetParentEvent; +import mops.gruppen2.domain.event.SetTitleEvent; +import mops.gruppen2.domain.event.SetTypeEvent; import mops.gruppen2.domain.event.UpdateRoleEvent; -import mops.gruppen2.domain.event.UpdateUserLimitEvent; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Description; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Limit; -import mops.gruppen2.domain.model.Role; -import mops.gruppen2.domain.model.Title; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Role; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.domain.model.group.wrapper.Description; +import mops.gruppen2.domain.model.group.wrapper.Limit; +import mops.gruppen2.domain.model.group.wrapper.Link; +import mops.gruppen2.domain.model.group.wrapper.Parent; +import mops.gruppen2.domain.model.group.wrapper.Title; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @@ -28,51 +35,47 @@ import java.util.UUID; * Behandelt Aufgaben, welche sich auf eine Gruppe beziehen. * Es werden übergebene Gruppen bearbeitet und dementsprechend Events erzeugt und gespeichert. */ -@Service @Log4j2 +@RequiredArgsConstructor +@Service public class GroupService { private final EventStoreService eventStoreService; - private final InviteService inviteService; - - public GroupService(EventStoreService eventStoreService, InviteService inviteService) { - this.eventStoreService = eventStoreService; - this.inviteService = inviteService; - } - // ################################# GRUPPE ERSTELLEN ######################################## - /** - * Erzeugt eine neue Gruppe und erzeugt nötige Events für die Initiale Setzung der Attribute. - * - * @param user Keycloak-Account - * @param title Gruppentitel - * @param description Gruppenbeschreibung - */ - public Group createGroup(User user, - Title title, - Description description, - Type type, - Limit userLimit, - UUID parent) { + public Group createGroup(String exec) { + return createGroup(UUID.randomUUID(), exec, LocalDateTime.now()); + } - // Regeln: - // isPrivate -> !isLecture - // isLecture -> !isPrivate - Group group = createGroup(user, parent, type); + public void initGroupMembers(Group group, + String exec, + String target, + User user, + Limit limit) { - // Die Reihenfolge ist wichtig, da der ausführende User Admin sein muss - addUser(user, group); - updateRole(user, group, Role.ADMIN); - updateTitle(user, group, title); - updateDescription(user, group, description); - updateUserLimit(user, group, userLimit); + addMember(group, exec, target, user); + updateRole(group, exec, target, Role.ADMIN); + setLimit(group, exec, limit); + } - inviteService.createLink(group); + public void initGroupMeta(Group group, + String exec, + Type type, + Parent parent) { - return group; + setType(group, exec, type); + setParent(group, exec, parent); + } + + public void initGroupText(Group group, + String exec, + Title title, + Description description) { + + setTitle(group, exec, title); + setDescription(group, exec, description); } @@ -87,12 +90,12 @@ public class GroupService { * * @param newUsers Userliste * @param group Gruppe - * @param user Ausführender User + * @param exec Ausführender User */ - public void addUsersToGroup(List newUsers, Group group, User user) { - updateUserLimit(user, group, getAdjustedUserLimit(newUsers, group)); + public void addUsersToGroup(Group group, String exec, List newUsers) { + setLimit(group, exec, getAdjustedUserLimit(newUsers, group)); - newUsers.forEach(newUser -> addUserSilent(newUser, group)); + newUsers.forEach(newUser -> addUserSilent(group, exec, newUser.getId(), newUser)); } /** @@ -106,24 +109,20 @@ public class GroupService { * @return Das neue Teilnehmermaximum */ private static Limit getAdjustedUserLimit(List newUsers, Group group) { - return new Limit(Math.max((long) group.getMembers().size() + newUsers.size(), group.getLimit().getUserLimit())); + return new Limit(Math.max((long) group.size() + newUsers.size(), group.getLimit())); } /** * Wechselt die Rolle eines Teilnehmers von Admin zu Member oder andersherum. * Überprüft, ob der User Mitglied ist und ob er der letzte Admin ist. * - * @param user Teilnehmer, welcher geändert wird - * @param group Gruppe, in welcher sih der Teilnehmer befindet + * @param target Teilnehmer, welcher geändert wird + * @param group Gruppe, in welcher sih der Teilnehmer befindet * * @throws EventException Falls der User nicht gefunden wird */ - public void toggleMemberRole(User user, Group group) throws EventException { - ValidationHelper.throwIfNoMember(group, user); - ValidationHelper.throwIfLastAdmin(user, group); - - Role role = group.getRoles().get(user.getUserid()); - updateRole(user, group, role.toggle()); + public void toggleMemberRole(Group group, String exec, String target) { + updateRole(group, exec, target, group.getRole(target).toggle()); } @@ -134,59 +133,44 @@ public class GroupService { /** * Erzeugt eine Gruppe, speichert diese und gibt diese zurück. */ - private Group createGroup(User user, UUID parent, Type type) { - Event event = new CreateGroupEvent(UUID.randomUUID(), - user, - parent, - type); + private Group createGroup(UUID groupid, String exec, LocalDateTime date) { + Event event = new CreateGroupEvent(groupid, + exec, + date); Group group = new Group(); - event.apply(group); - - eventStoreService.saveEvent(event); + applyAndSave(group, event); return group; } - /** - * Erzeugt, speichert ein AddUserEvent und wendet es auf eine Gruppe an. - * Prüft, ob der Nutzer schon Mitglied ist und ob Gruppe voll ist. - */ - public void addUser(User user, Group group) { - ValidationHelper.throwIfMember(group, user); - ValidationHelper.throwIfGroupFull(group); - - Event event = new AddUserEvent(group, user); - event.apply(group); - - eventStoreService.saveEvent(event); - } - /** * Dasselbe wie addUser(), aber exceptions werden abgefangen und nicht geworfen. */ - private void addUserSilent(User user, Group group) { + private void addUserSilent(Group group, String exec, String target, User user) { try { - addUser(user, group); + addMember(group, exec, target, user); } catch (Exception e) { log.debug("Doppelter User {} wurde nicht zu Gruppe {} hinzugefügt!", user, group); } } + /** + * Erzeugt, speichert ein AddUserEvent und wendet es auf eine Gruppe an. + * Prüft, ob der Nutzer schon Mitglied ist und ob Gruppe voll ist. + */ + public void addMember(Group group, String exec, String target, User user) { + applyAndSave(group, new AddMemberEvent(group, exec, target, user)); + } + /** * Erzeugt, speichert ein DeleteUserEvent und wendet es auf eine Gruppe an. * Prüft, ob der Nutzer Mitglied ist und ob er der letzte Admin ist. */ - public void deleteUser(User user, Group group) throws EventException { - ValidationHelper.throwIfNoMember(group, user); - ValidationHelper.throwIfLastAdmin(user, group); + public void deleteUser(Group group, String exec, String target) { + applyAndSave(group, new KickMemberEvent(group, exec, target)); if (ValidationHelper.checkIfGroupEmpty(group)) { - deleteGroup(user, group); - } else { - Event event = new DeleteUserEvent(group, user); - event.apply(group); - - eventStoreService.saveEvent(event); + deleteGroup(group, target); } } @@ -194,14 +178,8 @@ public class GroupService { * Erzeugt, speichert ein DeleteGroupEvent und wendet es auf eine Gruppe an. * Prüft, ob der Nutzer Admin ist. */ - public void deleteGroup(User user, Group group) { - ValidationHelper.throwIfNoAdmin(group, user); - - Event event = new DeleteGroupEvent(group, user); - event.apply(group); - inviteService.destroyLink(group); - - eventStoreService.saveEvent(event); + public void deleteGroup(Group group, String exec) { + applyAndSave(group, new DestroyGroupEvent(group, exec)); } /** @@ -209,17 +187,8 @@ public class GroupService { * Prüft, ob der Nutzer Admin ist und ob der Titel valide ist. * Bei keiner Änderung wird nichts erzeugt. */ - public void updateTitle(User user, Group group, Title title) { - ValidationHelper.throwIfNoAdmin(group, user); - - if (title.equals(group.getTitle())) { - return; - } - - Event event = new UpdateGroupTitleEvent(group, user, title); - event.apply(group); - - eventStoreService.saveEvent(event); + public void setTitle(Group group, String exec, Title title) { + applyAndSave(group, new SetTitleEvent(group, exec, title)); } /** @@ -227,17 +196,8 @@ public class GroupService { * Prüft, ob der Nutzer Admin ist und ob die Beschreibung valide ist. * Bei keiner Änderung wird nichts erzeugt. */ - public void updateDescription(User user, Group group, Description description) { - ValidationHelper.throwIfNoAdmin(group, user); - - if (description.equals(group.getDescription())) { - return; - } - - Event event = new UpdateGroupDescriptionEvent(group, user, description); - event.apply(group); - - eventStoreService.saveEvent(event); + public void setDescription(Group group, String exec, Description description) { + applyAndSave(group, new SetDescriptionEvent(group, exec, description)); } /** @@ -245,17 +205,8 @@ public class GroupService { * Prüft, ob der Nutzer Mitglied ist. * Bei keiner Änderung wird nichts erzeugt. */ - private void updateRole(User user, Group group, Role role) { - ValidationHelper.throwIfNoMember(group, user); - - if (role == group.getRoles().get(user.getUserid())) { - return; - } - - Event event = new UpdateRoleEvent(group, user, role); - event.apply(group); - - eventStoreService.saveEvent(event); + private void updateRole(Group group, String exec, String target, Role role) { + applyAndSave(group, new UpdateRoleEvent(group, exec, target, role)); } /** @@ -263,20 +214,24 @@ public class GroupService { * Prüft, ob der Nutzer Admin ist und ob das Limit valide ist. * Bei keiner Änderung wird nichts erzeugt. */ - public void updateUserLimit(User user, Group group, Limit userLimit) { - ValidationHelper.throwIfNoAdmin(group, user); + public void setLimit(Group group, String exec, Limit userLimit) { + applyAndSave(group, new SetLimitEvent(group, exec, userLimit)); + } - if (userLimit == group.getLimit()) { - return; - } + public void setParent(Group group, String exec, Parent parent) { + applyAndSave(group, new SetParentEvent(group, exec, parent)); + } - Event event; - if (userLimit.getUserLimit() < group.getMembers().size()) { - event = new UpdateUserLimitEvent(group, user, new Limit(group.getMembers().size())); - } else { - event = new UpdateUserLimitEvent(group, user, userLimit); - } + public void setLink(Group group, String exec, Link link) { + applyAndSave(group, new SetInviteLinkEvent(group, exec, link)); + } + private void setType(Group group, String exec, Type type) { + applyAndSave(group, new SetTypeEvent(group, exec, type)); + } + + private void applyAndSave(Group group, Event event) throws EventException { + event.init(group.version() + 1); event.apply(group); eventStoreService.saveEvent(event); diff --git a/src/main/java/mops/gruppen2/domain/service/InviteService.java b/src/main/java/mops/gruppen2/domain/service/InviteService.java deleted file mode 100644 index 669b3dd..0000000 --- a/src/main/java/mops/gruppen2/domain/service/InviteService.java +++ /dev/null @@ -1,54 +0,0 @@ -package mops.gruppen2.domain.service; - -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.exception.InvalidInviteException; -import mops.gruppen2.domain.exception.NoInviteExistException; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.persistance.InviteRepository; -import mops.gruppen2.persistance.dto.InviteLinkDTO; -import org.springframework.stereotype.Service; - -import java.util.UUID; - -@Service -@Log4j2 -public class InviteService { - - private final InviteRepository inviteRepository; - - public InviteService(InviteRepository inviteRepository) { - this.inviteRepository = inviteRepository; - } - - void createLink(Group group) { - inviteRepository.save(new InviteLinkDTO(null, - group.getGroupid().toString(), - UUID.randomUUID().toString())); - - log.debug("Link wurde erzeugt! (Gruppe: {})", group.getGroupid()); - } - - void destroyLink(Group group) { - inviteRepository.deleteLinkOfGroup(group.getGroupid().toString()); - - log.debug("Link wurde zerstört! (Gruppe: {})", group.getGroupid()); - } - - public UUID getGroupIdFromLink(String link) { - try { - return UUID.fromString(inviteRepository.findGroupIdByLink(link)); - } catch (Exception e) { - log.error("Gruppe zu Link ({}) konnte nicht gefunden werden!", link, e); - throw new InvalidInviteException(link); - } - } - - public String getLinkByGroup(Group group) { - try { - return inviteRepository.findLinkByGroupId(group.getGroupid().toString()); - } catch (Exception e) { - log.error("Link zu Gruppe ({}) konnte nicht gefunden werden!", group.getGroupid(), e); - throw new NoInviteExistException(group.getGroupid().toString()); - } - } -} diff --git a/src/main/java/mops/gruppen2/domain/service/ProjectionService.java b/src/main/java/mops/gruppen2/domain/service/ProjectionService.java index b241872..e7b64a3 100644 --- a/src/main/java/mops/gruppen2/domain/service/ProjectionService.java +++ b/src/main/java/mops/gruppen2/domain/service/ProjectionService.java @@ -1,37 +1,41 @@ package mops.gruppen2.domain.service; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.exception.GroupNotFoundException; -import mops.gruppen2.domain.helper.IdHelper; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.helper.CommonHelper; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; +import static mops.gruppen2.domain.event.EventType.CREATEGROUP; +import static mops.gruppen2.domain.event.EventType.SETDESCRIPTION; +import static mops.gruppen2.domain.event.EventType.SETLIMIT; +import static mops.gruppen2.domain.event.EventType.SETTITLE; +import static mops.gruppen2.domain.helper.CommonHelper.eventTypesToString; + /** * Liefert verschiedene Projektionen auf Gruppen. * Benötigt ausschließlich den EventStoreService. */ -@Service @Log4j2 +@RequiredArgsConstructor +@Service public class ProjectionService { private final EventStoreService eventStoreService; - public ProjectionService(EventStoreService eventStoreService) { - this.eventStoreService = eventStoreService; - } - // ################################## STATISCHE PROJEKTIONEN ################################# @@ -121,11 +125,16 @@ public class ProjectionService { @Cacheable("groups") public List projectPublicGroups() throws EventException { List groupIds = eventStoreService.findExistingGroupIds(); + + if (groupIds.isEmpty()) { + return Collections.emptyList(); + } + List events = eventStoreService.findEventsByGroupAndType(groupIds, - "CreateGroupEvent", - "UpdateGroupDescriptionEvent", - "UpdateGroupTitleEvent", - "UpdateUserMaxEvent"); + eventTypesToString(CREATEGROUP, + SETDESCRIPTION, + SETTITLE, + SETLIMIT)); List groups = projectGroups(events); @@ -143,9 +152,14 @@ public class ProjectionService { @Cacheable("groups") public List projectLectures() { List groupIds = eventStoreService.findExistingGroupIds(); + + if (groupIds.isEmpty()) { + return Collections.emptyList(); + } + List events = eventStoreService.findEventsByGroupAndType(groupIds, - "CreateGroupEvent", - "UpdateGroupTitleEvent"); + eventTypesToString(CREATEGROUP, + SETTITLE)); List lectures = projectGroups(events); @@ -158,17 +172,22 @@ public class ProjectionService { * Projiziert Gruppen, in welchen der User aktuell teilnimmt. * Die Gruppen enthalten nur Metainformationen: Titel und Beschreibung. * - * @param user Die Id + * @param userid Die Id * * @return Liste aus Gruppen */ @Cacheable("groups") - public List projectUserGroups(User user) { - List groupIds = eventStoreService.findExistingUserGroups(user); + public List projectUserGroups(String userid) { + List groupIds = eventStoreService.findExistingUserGroups(userid); + + if (groupIds.isEmpty()) { + return Collections.emptyList(); + } + List groupEvents = eventStoreService.findEventsByGroupAndType(groupIds, - "CreateGroupEvent", - "UpdateGroupTitleEvent", - "UpdateGroupDescriptionEvent"); + eventTypesToString(CREATEGROUP, + SETTITLE, + SETDESCRIPTION)); return projectGroups(groupEvents); } @@ -185,10 +204,6 @@ public class ProjectionService { * @throws GroupNotFoundException Wenn die Gruppe nicht gefunden wird */ public Group projectSingleGroup(UUID groupId) throws GroupNotFoundException { - if (IdHelper.isEmpty(groupId)) { - throw new GroupNotFoundException(groupId + ": " + ProjectionService.class); - } - try { List events = eventStoreService.findGroupEvents(groupId); return projectSingleGroup(events); @@ -198,27 +213,28 @@ public class ProjectionService { } } - /** - * Projiziert eine einzelne Gruppe, welche leer sein darf. - */ - public Group projectParent(UUID parentId) { - if (IdHelper.isEmpty(parentId)) { + public Group projectParent(UUID parent) { + if (CommonHelper.uuidIsEmpty(parent)) { return new Group(); } - return projectSingleGroup(parentId); + return projectSingleGroup(parent); } /** * Entfernt alle Gruppen, in welchen ein User teilnimmt, aus einer Gruppenliste. * * @param groups Gruppenliste, aus der entfernt wird - * @param user User, welcher teilnimmt + * @param userid User, welcher teilnimmt */ - void removeUserGroups(List groups, User user) { - List userGroups = eventStoreService.findExistingUserGroups(user); + void removeUserGroups(List groups, String userid) { + List userGroups = eventStoreService.findExistingUserGroups(userid); - groups.removeIf(group -> userGroups.contains(group.getGroupid())); + groups.removeIf(group -> userGroups.contains(group.getId())); + } + + public Group projectGroupByLink(String link) { + return projectSingleGroup(eventStoreService.findGroupByLink(link)); } } diff --git a/src/main/java/mops/gruppen2/domain/service/SearchService.java b/src/main/java/mops/gruppen2/domain/service/SearchService.java index 5e51c6b..627c964 100644 --- a/src/main/java/mops/gruppen2/domain/service/SearchService.java +++ b/src/main/java/mops/gruppen2/domain/service/SearchService.java @@ -2,9 +2,8 @@ package mops.gruppen2.domain.service; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.SortHelper; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -33,10 +32,10 @@ public class SearchService { * @throws EventException Projektionsfehler */ @Cacheable("groups") - public List searchPublicGroups(String search, User user) throws EventException { + public List searchPublicGroups(String search, String userid) { List groups = projectionService.projectPublicGroups(); - projectionService.removeUserGroups(groups, user); - sortByGroupType(groups); + projectionService.removeUserGroups(groups, userid); + SortHelper.sortByGroupType(groups); if (search.isEmpty()) { return groups; @@ -45,25 +44,8 @@ public class SearchService { log.debug("Es wurde gesucht nach: {}", search); return groups.stream() - .filter(group -> group.toString().toLowerCase().contains(search.toLowerCase())) + .filter(group -> group.format().toLowerCase().contains(search.toLowerCase())) .collect(Collectors.toList()); } - /** - * Sortiert die übergebene Liste an Gruppen, sodass Veranstaltungen am Anfang der Liste sind. - * - * @param groups Die Liste von Gruppen die sortiert werden soll - */ - private static void sortByGroupType(List groups) { - groups.sort((Group g1, Group g2) -> { - if (g1.getType() == Type.LECTURE) { - return -1; - } - if (g2.getType() == Type.LECTURE) { - return 0; - } - - return 1; - }); - } } diff --git a/src/main/java/mops/gruppen2/persistance/EventRepository.java b/src/main/java/mops/gruppen2/persistance/EventRepository.java index 6466a77..eaf266e 100644 --- a/src/main/java/mops/gruppen2/persistance/EventRepository.java +++ b/src/main/java/mops/gruppen2/persistance/EventRepository.java @@ -30,28 +30,28 @@ public interface EventRepository extends CrudRepository { @Query("SELECT * FROM event" + " WHERE group_id IN (:userIds) ") - List findEventDTOsByUser(@Param("groupIds") List userIds); + List findEventDTOsByUser(@Param("groupIds") String... userIds); @Query("SELECT * FROM event" + " WHERE event_type IN (:types)") - List findEventDTOsByType(@Param("types") List types); + List findEventDTOsByType(@Param("types") String... types); @Query("SELECT * FROM event" + " WHERE event_type IN (:types) AND group_id IN (:groupIds)") - List findEventDTOsByGroupAndType(@Param("types") List types, - @Param("groupIds") List groupIds); + List findEventDTOsByGroupAndType(@Param("groupIds") List groupIds, + @Param("types") String... types); @Query("SELECT * FROM event" + " WHERE event_type IN (:types) AND user_id = :userId") - List findEventDTOsByUserAndType(@Param("types") List types, - @Param("userId") String userId); + List findEventDTOsByUserAndType(@Param("userId") String userId, + @Param("types") String... types); // ################################ LATEST EVENT DTOs ######################################## @Query("WITH ranked_events AS (" + "SELECT *, ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY event_id DESC) AS rn" + " FROM event" - + " WHERE user_id = :userId AND event_type IN ('AddUserEvent', 'DeleteUserEvent')" + + " WHERE user_id = :userId AND event_type IN ('ADDMEMBER', 'KICKMEMBER')" + ")" + "SELECT * FROM ranked_events WHERE rn = 1;") List findLatestEventDTOsPartitionedByGroupByUser(@Param("userId") String userId); @@ -62,7 +62,7 @@ public interface EventRepository extends CrudRepository { + " WHERE event_type IN (:types)" + ")" + "SELECT * FROM ranked_events WHERE rn = 1;") - List findLatestEventDTOsPartitionedByGroupByType(@Param("types") List types); + List findLatestEventDTOsPartitionedByGroupByType(@Param("types") String... types); // ######################################### COUNT ########################################### diff --git a/src/main/java/mops/gruppen2/persistance/InviteRepository.java b/src/main/java/mops/gruppen2/persistance/InviteRepository.java deleted file mode 100644 index 246a289..0000000 --- a/src/main/java/mops/gruppen2/persistance/InviteRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package mops.gruppen2.persistance; - -import mops.gruppen2.persistance.dto.InviteLinkDTO; -import org.springframework.data.jdbc.repository.query.Modifying; -import org.springframework.data.jdbc.repository.query.Query; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -@Repository -public interface InviteRepository extends CrudRepository { - - @Query("SELECT group_id FROM invite WHERE invite_link = :link") - String findGroupIdByLink(@Param("link") String link); - - @Modifying - @Query("DELETE FROM invite WHERE group_id = :group") - void deleteLinkOfGroup(@Param("group") String group); - - @Query("SELECT invite_link FROM invite WHERE group_id = :group") - String findLinkByGroupId(@Param("group") String group); -} diff --git a/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java b/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java index 832abee..eacbfd1 100644 --- a/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java +++ b/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java @@ -12,8 +12,13 @@ public class EventDTO { @Id Long event_id; + String group_id; - String user_id; + long group_version; + + String exec_user_id; + String target_user_id; + String event_type; String event_payload; } diff --git a/src/main/java/mops/gruppen2/persistance/dto/InviteLinkDTO.java b/src/main/java/mops/gruppen2/persistance/dto/InviteLinkDTO.java deleted file mode 100644 index db28e7c..0000000 --- a/src/main/java/mops/gruppen2/persistance/dto/InviteLinkDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package mops.gruppen2.persistance.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.springframework.data.annotation.Id; -import org.springframework.data.relational.core.mapping.Table; - -@Table("invite") -@Getter -@AllArgsConstructor -public class InviteLinkDTO { - - @Id - Long invite_id; - String group_id; - String invite_link; -} diff --git a/src/main/java/mops/gruppen2/web/APIController.java b/src/main/java/mops/gruppen2/web/APIController.java index 75d12d5..38d1432 100644 --- a/src/main/java/mops/gruppen2/web/APIController.java +++ b/src/main/java/mops/gruppen2/web/APIController.java @@ -7,9 +7,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; import mops.gruppen2.domain.helper.APIHelper; -import mops.gruppen2.domain.helper.IdHelper; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.helper.CommonHelper; +import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.service.EventStoreService; import mops.gruppen2.domain.service.ProjectionService; import mops.gruppen2.web.api.GroupRequestWrapper; @@ -60,7 +59,7 @@ public class APIController { public List getApiUserGroups(@ApiParam("Nutzer-Id") @PathVariable("id") String userId) { - return IdHelper.uuidsToString(eventStoreService.findExistingUserGroups(new User(userId))); + return CommonHelper.uuidsToString(eventStoreService.findExistingUserGroups(userId)); } /** diff --git a/src/main/java/mops/gruppen2/web/GroupCreationController.java b/src/main/java/mops/gruppen2/web/GroupCreationController.java index 9cc435c..af56d77 100644 --- a/src/main/java/mops/gruppen2/web/GroupCreationController.java +++ b/src/main/java/mops/gruppen2/web/GroupCreationController.java @@ -4,16 +4,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; import mops.gruppen2.domain.helper.CsvHelper; -import mops.gruppen2.domain.helper.IdHelper; import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Description; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Limit; -import mops.gruppen2.domain.model.Title; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.domain.model.group.wrapper.Description; +import mops.gruppen2.domain.model.group.wrapper.Limit; +import mops.gruppen2.domain.model.group.wrapper.Parent; +import mops.gruppen2.domain.model.group.wrapper.Title; import mops.gruppen2.domain.service.GroupService; import mops.gruppen2.domain.service.ProjectionService; -import mops.gruppen2.web.form.CreateForm; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Controller; @@ -21,6 +21,8 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.security.RolesAllowed; import javax.validation.Valid; @@ -49,27 +51,27 @@ public class GroupCreationController { @PostMapping("/create") @CacheEvict(value = "groups", allEntries = true) public String postCreateOrga(KeycloakAuthenticationToken token, - @Valid CreateForm create, - @Valid Title title, - @Valid Description description, - @Valid Limit limit) { + @RequestParam("type") Type type, + @RequestParam("parent") @Valid Parent parent, + @RequestParam("title") @Valid Title title, + @RequestParam("description") @Valid Description description, + @RequestParam("limit") @Valid Limit limit, + @RequestParam(value = "file", required = false) MultipartFile file) { // Zusätzlicher check: studentin kann keine lecture erstellen - ValidationHelper.validateCreateForm(token, create); + ValidationHelper.validateCreateForm(token, type); - User user = new User(token); - Group group = groupService.createGroup(user, - title, - description, - create.getType(), - limit, - create.getParent()); + String principal = token.getName(); + Group group = groupService.createGroup(principal); + groupService.initGroupMembers(group, principal, principal, new User(token), limit); + groupService.initGroupMeta(group, principal, type, parent); + groupService.initGroupText(group, principal, title, description); // ROLE_studentin kann kein CSV importieren if (token.getAccount().getRoles().contains("orga")) { - groupService.addUsersToGroup(CsvHelper.readCsvFile(create.getFile()), group, user); + groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); } - return "redirect:/gruppen2/details/" + IdHelper.uuidToString(group.getGroupid()); + return "redirect:/gruppen2/details/" + group.getId(); } } diff --git a/src/main/java/mops/gruppen2/web/GroupDetailsController.java b/src/main/java/mops/gruppen2/web/GroupDetailsController.java index 843cba1..eafb33c 100644 --- a/src/main/java/mops/gruppen2/web/GroupDetailsController.java +++ b/src/main/java/mops/gruppen2/web/GroupDetailsController.java @@ -4,15 +4,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; import mops.gruppen2.domain.helper.CsvHelper; -import mops.gruppen2.domain.helper.IdHelper; import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Description; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Limit; -import mops.gruppen2.domain.model.Title; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.domain.model.group.wrapper.Description; +import mops.gruppen2.domain.model.group.wrapper.Limit; +import mops.gruppen2.domain.model.group.wrapper.Title; import mops.gruppen2.domain.service.GroupService; -import mops.gruppen2.domain.service.InviteService; import mops.gruppen2.domain.service.ProjectionService; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.cache.annotation.CacheEvict; @@ -38,7 +36,6 @@ import java.util.UUID; @RequestMapping("/gruppen2") public class GroupDetailsController { - private final InviteService inviteService; private final GroupService groupService; private final ProjectionService projectionService; @@ -48,18 +45,17 @@ public class GroupDetailsController { Model model, @PathVariable("id") String groupId) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); // Parent Badge - UUID parentId = group.getParent(); - Group parent = projectionService.projectParent(parentId); + Group parent = projectionService.projectParent(group.getParent()); model.addAttribute("group", group); model.addAttribute("parent", parent); // Detailseite für nicht-Mitglieder - if (!ValidationHelper.checkIfMember(group, user)) { + if (!ValidationHelper.checkIfMember(group, principal)) { return "preview"; } @@ -72,14 +68,14 @@ public class GroupDetailsController { public String postDetailsJoin(KeycloakAuthenticationToken token, @PathVariable("id") String groupId) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - if (ValidationHelper.checkIfMember(group, user)) { + if (ValidationHelper.checkIfMember(group, principal)) { return "redirect:/gruppen2/details/" + groupId; } - groupService.addUser(user, group); + groupService.addMember(group, principal, principal, new User(token)); return "redirect:/gruppen2/details/" + groupId; } @@ -90,12 +86,10 @@ public class GroupDetailsController { public String postDetailsLeave(KeycloakAuthenticationToken token, @PathVariable("id") String groupId) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - ValidationHelper.throwIfNoMember(group, user); - - groupService.deleteUser(user, group); + groupService.deleteUser(group, principal, principal); return "redirect:/gruppen2"; } @@ -107,15 +101,15 @@ public class GroupDetailsController { HttpServletRequest request, @PathVariable("id") String groupId) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); // Invite Link String actualURL = request.getRequestURL().toString(); String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/")); - String link = serverURL + "gruppen2/join/" + inviteService.getLinkByGroup(group); + String link = serverURL + "gruppen2/join/" + group.getLink(); - ValidationHelper.throwIfNoAdmin(group, user); + ValidationHelper.throwIfNoAdmin(group, principal); model.addAttribute("group", group); model.addAttribute("link", link); @@ -131,11 +125,11 @@ public class GroupDetailsController { @Valid Title title, @Valid Description description) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - groupService.updateTitle(user, group, title); - groupService.updateDescription(user, group, description); + groupService.setTitle(group, principal, title); + groupService.setDescription(group, principal, description); return "redirect:/gruppen2/details/" + groupId + "/edit"; } @@ -146,10 +140,10 @@ public class GroupDetailsController { public String postDetailsEditUserLimit(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, @Valid Limit limit) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - groupService.updateUserLimit(user, group, limit); + groupService.setLimit(group, principal, limit); return "redirect:/gruppen2/details/" + groupId + "/edit"; } @@ -161,10 +155,10 @@ public class GroupDetailsController { @PathVariable("id") String groupId, @RequestParam(value = "file", required = false) MultipartFile file) { - User user = new User(token); - Group group = projectionService.projectSingleGroup(IdHelper.stringToUUID(groupId)); + String principal = token.getName(); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - groupService.addUsersToGroup(CsvHelper.readCsvFile(file), group, user); + groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); return "redirect:/gruppen2/details/" + groupId + "/edit"; } @@ -174,17 +168,17 @@ public class GroupDetailsController { @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditRole(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, - @PathVariable("userid") String userId) { + @PathVariable("userid") String target) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - ValidationHelper.throwIfNoAdmin(group, user); + ValidationHelper.throwIfNoAdmin(group, principal); - groupService.toggleMemberRole(new User(userId), group); + groupService.toggleMemberRole(group, principal, target); // Falls sich der User selbst die Rechte genommen hat - if (!ValidationHelper.checkIfAdmin(group, user)) { + if (!ValidationHelper.checkIfAdmin(group, principal)) { return "redirect:/gruppen2/details/" + groupId; } @@ -196,16 +190,16 @@ public class GroupDetailsController { @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditDelete(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, - @PathVariable("userid") String userId) { + @PathVariable("userid") String target) { - User user = new User(token); + String principal = token.getName(); Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - ValidationHelper.throwIfNoAdmin(group, user); + ValidationHelper.throwIfNoAdmin(group, principal); // Der eingeloggte User kann sich nicht selbst entfernen (er kann aber verlassen) - if (!userId.equals(user.getUserid())) { - groupService.deleteUser(new User(userId), group); + if (!principal.equals(target)) { + groupService.deleteUser(group, principal, target); } return "redirect:/gruppen2/details/" + groupId + "/edit"; @@ -215,12 +209,12 @@ public class GroupDetailsController { @PostMapping("/details/{id}/edit/destroy") @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditDestroy(KeycloakAuthenticationToken token, - @PathVariable("id") String groupId) { + @PathVariable("id") String groupid) { - User user = new User(token); - Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + String principal = token.getName(); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupid)); - groupService.deleteGroup(user, group); + groupService.deleteGroup(group, principal); return "redirect:/gruppen2"; } diff --git a/src/main/java/mops/gruppen2/web/GruppenfindungController.java b/src/main/java/mops/gruppen2/web/GruppenfindungController.java index 96f1048..465be52 100644 --- a/src/main/java/mops/gruppen2/web/GruppenfindungController.java +++ b/src/main/java/mops/gruppen2/web/GruppenfindungController.java @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCall; import mops.gruppen2.domain.exception.PageNotFoundException; -import mops.gruppen2.domain.model.User; import mops.gruppen2.domain.service.ProjectionService; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.stereotype.Controller; @@ -35,9 +34,8 @@ public class GruppenfindungController { public String getIndexPage(KeycloakAuthenticationToken token, Model model) { - User user = new User(token); - - model.addAttribute("groups", projectionService.projectUserGroups(user)); + String principal = token.getName(); + model.addAttribute("groups", projectionService.projectUserGroups(principal)); return "index"; } diff --git a/src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java b/src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java index 364a189..71c432f 100644 --- a/src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java +++ b/src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java @@ -1,9 +1,9 @@ package mops.gruppen2.web; import mops.gruppen2.domain.Account; -import mops.gruppen2.domain.model.Role; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; +import mops.gruppen2.domain.model.group.Role; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.model.group.User; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -20,15 +20,15 @@ public class ModelAttributeControllerAdvice { // Prevent NullPointerException if not logged in if (token != null) { model.addAttribute("account", new Account(token)); - model.addAttribute("user", new User(token)); + model.addAttribute("principal", new User(token)); } // Add enums - model.addAttribute("member", Role.MEMBER); - model.addAttribute("admin", Role.ADMIN); - model.addAttribute("public", Type.PUBLIC); - model.addAttribute("private", Type.PRIVATE); - model.addAttribute("lecture", Type.LECTURE); + model.addAttribute("REGULAR", Role.REGULAR); + model.addAttribute("ADMIN", Role.ADMIN); + model.addAttribute("PUBLIC", Type.PUBLIC); + model.addAttribute("PRIVATE", Type.PRIVATE); + model.addAttribute("LECTURE", Type.LECTURE); } } diff --git a/src/main/java/mops/gruppen2/web/SearchAndInviteController.java b/src/main/java/mops/gruppen2/web/SearchAndInviteController.java index db51f2c..10bc6f6 100644 --- a/src/main/java/mops/gruppen2/web/SearchAndInviteController.java +++ b/src/main/java/mops/gruppen2/web/SearchAndInviteController.java @@ -4,10 +4,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; import mops.gruppen2.domain.helper.ValidationHelper; -import mops.gruppen2.domain.model.Group; -import mops.gruppen2.domain.model.Type; -import mops.gruppen2.domain.model.User; -import mops.gruppen2.domain.service.InviteService; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; import mops.gruppen2.domain.service.ProjectionService; import mops.gruppen2.domain.service.SearchService; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; @@ -31,7 +29,6 @@ import java.util.List; @RequestMapping("/gruppen2") public class SearchAndInviteController { - private final InviteService inviteService; private final ProjectionService projectionService; private final SearchService searchService; @@ -50,8 +47,8 @@ public class SearchAndInviteController { Model model, @RequestParam("string") String search) { - User user = new User(token); - List groups = searchService.searchPublicGroups(search, user); + String principal = token.getName(); + List groups = searchService.searchPublicGroups(search, principal); model.addAttribute("groups", groups); @@ -64,19 +61,19 @@ public class SearchAndInviteController { Model model, @PathVariable("link") String link) { - User user = new User(token); - Group group = projectionService.projectSingleGroup(inviteService.getGroupIdFromLink(link)); + String principal = token.getName(); + Group group = projectionService.projectGroupByLink(link); model.addAttribute("group", group); // Gruppe öffentlich if (group.getType() == Type.PUBLIC) { - return "redirect:/gruppen2/details/" + group.getGroupid(); + return "redirect:/gruppen2/details/" + group.getId(); } // Bereits Mitglied - if (ValidationHelper.checkIfMember(group, user)) { - return "redirect:/gruppen2/details/" + group.getGroupid(); + if (ValidationHelper.checkIfMember(group, principal)) { + return "redirect:/gruppen2/details/" + group.getId(); } return "link"; diff --git a/src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java b/src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java index 73a045b..d8e0680 100644 --- a/src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java +++ b/src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java @@ -2,7 +2,7 @@ package mops.gruppen2.web.api; import lombok.AllArgsConstructor; import lombok.Getter; -import mops.gruppen2.domain.model.Group; +import mops.gruppen2.domain.model.group.Group; import java.util.List; diff --git a/src/main/java/mops/gruppen2/web/form/CreateForm.java b/src/main/java/mops/gruppen2/web/form/CreateForm.java deleted file mode 100644 index b5be8b3..0000000 --- a/src/main/java/mops/gruppen2/web/form/CreateForm.java +++ /dev/null @@ -1,29 +0,0 @@ -package mops.gruppen2.web.form; - -import lombok.Data; -import mops.gruppen2.domain.helper.IdHelper; -import mops.gruppen2.domain.model.Type; -import org.springframework.web.multipart.MultipartFile; - -import javax.validation.constraints.NotBlank; -import java.util.UUID; - -@Data -public class CreateForm { - - @NotBlank - String type; - - @NotBlank - String parent; - - MultipartFile file; - - public Type getType() { - return Type.valueOf(type); - } - - public UUID getParent() { - return getType() == Type.LECTURE ? IdHelper.emptyUUID() : IdHelper.stringToUUID(parent); - } -} diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 1b12d91..fb46f63 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -2,7 +2,7 @@ logging.application.name = gruppen2 logging.pattern.console = [${logging.application.name}], %magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS},\t%blue(%msg)\t%thread,%logger.%M%n spring.output.ansi.enabled = always -logging.level.mops.gruppen2 = info +logging.level.mops.gruppen2 = trace logging.level.org.springframework.jdbc.core = info # Database diff --git a/src/main/resources/schema-h2.sql b/src/main/resources/schema-h2.sql index 72405f2..15e1d44 100644 --- a/src/main/resources/schema-h2.sql +++ b/src/main/resources/schema-h2.sql @@ -2,18 +2,11 @@ DROP TABLE IF EXISTS event; CREATE TABLE event ( - event_id INT PRIMARY KEY AUTO_INCREMENT, - group_id VARCHAR(36) NOT NULL, - user_id VARCHAR(50), - event_type VARCHAR(32), - event_payload VARCHAR(2500) -); - -DROP TABLE IF EXISTS invite; - -CREATE TABLE invite -( - invite_id INT PRIMARY KEY AUTO_INCREMENT, - group_id VARCHAR(36) NOT NULL, - invite_link VARCHAR(36) NOT NULL + event_id INT PRIMARY KEY AUTO_INCREMENT, + group_id VARCHAR(36) NOT NULL, + group_version INT NOT NULL, + exec_user_id VARCHAR(50) NOT NULL, + target_user_id VARCHAR(50), + event_type VARCHAR(32) NOT NULL, + event_payload VARCHAR(2500) NOT NULL ); diff --git a/src/main/resources/templates/details.html b/src/main/resources/templates/details.html index 5bf76fc..c9a81b4 100644 --- a/src/main/resources/templates/details.html +++ b/src/main/resources/templates/details.html @@ -27,7 +27,7 @@ -

+
@@ -40,13 +40,13 @@
Teilnehmer: - +
-
+
+ th:action="@{/gruppen2/details/{id}/edit(id=${group.getId()})}">
@@ -55,8 +55,8 @@
  • - + th:each="member : ${group.getMembers()}"> +
diff --git a/src/main/resources/templates/edit.html b/src/main/resources/templates/edit.html index fa04b00..17baeaa 100644 --- a/src/main/resources/templates/edit.html +++ b/src/main/resources/templates/edit.html @@ -15,14 +15,14 @@
-
+
-
+
@@ -50,7 +50,7 @@
-
@@ -63,7 +63,7 @@
-
@@ -76,7 +76,7 @@
- @@ -96,20 +96,20 @@
    -
  • +
  • - -
    diff --git a/src/main/resources/templates/fragments/forms.html b/src/main/resources/templates/fragments/forms.html index ba4a19a..d8b539b 100644 --- a/src/main/resources/templates/fragments/forms.html +++ b/src/main/resources/templates/fragments/forms.html @@ -10,7 +10,7 @@
    Gruppentitel:
    -
    @@ -20,8 +20,8 @@
    Beschreibung:
    - +
@@ -50,7 +50,7 @@
@@ -61,17 +61,17 @@
- +
@@ -81,10 +81,10 @@
Limit:
- + th:disabled="${group != null && group.getLimit() < 999999} ? 'false' : 'disabled'">
Teilnehmer
@@ -99,7 +99,7 @@ CSV:
- +