diff --git a/mysql/db/entrypoint/schema.sql b/mysql/db/entrypoint/schema.sql index 5a86b10..cfdfe0c 100644 --- a/mysql/db/entrypoint/schema.sql +++ b/mysql/db/entrypoint/schema.sql @@ -5,6 +5,6 @@ CREATE TABLE event group_version INT NOT NULL, exec_id VARCHAR(32) NOT NULL, target_id VARCHAR(32), - event_type VARCHAR(16) NOT NULL, + event_date DATETIME NOT NULL, event_payload JSON NOT NULL ); diff --git a/mysql/db/schema-heroku.sql b/mysql/db/schema-heroku.sql index f8bf704..fd92919 100644 --- a/mysql/db/schema-heroku.sql +++ b/mysql/db/schema-heroku.sql @@ -1,3 +1,5 @@ +DROP TABLE IF EXISTS event; + CREATE TABLE event ( event_id INT PRIMARY KEY AUTO_INCREMENT, @@ -5,6 +7,6 @@ CREATE TABLE event group_version INT NOT NULL, exec_id VARCHAR(32) NOT NULL, target_id VARCHAR(32), - event_type VARCHAR(16) NOT NULL, + event_date DATETIME NOT NULL, event_payload TEXT NOT NULL ); diff --git a/src/main/java/mops/gruppen2/config/KeycloakConfig.java b/src/main/java/mops/gruppen2/config/KeycloakConfig.java index 3d99c83..74e607d 100644 --- a/src/main/java/mops/gruppen2/config/KeycloakConfig.java +++ b/src/main/java/mops/gruppen2/config/KeycloakConfig.java @@ -22,10 +22,10 @@ public class KeycloakConfig { @Value("${keycloak.resource}") private String clientId; - @Value("f73354fd-614e-4e24-b801-a8d0f4abf531") + @Value("2e2e5770-c454-4d31-be99-9d8c34c93089") private String clientSecret; - @Value("https://gruppenkeycloak.herokuapp.com/auth/realms/master/protocol/openid-connect/token") + @Value("https://churl-keycloak.herokuapp.com/auth/realms/Gruppen/protocol/openid-connect/token") private String tokenUri; @Bean diff --git a/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java b/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java index e294cc1..0394a0c 100644 --- a/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/AddMemberEvent.java @@ -6,7 +6,7 @@ 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.exception.UserExistsException; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.User; import mops.gruppen2.infrastructure.GroupCache; @@ -39,12 +39,17 @@ public class AddMemberEvent extends Event { } @Override - protected void applyEvent(Group group) throws UserAlreadyExistsException, GroupFullException { + protected void applyEvent(Group group) throws UserExistsException, GroupFullException { group.addMember(target, user); log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers()); } + @Override + public String format() { + return "Benutzer hinzugefügt: " + target + "."; + } + @Override public String type() { return EventType.ADDMEMBER.toString(); diff --git a/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java index c3457cb..8e55353 100644 --- a/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java @@ -39,6 +39,11 @@ public class CreateGroupEvent extends Event { log.trace("\t\t\t\t\tNeue Gruppe: {}", group.toString()); } + @Override + public String format() { + return "Gruppe erstellt."; + } + @Override public String type() { return EventType.CREATEGROUP.toString(); diff --git a/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java index 432c174..d244f3f 100644 --- a/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/DestroyGroupEvent.java @@ -20,7 +20,7 @@ public class DestroyGroupEvent extends Event { @Override protected void updateCache(GroupCache cache, Group group) { - cache.groupsRemove(group); + cache.groupsRemove(groupid, group); } @Override @@ -30,6 +30,11 @@ public class DestroyGroupEvent extends Event { log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group.toString()); } + @Override + public String format() { + return "Gruppe gelöscht."; + } + @Override public String type() { 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 7ef6a92..0da2a41 100644 --- a/src/main/java/mops/gruppen2/domain/event/Event.java +++ b/src/main/java/mops/gruppen2/domain/event/Event.java @@ -13,21 +13,26 @@ import mops.gruppen2.domain.exception.IdMismatchException; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.infrastructure.GroupCache; +import java.time.LocalDateTime; import java.util.UUID; +import static com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import static com.fasterxml.jackson.annotation.JsonTypeInfo.As; +import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + @Log4j2 -@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")}) +@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "class") +@JsonSubTypes({@Type(value = AddMemberEvent.class, name = "ADDMEMBER"), + @Type(value = CreateGroupEvent.class, name = "CREATEGROUP"), + @Type(value = DestroyGroupEvent.class, name = "DESTROYGROUP"), + @Type(value = KickMemberEvent.class, name = "KICKMEMBER"), + @Type(value = SetDescriptionEvent.class, name = "SETDESCRIPTION"), + @Type(value = SetInviteLinkEvent.class, name = "SETLINK"), + @Type(value = SetLimitEvent.class, name = "SETLIMIT"), + @Type(value = SetParentEvent.class, name = "SETPARENT"), + @Type(value = SetTitleEvent.class, name = "SETTITLE"), + @Type(value = SetTypeEvent.class, name = "SETTYPE"), + @Type(value = UpdateRoleEvent.class, name = "UPDATEROLE")}) @Getter @NoArgsConstructor // Lombok needs a default constructor in the base class public abstract class Event { @@ -44,6 +49,10 @@ public abstract class Event { @JsonProperty("target") protected String target; + @JsonProperty("date") + protected LocalDateTime date; + + //TODO: Eigentlich sollte die Gruppe aus dem Cache genommen werden, nicht übergeben public Event(UUID groupid, String exec, String target) { this.groupid = groupid; this.exec = exec; @@ -54,6 +63,7 @@ public abstract class Event { if (this.version != 0) { throw new BadArgumentException("Event wurde schon initialisiert. (" + type() + ")"); } + date = LocalDateTime.now(); log.trace("Event wurde initialisiert. (" + type() + "," + version + ")"); @@ -68,9 +78,22 @@ public abstract class Event { } checkGroupIdMatch(group.getId()); - updateCache(cache, group); group.updateVersion(version); applyEvent(group); + updateCache(cache, group); // Update erst nachdem apply keine exception geworfen hat + } + + public void apply(Group group) throws EventException { + log.trace("Event wird angewendet:\t{}", this); + + if (version == 0) { + throw new BadArgumentException("Event wurde nicht initialisiert."); + } + + checkGroupIdMatch(group.getId()); + group.updateVersion(version); + applyEvent(group); + } private void checkGroupIdMatch(UUID groupid) throws IdMismatchException { @@ -88,6 +111,9 @@ public abstract class Event { protected abstract void applyEvent(Group group) throws EventException; + @JsonIgnore + public abstract String format(); + @JsonIgnore public abstract String type(); } diff --git a/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java b/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java index d5341d0..8d9a080 100644 --- a/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/KickMemberEvent.java @@ -34,6 +34,11 @@ public class KickMemberEvent extends Event { log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers()); } + @Override + public String format() { + return "Mitglied entfernt: " + target + "."; + } + @Override public String type() { 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 index f400d98..5ad51dd 100644 --- a/src/main/java/mops/gruppen2/domain/event/SetDescriptionEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/SetDescriptionEvent.java @@ -38,6 +38,11 @@ public class SetDescriptionEvent extends Event { log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription()); } + @Override + public String format() { + return "Beschreibung gesetzt: " + description + "."; + } + @Override public String type() { 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 index 72979c4..30c19e7 100644 --- a/src/main/java/mops/gruppen2/domain/event/SetInviteLinkEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/SetInviteLinkEvent.java @@ -38,6 +38,11 @@ public class SetInviteLinkEvent extends Event { log.trace("\t\t\t\t\tNeuer Link: {}", group.getLink()); } + @Override + public String format() { + return "Einladungslink gesetzt: " + link + "."; + } + @Override public String type() { 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 index f1b06eb..e69576b 100644 --- a/src/main/java/mops/gruppen2/domain/event/SetLimitEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/SetLimitEvent.java @@ -36,6 +36,11 @@ public class SetLimitEvent extends Event { log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getLimit()); } + @Override + public String format() { + return "Benutzerlimit gesetzt: " + limit + "."; + } + @Override public String type() { 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 index 98f5fe9..be1dda9 100644 --- a/src/main/java/mops/gruppen2/domain/event/SetParentEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/SetParentEvent.java @@ -4,6 +4,7 @@ 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.Parent; @@ -29,12 +30,17 @@ public class SetParentEvent extends Event { protected void updateCache(GroupCache cache, Group group) {} @Override - protected void applyEvent(Group group) throws NoAccessException { + protected void applyEvent(Group group) throws NoAccessException, BadArgumentException { group.setParent(exec, parent); log.trace("\t\t\t\t\tNeues Parent: {}", group.getParent()); } + @Override + public String format() { + return "Veranstaltungszugehörigkeit gesetzt: " + parent.getValue() + "."; + } + @Override public String type() { 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 index 1a5acd6..bdfa3d1 100644 --- a/src/main/java/mops/gruppen2/domain/event/SetTitleEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/SetTitleEvent.java @@ -38,6 +38,11 @@ public class SetTitleEvent extends Event { log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle()); } + @Override + public String format() { + return "Titel gesetzt: " + title + "."; + } + @Override public String type() { 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 index b62e09d..85be321 100644 --- a/src/main/java/mops/gruppen2/domain/event/SetTypeEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/SetTypeEvent.java @@ -3,6 +3,7 @@ package mops.gruppen2.domain.event; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Value; +import lombok.experimental.NonFinal; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.model.group.Group; @@ -20,6 +21,12 @@ public class SetTypeEvent extends Event { @JsonProperty("type") Type type; + //TODO: blöder hack, das soll eigentlich anders gehen + // Problem ist, dass die Gruppe vor dem Cache verändert wird, also kann der cache den alten Typ + // nicht mehr aus der Gruppe holen + @NonFinal + Type oldType; + public SetTypeEvent(UUID groupId, String exec, @Valid Type type) { super(groupId, exec, null); @@ -28,15 +35,21 @@ public class SetTypeEvent extends Event { @Override protected void updateCache(GroupCache cache, Group group) { - cache.typesRemove(group); + cache.typesRemove(oldType, group); cache.typesPut(type, group); } @Override protected void applyEvent(Group group) throws EventException { + oldType = group.getType(); group.setType(exec, type); } + @Override + public String format() { + return "Gruppentype gesetzt: " + type + "."; + } + @Override public String type() { return EventType.SETTYPE.toString(); diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java index 4df2fc8..e5f16fb 100644 --- a/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java @@ -38,6 +38,11 @@ public class UpdateRoleEvent extends Event { log.trace("\t\t\t\t\tNeue Admin: {}", group.getAdmins()); } + @Override + public String format() { + return "Mitgliedsrolle gesetzt: " + target + ": " + role + "."; + } + @Override public String type() { return EventType.UPDATEROLE.toString(); diff --git a/src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java b/src/main/java/mops/gruppen2/domain/exception/UserExistsException.java similarity index 68% rename from src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java rename to src/main/java/mops/gruppen2/domain/exception/UserExistsException.java index d0288cd..43f5d59 100644 --- a/src/main/java/mops/gruppen2/domain/exception/UserAlreadyExistsException.java +++ b/src/main/java/mops/gruppen2/domain/exception/UserExistsException.java @@ -2,11 +2,11 @@ package mops.gruppen2.domain.exception; import org.springframework.http.HttpStatus; -public class UserAlreadyExistsException extends EventException { +public class UserExistsException extends EventException { private static final long serialVersionUID = -8150634358760194625L; - public UserAlreadyExistsException(String info) { + public UserExistsException(String info) { super(HttpStatus.INTERNAL_SERVER_ERROR, "User existiert bereits.", info); } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/Group.java b/src/main/java/mops/gruppen2/domain/model/group/Group.java index c081f45..61feb00 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/Group.java +++ b/src/main/java/mops/gruppen2/domain/model/group/Group.java @@ -10,7 +10,7 @@ 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.UserExistsException; import mops.gruppen2.domain.exception.UserNotFoundException; import mops.gruppen2.domain.model.group.wrapper.Body; import mops.gruppen2.domain.model.group.wrapper.Description; @@ -19,6 +19,7 @@ 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 mops.gruppen2.domain.service.helper.CommonHelper; +import mops.gruppen2.domain.service.helper.SortHelper; import mops.gruppen2.domain.service.helper.ValidationHelper; import javax.validation.Valid; @@ -56,19 +57,21 @@ public class Group { private GroupMeta meta = GroupMeta.EMPTY(); - private GroupOptions options = GroupOptions.DEFAULT(); + //TODO: UI set + use for options + private final GroupOptions options = GroupOptions.DEFAULT(); // Inhalt private Title title = Title.EMPTY(); private Description description = Description.EMPTY(); + //TODO: Asciidoc description private Body body; // Integrationen // Teilnehmer - private Map memberships = new HashMap<>(); + private final Map memberships = new HashMap<>(); // ####################################### Members ########################################### @@ -98,7 +101,7 @@ public class Group { return memberships.get(userid).getRole(); } - public void addMember(String target, User user) throws UserAlreadyExistsException, GroupFullException { + public void addMember(String target, User user) throws UserExistsException, GroupFullException { ValidationHelper.throwIfMember(this, target); ValidationHelper.throwIfGroupFull(this); @@ -215,6 +218,26 @@ public class Group { return !parent.isEmpty(); } + public boolean hasMaterial() { + return options.isHasMaterialIntegration(); + } + + public boolean hasForums() { + return options.isHasForumsIntegration(); + } + + public boolean hasCalendar() { + return options.isHasTermineIntegration(); + } + + public boolean hasModules() { + return options.isHasModulesIntegration(); + } + + public boolean hasPortfolios() { + return options.isHasPortfolioIntegration(); + } + // ######################################## Setters ########################################## @@ -255,14 +278,20 @@ public class Group { this.limit = limit; } - public void setParent(String exec, @Valid Parent parent) throws NoAccessException { + public void setParent(String exec, @Valid Parent parent) throws NoAccessException, BadArgumentException { ValidationHelper.throwIfNoAdmin(this, exec); + if (parent.getValue().equals(groupid)) { + throw new BadArgumentException("Die Gruppe kann nicht zu sich selbst gehören!"); + } this.parent = parent; } public void setLink(String exec, @Valid Link link) throws NoAccessException { ValidationHelper.throwIfNoAdmin(this, exec); + if (link.getValue().equals(groupid.toString())) { + throw new BadArgumentException("Link kann nicht der GruppenID entsprechen."); + } this.link = link; } @@ -289,7 +318,9 @@ public class Group { } groupid = null; - type = null; + // Wenn man alles null setzt hat der cache mehr arbeit, weil dieser erst nach der löschung + // geupdated wird und sich link und mitgliedschaften selber heraussuchen muss + /*type = null; parent = null; limit = null; link = null; @@ -298,11 +329,11 @@ public class Group { title = null; description = null; body = null; - memberships = null; + memberships = null;*/ } public String format() { - return title + " " + description; + return title + " - " + description; } @Override @@ -319,4 +350,8 @@ public class Group { public static Group EMPTY() { return new Group(); } + + public long getVersion() { + return meta.getVersion(); + } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java b/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java index 60372dd..44a7a07 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java +++ b/src/main/java/mops/gruppen2/domain/model/group/GroupMeta.java @@ -21,6 +21,9 @@ class GroupMeta { if (this.version >= version) { throw new IdMismatchException("Die Gruppe ist bereits auf einem neueren Stand."); } + if (this.version + 1 != version) { + throw new IdMismatchException("Es fehlen vorherige Events."); + } return new GroupMeta(version, creator, creationDate); } diff --git a/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java b/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java index 88d069a..f0cd1f6 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java +++ b/src/main/java/mops/gruppen2/domain/model/group/GroupOptions.java @@ -20,6 +20,8 @@ class GroupOptions { boolean hasMaterialIntegration; boolean hasTermineIntegration; boolean hasPortfolioIntegration; + boolean hasForumsIntegration; + boolean hasModulesIntegration; static GroupOptions DEFAULT() { return new GroupOptions(true, @@ -31,6 +33,8 @@ class GroupOptions { null, true, true, + true, + true, true); } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/SortHelper.java b/src/main/java/mops/gruppen2/domain/model/group/SortHelper.java deleted file mode 100644 index 0dbf19d..0000000 --- a/src/main/java/mops/gruppen2/domain/model/group/SortHelper.java +++ /dev/null @@ -1,51 +0,0 @@ -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 void sortByGroupType(List groups) { - groups.sort((Group g1, Group g2) -> { - if (g1.getType() == Type.LECTURE) { - return -1; - } - if (g2.getType() == Type.LECTURE) { - return 1; - } - - if (g1.getType() == Type.PUBLIC) { - return -1; - } - - if (g2.getType() == Type.PUBLIC) { - return 1; - } - - return 0; - }); - } - - 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/group/User.java b/src/main/java/mops/gruppen2/domain/model/group/User.java index 5fa6802..7f781ca 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/User.java +++ b/src/main/java/mops/gruppen2/domain/model/group/User.java @@ -12,6 +12,7 @@ import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; @Log4j2 @Value +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @AllArgsConstructor public class User { 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 index a79db9a..963dd94 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/wrapper/Link.java +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Link.java @@ -4,18 +4,24 @@ 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; + UUID value; + + public Link(String value) { + this.value = UUID.fromString(value); + } public static Link RANDOM() { return new Link(UUID.randomUUID().toString()); } + + public String getValue() { + return value.toString(); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java index a7cd712..31ea0b8 100644 --- a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java +++ b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java @@ -5,12 +5,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.exception.BadPayloadException; -import mops.gruppen2.domain.service.helper.JsonHelper; +import mops.gruppen2.domain.service.helper.CommonHelper; +import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.persistance.EventRepository; import mops.gruppen2.persistance.dto.EventDTO; import org.springframework.stereotype.Service; +import java.sql.Timestamp; +import java.util.Collection; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; @Log4j2 @@ -52,13 +56,13 @@ public class EventStoreService { */ private static EventDTO getDTOFromEvent(Event event) { try { - String payload = JsonHelper.serializeEvent(event); + String payload = FileHelper.serializeEventJson(event); return new EventDTO(null, event.getGroupid().toString(), event.getVersion(), event.getExec(), event.getTarget(), - event.type(), + Timestamp.valueOf(event.getDate()), payload); } catch (JsonProcessingException e) { log.error("Event ({}) konnte nicht serialisiert werden!", event, e); @@ -81,7 +85,7 @@ public class EventStoreService { private static Event getEventFromDTO(EventDTO dto) { try { - return JsonHelper.deserializeEvent(dto.getEvent_payload()); + return FileHelper.deserializeEventJson(dto.getEvent_payload()); } catch (JsonProcessingException e) { log.error("Payload {} konnte nicht deserialisiert werden!", dto.getEvent_payload(), e); throw new BadPayloadException(EventStoreService.class.toString()); @@ -95,4 +99,32 @@ public class EventStoreService { public List findAllEvents() { return getEventsFromDTOs(eventStore.findAllEvents()); } + + public List findGroupEvents(UUID groupId) { + return getEventsFromDTOs(eventStore.findGroupEvents(groupId.toString())); + } + + public List findGroupEvents(List ids) { + return ids.stream() + .map(id -> eventStore.findGroupEvents(id.toString())) + .map(EventStoreService::getEventsFromDTOs) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + public List findGroupPayloads(UUID groupId) { + return eventStore.findGroupPayloads(groupId.toString()); + } + + public List findGroupDTOs(UUID groupid) { + return eventStore.findGroupEvents(groupid.toString()); + } + + public List findChangedGroups(long eventid) { + return CommonHelper.stringsToUUID(eventStore.findChangedGroupIds(eventid)); + } + + public long findMaxEventId() { + return eventStore.findMaxEventId(); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/GroupService.java b/src/main/java/mops/gruppen2/domain/service/GroupService.java index 1522394..9569a67 100644 --- a/src/main/java/mops/gruppen2/domain/service/GroupService.java +++ b/src/main/java/mops/gruppen2/domain/service/GroupService.java @@ -24,12 +24,15 @@ 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 mops.gruppen2.domain.service.helper.ValidationHelper; import mops.gruppen2.infrastructure.GroupCache; import org.springframework.stereotype.Service; +import javax.validation.Valid; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; /** * Behandelt Aufgaben, welche sich auf eine Gruppe beziehen. @@ -94,9 +97,11 @@ public class GroupService { * @param exec Ausführender User */ public void addUsersToGroup(Group group, String exec, List newUsers) { - setLimit(group, exec, getAdjustedUserLimit(newUsers, group)); + List users = newUsers.stream().distinct().collect(Collectors.toUnmodifiableList()); - newUsers.forEach(newUser -> addUserSilent(group, exec, newUser.getId(), newUser)); + setLimit(group, exec, getAdjustedUserLimit(users, group)); + + users.forEach(newUser -> addUserSilent(group, exec, newUser.getId(), newUser)); } /** @@ -123,6 +128,8 @@ public class GroupService { * @throws EventException Falls der User nicht gefunden wird */ public void toggleMemberRole(Group group, String exec, String target) { + ValidationHelper.throwIfNoMember(group, target); + updateRole(group, exec, target, group.getRole(target).toggle()); } @@ -192,7 +199,7 @@ public class GroupService { * Prüft, ob der Nutzer Admin ist und ob der Titel valide ist. * Bei keiner Änderung wird nichts erzeugt. */ - public void setTitle(Group group, String exec, Title title) { + public void setTitle(Group group, String exec, @Valid Title title) { if (group.getTitle().equals(title.getValue())) { return; } @@ -205,7 +212,7 @@ public class GroupService { * Prüft, ob der Nutzer Admin ist und ob die Beschreibung valide ist. * Bei keiner Änderung wird nichts erzeugt. */ - public void setDescription(Group group, String exec, Description description) { + public void setDescription(Group group, String exec, @Valid Description description) { if (group.getDescription().equals(description.getValue())) { return; } @@ -231,7 +238,7 @@ public class GroupService { * Prüft, ob der Nutzer Admin ist und ob das Limit valide ist. * Bei keiner Änderung wird nichts erzeugt. */ - public void setLimit(Group group, String exec, Limit userLimit) { + public void setLimit(Group group, String exec, @Valid Limit userLimit) { if (userLimit.getValue() == group.getLimit()) { return; } @@ -247,7 +254,8 @@ public class GroupService { applyAndSave(group, new SetParentEvent(group.getId(), exec, parent)); } - public void setLink(Group group, String exec, Link link) { + //TODO: UI Link regenerieren button + public void setLink(Group group, String exec, @Valid Link link) { if (group.getLink().equals(link.getValue())) { return; } diff --git a/src/main/java/mops/gruppen2/domain/service/SearchService.java b/src/main/java/mops/gruppen2/domain/service/SearchService.java index d2bedeb..06b65f3 100644 --- a/src/main/java/mops/gruppen2/domain/service/SearchService.java +++ b/src/main/java/mops/gruppen2/domain/service/SearchService.java @@ -4,11 +4,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.model.group.Group; -import mops.gruppen2.domain.model.group.SortHelper; +import mops.gruppen2.domain.model.group.Type; import mops.gruppen2.infrastructure.GroupCache; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -30,25 +31,35 @@ public class SearchService { * * @throws EventException Projektionsfehler */ - //TODO: search in lectures - public List search(String search, String principal) { + public List searchString(String search, String principal) { List groups = new ArrayList<>(); groups.addAll(groupCache.publics()); groups.addAll(groupCache.lectures()); groups = removeUserGroups(groups, principal); - SortHelper.sortByGroupType(groups); if (search.isEmpty()) { return groups; } log.debug("Es wurde gesucht nach: {}", search); - return groups.stream() .filter(group -> group.format().toLowerCase().contains(search.toLowerCase())) .collect(Collectors.toList()); } + public List searchType(Type type, String principal) { + log.debug("Es wurde gesucht nach: {}", type); + + if (type == Type.LECTURE) { + return removeUserGroups(groupCache.lectures(), principal); + } + if (type == Type.PUBLIC) { + return removeUserGroups(groupCache.publics(), principal); + } + + return Collections.emptyList(); + } + private static List removeUserGroups(List groups, String principal) { return groups.stream() .filter(group -> !group.isMember(principal)) diff --git a/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java index 906d979..7f1d283 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java @@ -3,13 +3,24 @@ package mops.gruppen2.domain.service.helper; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.infrastructure.api.GroupRequestWrapper; +import mops.gruppen2.infrastructure.api.GroupWrapper; + +import java.util.List; +import java.util.stream.Collectors; -//TODO: sinnvolles format @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class APIHelper { - /*public static GroupRequestWrapper wrap(long status, List groupList) { - return new GroupRequestWrapper(status, groupList); - }*/ + public static GroupRequestWrapper wrap(long status, List groupList) { + return new GroupRequestWrapper(status, wrap(groupList)); + } + + public static List wrap(List groups) { + return groups.stream() + .map(GroupWrapper::new) + .collect(Collectors.toUnmodifiableList()); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java index 4694501..25a8575 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java @@ -4,7 +4,9 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -13,4 +15,10 @@ public final class CommonHelper { public static boolean uuidIsEmpty(UUID uuid) { return "00000000-0000-0000-0000-000000000000".equals(uuid.toString()); } + + public static List stringsToUUID(List changedGroupIds) { + return changedGroupIds.stream() + .map(UUID::fromString) + .collect(Collectors.toUnmodifiableList()); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java deleted file mode 100644 index c087540..0000000 --- a/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java +++ /dev/null @@ -1,48 +0,0 @@ -package mops.gruppen2.domain.service.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.group.User; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@Log4j2 -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class CsvHelper { - - public static List readCsvFile(MultipartFile file) throws EventException { - if (file == null || file.isEmpty()) { - return Collections.emptyList(); - } - - try { - List userList = read(file.getInputStream()); - return userList.stream() - .distinct() - .collect(Collectors.toList()); //filter duplicates from list - } catch (IOException e) { - log.error("File konnte nicht gelesen werden!", e); - throw new WrongFileException(file.getOriginalFilename()); - } - } - - private static List read(InputStream stream) throws IOException { - CsvMapper mapper = new CsvMapper(); - - CsvSchema schema = mapper.schemaFor(User.class).withHeader().withColumnReordering(true); - ObjectReader reader = mapper.readerFor(User.class).with(schema); - - return reader.readValues(stream).readAll(); - } -} diff --git a/src/main/java/mops/gruppen2/domain/service/helper/FileHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/FileHelper.java new file mode 100644 index 0000000..78857a6 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/service/helper/FileHelper.java @@ -0,0 +1,148 @@ +package mops.gruppen2.domain.service.helper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +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.exception.WrongFileException; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.persistance.dto.EventDTO; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class FileHelper { + + // ######################################## CSV ############################################# + + + public static List readCsvFile(MultipartFile file) throws EventException { + if (file == null || file.isEmpty()) { + return Collections.emptyList(); + } + + try { + List userList = readCsv(file.getInputStream()); + return userList.stream() + .distinct() + .collect(Collectors.toList()); //filter duplicates from list + } catch (IOException e) { + log.error("File konnte nicht gelesen werden!", e); + throw new WrongFileException(file.getOriginalFilename()); + } + } + + private static List readCsv(InputStream stream) throws IOException { + CsvMapper mapper = new CsvMapper(); + + CsvSchema schema = mapper.schemaFor(User.class).withHeader().withColumnReordering(true); + ObjectReader reader = mapper.readerFor(User.class).with(schema); + + return reader.readValues(stream).readAll(); + } + + public static String writeCsvUserList(List members) { + StringBuilder builder = new StringBuilder(); + builder.append("id,givenname,familyname,email\n"); + + members.forEach(user -> builder.append(user.getId()) + .append(",") + .append(user.getGivenname()) + .append(",") + .append(user.getFamilyname()) + .append(",") + .append(user.getEmail()) + .append("\n")); + + return builder.toString(); + } + + + // ########################################## JSON ########################################### + + + /** + * Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload. + * + * @param event Java-Event-Repräsentation + * + * @return JSON-Event-Payload als String + * + * @throws JsonProcessingException Bei JSON Fehler + */ + + public static String serializeEventJson(Event event) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + String payload = mapper.writeValueAsString(event); + log.trace(payload); + return payload; + } + + /** + * Übersetzt eine JSON-Event-Payload zu einer Java-Event-Repräsentation. + * + * @param json JSON-Event-Payload als String + * + * @return Java-Event-Repräsentation + * + * @throws JsonProcessingException Bei JSON Fehler + */ + public static Event deserializeEventJson(String json) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + Event event = mapper.readValue(json, Event.class); + log.trace(event); + return event; + } + + + // ############################################### TXT ####################################### + + + public static String payloadsToPlain(List payloads) { + return payloads.stream() + .map(payload -> payload + "\n") + .reduce((String payloadA, String payloadB) -> payloadA + payloadB) + .orElseThrow(() -> new GroupNotFoundException("Keine Payloads gefunden.")); + } + + public static String eventDTOsToSql(List dtos) { + StringBuilder builder = new StringBuilder(); + + builder.append("INSERT INTO event(group_id, group_version, exec_id, target_id, event_date, event_payload)\nVALUES\n"); + + dtos.forEach(dto -> builder.append("('") + .append(dto.getGroup_id()) + .append("','") + .append(dto.getGroup_version()) + .append("','") + .append(dto.getExec_id()) + .append("','") + .append(dto.getTarget_id()) + .append("','") + .append(dto.getEvent_date()) + .append("','") + .append(dto.getEvent_payload()) + .append("'),\n")); + + builder.replace(builder.length() - 2, builder.length(), ";"); + + return builder.toString(); + } + + + // ############################################### SQL ####################################### +} diff --git a/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java deleted file mode 100644 index b66c57f..0000000 --- a/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -package mops.gruppen2.domain.service.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; - -/** - * Übersetzt JSON-Event-Payloads zu Java-Event-Repräsentationen und zurück. - */ -@Log4j2 -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class JsonHelper { - - /** - * Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload. - * - * @param event Java-Event-Repräsentation - * - * @return JSON-Event-Payload als String - * - * @throws JsonProcessingException Bei JSON Fehler - */ - - public static String serializeEvent(Event event) throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); - String payload = mapper.writeValueAsString(event); - log.trace(payload); - return payload; - } - - /** - * Übersetzt eine JSON-Event-Payload zu einer Java-Event-Repräsentation. - * - * @param json JSON-Event-Payload als String - * - * @return Java-Event-Repräsentation - * - * @throws JsonProcessingException Bei JSON Fehler - */ - public static Event deserializeEvent(String json) throws JsonProcessingException { - 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/service/helper/ProjectionHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java index f4f9011..2e686e9 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java @@ -7,6 +7,9 @@ import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.infrastructure.GroupCache; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -19,6 +22,21 @@ import java.util.UUID; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ProjectionHelper { + public static List project(List events) { + Map groups = new HashMap<>(); + + if (events.isEmpty()) { + return Collections.emptyList(); + } + + log.trace(groups); + log.trace(events); + + events.forEach(event -> event.apply(getOrCreateGroup(groups, event.getGroupid()))); + + return new ArrayList<>(groups.values()); + } + public static void project(Map groups, List events, GroupCache cache) { if (events.isEmpty()) { return; diff --git a/src/main/java/mops/gruppen2/domain/service/helper/SortHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/SortHelper.java new file mode 100644 index 0000000..2f7583f --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/service/helper/SortHelper.java @@ -0,0 +1,27 @@ +package mops.gruppen2.domain.service.helper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import mops.gruppen2.domain.model.group.Membership; +import mops.gruppen2.domain.model.group.Role; + +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class SortHelper { + + 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/service/helper/ValidationHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/ValidationHelper.java index 4e3ddd5..c82fff4 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/ValidationHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/ValidationHelper.java @@ -7,7 +7,7 @@ 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.UserAlreadyExistsException; +import mops.gruppen2.domain.exception.UserExistsException; import mops.gruppen2.domain.exception.UserNotFoundException; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.Type; @@ -39,10 +39,10 @@ public final class ValidationHelper { // ######################################## THROW ############################################ - public static void throwIfMember(Group group, String userid) throws UserAlreadyExistsException { + public static void throwIfMember(Group group, String userid) throws UserExistsException { if (group.isMember(userid)) { log.error("Benutzer {} ist schon in Gruppe {}", userid, group); - throw new UserAlreadyExistsException(userid); + throw new UserExistsException(userid); } } diff --git a/src/main/java/mops/gruppen2/infrastructure/GroupCache.java b/src/main/java/mops/gruppen2/infrastructure/GroupCache.java index d3999fd..9dc33de 100644 --- a/src/main/java/mops/gruppen2/infrastructure/GroupCache.java +++ b/src/main/java/mops/gruppen2/infrastructure/GroupCache.java @@ -3,6 +3,8 @@ package mops.gruppen2.infrastructure; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.exception.GroupNotFoundException; +import mops.gruppen2.domain.exception.IdMismatchException; +import mops.gruppen2.domain.exception.UserNotFoundException; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.Type; import mops.gruppen2.domain.service.EventStoreService; @@ -17,7 +19,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; +/** + * Cached alle existierenden Gruppen und einige Beziehungen. + * Gruppen können nach Typ angefragt werden, nach ID, nach Link oder nach User. + * Der Cache wird von den Events aktualisiert. + * Beim Aufruf der init() Methode werden alle bisherigen Events projiziert und die Gruppen gespeichert. + * Die Komplette Anwendung verwendet eine Instanz des Caches. + */ @Log4j2 @RequiredArgsConstructor @Component @@ -28,7 +38,7 @@ public class GroupCache { private final Map groups = new HashMap<>(); private final Map links = new HashMap<>(); - private final Map> users = new HashMap<>(); + private final Map> users = new HashMap<>(); // Wird vielleicht zu groß? private final Map> types = new EnumMap<>(Type.class); @@ -59,6 +69,14 @@ public class GroupCache { return links.get(link); } + public List groups() { + if (groups.isEmpty()) { + return Collections.emptyList(); + } + + return List.copyOf(groups.values()); + } + public List userGroups(String userid) { if (!users.containsKey(userid)) { return Collections.emptyList(); @@ -67,6 +85,24 @@ public class GroupCache { return Collections.unmodifiableList(users.get(userid)); } + public List userLectures(String userid) { + return userGroups(userid).stream() + .filter(Group::isLecture) + .collect(Collectors.toUnmodifiableList()); + } + + public List userPublics(String userid) { + return userGroups(userid).stream() + .filter(Group::isPublic) + .collect(Collectors.toUnmodifiableList()); + } + + public List userPrivates(String userid) { + return userGroups(userid).stream() + .filter(Group::isPrivate) + .collect(Collectors.toUnmodifiableList()); + } + public List publics() { if (!types.containsKey(Type.PUBLIC)) { return Collections.emptyList(); @@ -96,6 +132,9 @@ public class GroupCache { public void usersPut(String userid, Group group) { + if (!group.isMember(userid)) { + throw new UserNotFoundException("User ist kein Mitglied, Gruppe nicht gecached."); + } if (!users.containsKey(userid)) { users.put(userid, new ArrayList<>()); log.debug("Ein User wurde dem Cache hinzugefügt."); @@ -113,18 +152,37 @@ public class GroupCache { } public void groupsPut(UUID groupid, Group group) { + if (group.getId() != groupid) { + throw new IdMismatchException("ID passt nicht zu Gruppe, Gruppe nicht gecached."); + } + groups.put(groupid, group); } - public void groupsRemove(Group group) { - groups.remove(group.getId()); + public void groupsRemove(UUID groupid, Group group) { + if (!groups.containsKey(groupid)) { + return; + } + + groups.remove(groupid); + links.remove(group.getLink()); + group.getMembers().forEach(user -> users.get(user.getId()).removeIf(usergroup -> !usergroup.exists())); + types.get(group.getType()).removeIf(typegroup -> !typegroup.exists()); } public void linksPut(String link, Group group) { + if (!link.equals(group.getLink())) { + throw new IdMismatchException("Link passt nicht zu Gruppe, Gruppe nicht gecached."); + } + links.put(link, group); } public void linksRemove(String link) { + if (!links.containsKey(link)) { + return; + } + links.remove(link); } @@ -133,15 +191,18 @@ public class GroupCache { types.put(type, new ArrayList<>()); log.debug("Ein Typ wurde dem Cache hinzugefügt."); } + if (group.getType() != type) { + throw new IdMismatchException("Typ passt nicht zu Gruppe, Gruppe nicht gecached."); + } types.get(type).add(group); } - public void typesRemove(Group group) { - if (!types.containsKey(group.getType())) { + public void typesRemove(Type type, Group group) { + if (!types.containsKey(type)) { return; } - types.get(group.getType()).remove(group); + types.get(type).remove(group); } } diff --git a/src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java b/src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java index 93a0c20..d2d059d 100644 --- a/src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java +++ b/src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java @@ -1,12 +1,14 @@ package mops.gruppen2.infrastructure.api; import lombok.AllArgsConstructor; +import lombok.Getter; import java.util.List; /** * Kombiniert den Status und die Gruppenliste zur ausgabe über die API. */ +@Getter @AllArgsConstructor public class GroupRequestWrapper { diff --git a/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java b/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java index 2fb3fba..75b628d 100644 --- a/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java +++ b/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java @@ -1,4 +1,31 @@ package mops.gruppen2.infrastructure.api; +import lombok.Value; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.model.group.User; + +import java.util.List; +import java.util.UUID; + +@Value public class GroupWrapper { + + UUID groupid; + Type type; + UUID parent; + String title; + String description; + List admins; + List regulars; + + public GroupWrapper(Group group) { + groupid = group.getId(); + type = group.getType(); + parent = group.getParent(); + title = group.getTitle(); + description = group.getDescription(); + admins = group.getAdmins(); + regulars = group.getRegulars(); + } } diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java b/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java index ac02052..1c79bd2 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java @@ -1,13 +1,27 @@ package mops.gruppen2.infrastructure.controller; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; +import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.domain.service.helper.APIHelper; +import mops.gruppen2.domain.service.helper.ProjectionHelper; +import mops.gruppen2.infrastructure.GroupCache; +import mops.gruppen2.infrastructure.api.GroupRequestWrapper; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + /** * Api zum Datenabgleich. */ @@ -18,8 +32,7 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/gruppen2/api") public class APIController { - //TODO: redo api - + private final GroupCache cache; private final EventStoreService eventStoreService; /** @@ -28,38 +41,43 @@ public class APIController { * * @param eventId Die Event-ID, welche der Anfragesteller beim letzten Aufruf erhalten hat */ - /*@GetMapping("/update/{id}") + //TODO: sollte den cache benutzen, am besten wäre eine groupversion, welche der eventid + //TODO: entspricht, dann kann man leicht alle geänderten gruppen finden + @GetMapping("/update/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt veränderte Gruppen zurück") public GroupRequestWrapper getApiUpdate(@ApiParam("Letzte gespeicherte EventId des Anfragestellers") @PathVariable("id") long eventId) { return APIHelper.wrap(eventStoreService.findMaxEventId(), - projectionHelper.projectChangedGroups(eventId)); - }*/ + ProjectionHelper.project(eventStoreService.findGroupEvents(eventStoreService.findChangedGroups(eventId)))); + } /** * Gibt die Gruppen-IDs von Gruppen, in welchen der übergebene Nutzer teilnimmt, zurück. */ - /*@GetMapping("/usergroups/{id}") + @GetMapping("/usergroups/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt Gruppen zurück, in welchen ein Nutzer teilnimmt") public List getApiUserGroups(@ApiParam("Nutzer-Id") @PathVariable("id") String userId) { - return CommonHelper.uuidsToString(eventStoreService.findExistingUserGroups(userId)); - }*/ + return cache.userGroups(userId).stream() + .map(Group::getId) + .map(UUID::toString) + .collect(Collectors.toUnmodifiableList()); + } /** * Konstruiert eine einzelne, vollständige Gruppe. */ - /*@GetMapping("/group/{id}") + @GetMapping("/group/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück") public Group getApiGroup(@ApiParam("Gruppen-Id der gefordeten Gruppe") @PathVariable("id") String groupId) { - return projectionHelper.projectGroupById(UUID.fromString(groupId)); - }*/ + return cache.group(UUID.fromString(groupId)); + } } diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java index 84d4e1e..60cb879 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java @@ -11,7 +11,7 @@ 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.helper.CsvHelper; +import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.domain.service.helper.ValidationHelper; import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; @@ -67,7 +67,7 @@ public class GroupCreationController { // ROLE_studentin kann kein CSV importieren if (token.getAccount().getRoles().contains("orga")) { - groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); + groupService.addUsersToGroup(group, principal, FileHelper.readCsvFile(file)); } return "redirect:/gruppen2/details/" + group.getId(); diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java index 5fffd22..a75abf6 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java @@ -8,11 +8,13 @@ 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.EventStoreService; import mops.gruppen2.domain.service.GroupService; -import mops.gruppen2.domain.service.helper.CsvHelper; +import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.domain.service.helper.ValidationHelper; import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -24,7 +26,9 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.io.IOException; import java.util.UUID; @SuppressWarnings("SameReturnValue") @@ -37,6 +41,7 @@ public class GroupDetailsController { private final GroupCache groupCache; private final GroupService groupService; + private final EventStoreService eventStoreService; @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @GetMapping("/details/{id}") @@ -94,6 +99,77 @@ public class GroupDetailsController { return "redirect:/gruppen2"; } + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping("details/{id}/history") + public String getDetailsHistory(KeycloakAuthenticationToken token, + Model model, + @PathVariable("id") String groupId) { + + model.addAttribute("events", + eventStoreService.findGroupEvents(UUID.fromString(groupId))); + + return "log"; + } + + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping(value = "details/{id}/export/history/plain", produces = "text/plain;charset=UTF-8") + public void getDetailsExportHistoryPlain(HttpServletResponse response, + @PathVariable("id") String groupId) { + + String filename = "eventlog-" + groupId + ".txt"; + + response.setContentType("text/txt;charset=UTF-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + filename + "\""); + + try { + response.getWriter() + .write(FileHelper.payloadsToPlain( + eventStoreService.findGroupPayloads(UUID.fromString(groupId)))); + } catch (IOException e) { + log.error("Payloads konnten nicht geschrieben werden.", e); + } + } + + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping(value = "details/{id}/export/history/sql", produces = "application/sql;charset=UTF-8") + public void getDetailsExportHistorySql(HttpServletResponse response, + @PathVariable("id") String groupId) { + + String filename = "data.sql"; + + response.setContentType("application/sql;charset=UTF-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + filename + "\""); + + try { + response.getWriter() + .write(FileHelper.eventDTOsToSql( + eventStoreService.findGroupDTOs(UUID.fromString(groupId)))); + } catch (IOException e) { + log.error("Payloads konnten nicht geschrieben werden.", e); + } + } + + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping(value = "details/{id}/export/members", produces = "text/csv;charset=UTF-8") + public void getDetailsExportMembers(HttpServletResponse response, + @PathVariable("id") String groupId) { + + String filename = "teilnehmer-" + groupId + ".csv"; + + response.setContentType("text/csv;charset=UTF-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + filename + "\""); + + try { + response.getWriter() + .print(FileHelper.writeCsvUserList(groupCache.group(UUID.fromString(groupId)).getMembers())); + } catch (IOException e) { + log.error("Teilnehmerliste konnte nicht geschrieben werden.", e); + } + } + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @GetMapping("/details/{id}/edit") public String getDetailsEdit(KeycloakAuthenticationToken token, @@ -127,8 +203,6 @@ public class GroupDetailsController { String principal = token.getName(); Group group = groupCache.group(UUID.fromString(groupId)); - System.out.println(group); - groupService.setTitle(group, principal, title); groupService.setDescription(group, principal, description); @@ -157,7 +231,7 @@ public class GroupDetailsController { String principal = token.getName(); Group group = groupCache.group(UUID.fromString(groupId)); - groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); + groupService.addUsersToGroup(group, principal, FileHelper.readCsvFile(file)); return "redirect:/gruppen2/details/" + groupId + "/edit"; } @@ -172,6 +246,9 @@ public class GroupDetailsController { Group group = groupCache.group(UUID.fromString(groupId)); ValidationHelper.throwIfNoAdmin(group, principal); + if (target.equals(principal)) { + ValidationHelper.throwIfLastAdmin(group, principal); + } groupService.toggleMemberRole(group, principal, target); diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java index 4d09bdb..2e414ba 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java @@ -33,7 +33,10 @@ public class GruppenfindungController { @GetMapping("/gruppen2") public String getIndexPage(KeycloakAuthenticationToken token, Model model) { - model.addAttribute("groups", groupCache.userGroups(token.getName())); + + model.addAttribute("lectures", groupCache.userLectures(token.getName())); + model.addAttribute("publics", groupCache.userPublics(token.getName())); + model.addAttribute("privates", groupCache.userPrivates(token.getName())); return "index"; } diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java b/src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java index 1c96f01..35ae56c 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java @@ -41,13 +41,40 @@ public class SearchAndInviteController { } @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) - @PostMapping("/search") - public String postSearch(KeycloakAuthenticationToken token, - Model model, - @RequestParam("string") String search) { + @PostMapping("/search/string") + public String postSearchString(KeycloakAuthenticationToken token, + Model model, + @RequestParam("string") String search) { String principal = token.getName(); - List groups = searchService.search(search, principal); + List groups = searchService.searchString(search, principal); + + model.addAttribute("groups", groups); + + return "search"; + } + + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping("/search/all") + public String getSearchAll(KeycloakAuthenticationToken token, + Model model) { + + String principal = token.getName(); + List groups = searchService.searchString("", principal); + + model.addAttribute("groups", groups); + + return "search"; + } + + @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) + @GetMapping("/search/type/{type}") + public String getSearchType(KeycloakAuthenticationToken token, + Model model, + @PathVariable("type") Type type) { + + String principal = token.getName(); + List groups = searchService.searchType(type, principal); model.addAttribute("groups", groups); diff --git a/src/main/java/mops/gruppen2/persistance/EventRepository.java b/src/main/java/mops/gruppen2/persistance/EventRepository.java index 26a94bb..8d21c79 100644 --- a/src/main/java/mops/gruppen2/persistance/EventRepository.java +++ b/src/main/java/mops/gruppen2/persistance/EventRepository.java @@ -3,6 +3,7 @@ package mops.gruppen2.persistance; import mops.gruppen2.persistance.dto.EventDTO; 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; import java.util.List; @@ -16,4 +17,16 @@ public interface EventRepository extends CrudRepository { @Query("SELECT * FROM event") List findAllEvents(); + + @Query("SELECT * FROM event WHERE group_id = :groupid") + List findGroupEvents(@Param("groupid") String groupId); + + @Query("SELECT event_payload FROM event WHERE group_id = :groupid") + List findGroupPayloads(@Param("groupid") String groupId); + + @Query("SELECT MAX(event_id) FROM event") + long findMaxEventId(); + + @Query("SELECT DISTINCT group_id FROM event WHERE event_id > :eventid") + List findChangedGroupIds(@Param("eventid") long eventid); } diff --git a/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java b/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java index d0c326f..c46878a 100644 --- a/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java +++ b/src/main/java/mops/gruppen2/persistance/dto/EventDTO.java @@ -5,6 +5,8 @@ import lombok.Getter; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Table; +import java.sql.Timestamp; + @Table("event") @Getter @AllArgsConstructor @@ -19,6 +21,6 @@ public class EventDTO { String exec_id; String target_id; - String event_type; + Timestamp event_date; String event_payload; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 45828c0..be154ec 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -11,12 +11,12 @@ spring.profiles.active = dev #keycloak.use-resource-role-mappings = true #keycloak.autodetect-bearer-only = true #keycloak.confidential-port = 443 -keycloak.auth-server-url = https://gruppenkeycloak.herokuapp.com/auth -hhu_keycloak.token-uri = https://gruppenkeycloak.herokuapp.com/auth/realms/master/protocol/openid-connect/token +keycloak.auth-server-url = https://churl-keycloak.herokuapp.com/auth +hhu_keycloak.token-uri = https://churl-keycloak.herokuapp.com/auth/realms/gruppen/protocol/openid-connect/token keycloak.principal-attribute = preferred_username -keycloak.realm = master +keycloak.realm = gruppen keycloak.resource = gruppen-app -keycloak.credentials.secret = f73354fd-614e-4e24-b801-a8d0f4abf531 +keycloak.credentials.secret = 2e2e5770-c454-4d31-be99-9d8c34c93089 keycloak.verify-token-audience = true keycloak.use-resource-role-mappings = true keycloak.autodetect-bearer-only = true diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..45613fb --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,27 @@ +INSERT INTO event(group_id, group_version, exec_id, target_id, event_date, event_payload) +VALUES ('e65dd5f1-b252-4512-8db4-0407b31d199f', '1', 'orga', 'null', '2020-04-17 18:52:01.259555', + '{"class":"CREATEGROUP","date":[2020,4,17,18,52,1,259555000],"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":1,"exec":"orga","target":null}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '2', 'orga', 'orga', '2020-04-17 18:52:01.291448', + '{"class":"ADDMEMBER","user":{"id":"orga","givenname":"Thomas","familyname":"Organisator","email":"orga@hhu.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":2,"exec":"orga","target":"orga","date":[2020,4,17,18,52,1,291448000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '3', 'orga', 'orga', '2020-04-17 18:52:01.301972', + '{"class":"UPDATEROLE","role":"ADMIN","groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":3,"exec":"orga","target":"orga","date":[2020,4,17,18,52,1,301972000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '4', 'orga', 'null', '2020-04-17 18:52:01.3053', + '{"class":"SETLIMIT","limit":{"value":1000},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":4,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,305300000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '5', 'orga', 'null', '2020-04-17 18:52:01.310406', + '{"class":"SETTYPE","type":"LECTURE","groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":5,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,310406000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '6', 'orga', 'null', '2020-04-17 18:52:01.313421', + '{"class":"SETPARENT","parent":{"id":"00000000-0000-0000-0000-000000000000"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":6,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,313421000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '7', 'orga', 'null', '2020-04-17 18:52:01.317931', + '{"class":"SETTITLE","title":{"value":"Programmierung SS2020"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":7,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,317931000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '8', 'orga', 'null', '2020-04-17 18:52:01.320693', + '{"class":"SETDESCRIPTION","desc":{"value":"Einführung in die objektorientierte Programmierung mit Java."},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":8,"exec":"orga","target":null,"date":[2020,4,17,18,52,1,320693000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '9', 'orga', 'A5ggd', '2020-04-17 18:52:01.330879', + '{"class":"ADDMEMBER","user":{"id":"A5ggd","givenname":"peter","familyname":"pan","email":"peter@pan.com"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":9,"exec":"orga","target":"A5ggd","date":[2020,4,17,18,52,1,330879000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '10', 'orga', 'Affs', '2020-04-17 18:52:01.333756', + '{"class":"ADDMEMBER","user":{"id":"Affs","givenname":"olaf","familyname":"pomodoro","email":"ol@f-99.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":10,"exec":"orga","target":"Affs","date":[2020,4,17,18,52,1,333756000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '11', 'orga', '55fdd', '2020-04-17 18:52:01.336206', + '{"class":"ADDMEMBER","user":{"id":"55fdd","givenname":"dieter","familyname":"niemöller","email":"pfarrer@erde.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":11,"exec":"orga","target":"55fdd","date":[2020,4,17,18,52,1,336206000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '12', 'orga', '22ööl', '2020-04-17 18:52:01.338582', + '{"class":"ADDMEMBER","user":{"id":"22ööl","givenname":"thomas","familyname":"müller","email":"thot@scheisse.de"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":12,"exec":"orga","target":"22ööl","date":[2020,4,17,18,52,1,338582000]}'), + ('e65dd5f1-b252-4512-8db4-0407b31d199f', '13', 'orga', 'tdsd8', '2020-04-17 18:52:01.341216', + '{"class":"ADDMEMBER","user":{"id":"tdsd8","givenname":"tobidignouserandingdong","familyname":"abraham","email":"g@gmail.mail"},"groupid":"e65dd5f1-b252-4512-8db4-0407b31d199f","version":13,"exec":"orga","target":"tdsd8","date":[2020,4,17,18,52,1,341216000]}'); diff --git a/src/main/resources/schema-h2.sql b/src/main/resources/schema-h2.sql index 46b52a7..0f2779a 100644 --- a/src/main/resources/schema-h2.sql +++ b/src/main/resources/schema-h2.sql @@ -7,6 +7,6 @@ CREATE TABLE event group_version INT NOT NULL, exec_id VARCHAR(50) NOT NULL, target_id VARCHAR(50), - event_type VARCHAR(32) NOT NULL, + event_date DATETIME NOT NULL, event_payload VARCHAR(2500) NOT NULL ); diff --git a/src/main/resources/static/css/details.css b/src/main/resources/static/css/details.css index ac5b9bf..1df08cc 100644 --- a/src/main/resources/static/css/details.css +++ b/src/main/resources/static/css/details.css @@ -3,34 +3,15 @@ margin-bottom: 15px; } -/*Buttons*/ -.edit { - font-size: 20px; -} - -.btn-bar { - align-self: start; -} - -/*Badges*/ -.badge-pill { - align-self: start; - margin-right: 10px; -} - -.members .badge { - align-self: start; - margin-top: 2px; -} - /*Member List*/ .members { overflow-y: auto; + overflow-x: hidden; max-height: calc(100vh - 250px); + max-width: 100%; } -.members li span { +li span { text-overflow: ellipsis; overflow: hidden; - white-space: nowrap; } diff --git a/src/main/resources/static/css/index.css b/src/main/resources/static/css/index.css index a68447b..08993cb 100644 --- a/src/main/resources/static/css/index.css +++ b/src/main/resources/static/css/index.css @@ -1,19 +1,16 @@ /*Grouplist Hover Effect*/ .content { - padding-right: 20px; background: #e6f4ff; box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075); } .content:hover { - padding-right: 10px; background: #d4ecff; box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .25); } .content:hover .content-heading { padding-bottom: 10px; - padding-left: 10px; } .content-text-in { @@ -26,6 +23,5 @@ /*Badges*/ .badge-pill { - margin-left: 10px; - align-self: start; + margin-right: 15px; } diff --git a/src/main/resources/static/css/search.css b/src/main/resources/static/css/search.css index 7195bb3..eed7426 100644 --- a/src/main/resources/static/css/search.css +++ b/src/main/resources/static/css/search.css @@ -23,5 +23,5 @@ .badge-pill { align-self: start; margin-top: 2px; - margin-right: 5px; + margin-right: 15px; } diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index 9ca338f..dab0b4a 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -16,6 +16,9 @@ margin-bottom: 15px; box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .25); transition: 100ms; + overflow: hidden; + text-overflow: ellipsis; + cursor: default; } /*Content Heading*/ @@ -33,6 +36,12 @@ white-space: nowrap; } +h1, h3 { + cursor: default; + overflow: hidden; + text-overflow: ellipsis; +} + /*Content Paragraph*/ .content-text { margin-bottom: 15px; @@ -65,10 +74,19 @@ color: #4fa4ff; } +/*Button*/ +.btn { + width: 250px; +} + +.btn-spacer { +} + /*Badges*/ .badge-pill { cursor: default; color: whitesmoke; + align-self: start; } .lecture { @@ -87,18 +105,31 @@ background: var(--warning); } -/*Grid System*/ -.row { - margin-left: 0; - margin-right: 0; +/*Input*/ +.input-group { + width: auto; } -.col { - padding-left: 0; - padding-right: 0; +.input-group-append { + max-width: 50%; + overflow: hidden; + text-overflow: ellipsis; } -/*Miscellanous*/ -.def-cursor { - cursor: default; +.input-group-append { + max-width: 50%; + overflow: hidden; + text-overflow: ellipsis; +} + +.input-group-append span { + overflow: hidden; + text-overflow: ellipsis; + font-family: monospace; +} + +.input-group-append span { + overflow: hidden; + text-overflow: ellipsis; + font-family: monospace; } diff --git a/src/main/resources/templates/create.html b/src/main/resources/templates/create.html index b2f4c21..8c0d28b 100644 --- a/src/main/resources/templates/create.html +++ b/src/main/resources/templates/create.html @@ -9,37 +9,35 @@
-
+

Neue Gruppe

-

Neue Gruppe

+
+
+

Eigenschaften:

- -
-

Eigenschaften:

+ +
- -
+ - +
+ +
-
- -
- - -
-
- - -
+ +
-
- - -
- -
+ +
+
+ +
+ + +
+
diff --git a/src/main/resources/templates/details.html b/src/main/resources/templates/details.html index aa64206..0a4a400 100644 --- a/src/main/resources/templates/details.html +++ b/src/main/resources/templates/details.html @@ -9,60 +9,198 @@
-
+

-

+
+ +
-
- -
+
-
-
- -
- Fertig + +
+ +
+
+ Gruppenmaterialien +
- - + + +
+ +
+
+ Forum +
+ + + + +
+ + +
+
+ Gruppentermine +
+ + + + +
+ + +
+
+ Gruppenportfolio +
+ +
+

Neueste Einträge:

+ +
+ + +
+ + +
+
+ Modulhandbuch +
+ +
+

Veranstaltungsinfo:

+

+ Dieses Modul vermittelt grundlegende Programmierkenntnisse in einer + objektorientierten Programmiersprache. + Darüber hinaus werden einführend Aspekte von Algorithmen und + Datenstrukturen behandelt. + Es wird keine Programmiererfahrung vorausgesetzt. +

+
    +
  • Grundlegende Begriffe der Informatik
  • +
  • Primitive Datentypen und Variablen
  • +
  • Kontrollstrukturen
  • +
  • Eigene Datentypen (Klassen) und Arrays
  • +
  • Programmstrukturen im Speicher (Heap, Stack)
  • +
  • Konzepte der Objektorientierung (Polymorphie, Schnittstellen) +
  • +
  • Rekursion
  • +
  • Fehlerbehandlung
  • +
  • Dynamische Datenstrukturen (Listen, Binärbäume, Hashing)
  • +
  • Suchen und Sortieren (ausgewählte Algorithmen, u.a. binäre + Suche, BubbleSort, QuickSort) +
  • +
  • Datenströme (Standard-Eingabe und -Ausgabe, einfache 2D-Grafik, + Dateien) +
  • +
+
+ + +
+
+ +
+ +
+ Fertig + +
-
+
- -
- -
- Teilnehmer: - -
- - -
-
- -
-
- - -
-
    -
  • - - -
  • -
-
+ +
+ +
+ Teilnehmer: +
+ +
+
+ +
+
+ + +
+
    +
  • + + +
  • +
+
diff --git a/src/main/resources/templates/edit.html b/src/main/resources/templates/edit.html index e65cbdb..0756e05 100644 --- a/src/main/resources/templates/edit.html +++ b/src/main/resources/templates/edit.html @@ -9,113 +9,148 @@
-
-

+

- -
-
-
- -
- - - + +
+
+ Fertig +
-
+
- -
-
- Einladungslink: -
- -
- -
+ +
+
+ Einladungslink:
- - -
-
- Eigenschaften -
- - -
-
- -
-
- - -
-
-
- - -
-
- -
-
- - -
-
-
- - -
-
- -
-
- - -
-
-
-
- - -
-
- Teilnehmer -
- -
    -
  • -
    - - -
    - -
    -
    - -
    - -
    - -
    -
    -
  • -
+ +
+
+ + +
+
+ Eigenschaften +
+ + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ +
+ +
+ +
+
+
+
+ + + + + +
+
+ Teilnehmer +
+ +
    +
  • +
    + + +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
  • +
+
diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index c77a090..a2c3274 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -12,7 +12,7 @@
-

UPSI

+

UPSI

Da ist wohl etwas schiefgelaufen :(

diff --git a/src/main/resources/templates/fragments/forms.html b/src/main/resources/templates/fragments/forms.html index f38f3bc..17607d7 100644 --- a/src/main/resources/templates/fragments/forms.html +++ b/src/main/resources/templates/fragments/forms.html @@ -13,7 +13,7 @@
- Gruppentitel: + Gruppentitel:
@@ -24,7 +24,7 @@
- Beschreibung: + Beschreibung:
@@ -34,8 +34,8 @@ -
-
+
+
@@ -48,10 +48,10 @@
-
- Gehört zu: + Gehört zu:
-
+
-
-
- Limit: +
+ Limit:
-
- Teilnehmer +
+ Teilnehmer
@@ -102,7 +102,7 @@
- CSV: + CSV:
diff --git a/src/main/resources/templates/fragments/general.html b/src/main/resources/templates/fragments/general.html index 34e4b56..80e4a81 100644 --- a/src/main/resources/templates/fragments/general.html +++ b/src/main/resources/templates/fragments/general.html @@ -7,7 +7,6 @@ - diff --git a/src/main/resources/templates/fragments/groups.html b/src/main/resources/templates/fragments/groups.html index 4d5f38c..7030f88 100644 --- a/src/main/resources/templates/fragments/groups.html +++ b/src/main/resources/templates/fragments/groups.html @@ -7,17 +7,17 @@ - Privat - Öffentlich - Veranstaltung - Parent @@ -36,7 +36,7 @@ -
+
@@ -48,6 +48,19 @@ + +
+ + + +
+ +
+ +
+
+
@@ -59,15 +72,13 @@
-
+
-
- - Startseite.
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 5a3ef98..8f918c4 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -9,23 +9,26 @@
-
+

Meine Gruppen

-

Meine Gruppen

+ +
+

Veranstaltungen

- -
-
- +
+
- -
-
- -
-
+
+

Öffentliche Gruppen

+
+
+
+

Private Gruppen

+
diff --git a/src/main/resources/templates/link.html b/src/main/resources/templates/link.html index 5a5741a..c4f8f81 100644 --- a/src/main/resources/templates/link.html +++ b/src/main/resources/templates/link.html @@ -10,7 +10,7 @@
-

+

diff --git a/src/main/resources/templates/log.html b/src/main/resources/templates/log.html new file mode 100644 index 0000000..f60bd36 --- /dev/null +++ b/src/main/resources/templates/log.html @@ -0,0 +1,31 @@ + + + + + +
+

Event-Log

+ +
+
+ + + Datum: + +
+ +
+ + + +
+
+
+ + + diff --git a/src/main/resources/templates/preview.html b/src/main/resources/templates/preview.html index 94b6ea0..2b78821 100644 --- a/src/main/resources/templates/preview.html +++ b/src/main/resources/templates/preview.html @@ -10,17 +10,15 @@
-

+

-
diff --git a/src/main/resources/templates/search.html b/src/main/resources/templates/search.html index 132d82d..4ed2c60 100644 --- a/src/main/resources/templates/search.html +++ b/src/main/resources/templates/search.html @@ -8,38 +8,44 @@ + + +
-
+

Suchen

-

Suchen

- - -
-
-
-
- Suchbegriff: + +
+ +
+
+
+ Suchbegriff:
- +
- - -
- -
-
- - - +
-
- -
-
+ +
+ + +
diff --git a/src/test/java/mops/gruppen2/GroupBuilder.java b/src/test/java/mops/gruppen2/GroupBuilder.java new file mode 100644 index 0000000..154c27c --- /dev/null +++ b/src/test/java/mops/gruppen2/GroupBuilder.java @@ -0,0 +1,154 @@ +package mops.gruppen2; + +import mops.gruppen2.domain.event.AddMemberEvent; +import mops.gruppen2.domain.event.CreateGroupEvent; +import mops.gruppen2.domain.event.DestroyGroupEvent; +import mops.gruppen2.domain.event.Event; +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.SetTitleEvent; +import mops.gruppen2.domain.event.SetTypeEvent; +import mops.gruppen2.domain.event.UpdateRoleEvent; +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.Title; +import mops.gruppen2.infrastructure.GroupCache; + +import java.time.LocalDateTime; +import java.util.UUID; + +import static mops.gruppen2.TestHelper.uuid; + +public final class GroupBuilder { + + private final UUID groupid; + private int version; + private final GroupCache groupCache; + private final Group group = Group.EMPTY(); + + private GroupBuilder(GroupCache cache, UUID id) { + groupCache = cache; + groupid = id; + } + + /** + * Erzeugt neuen GruppenBuilder mit Cache und ID + */ + public static GroupBuilder get(GroupCache cache, int id) { + return new GroupBuilder(cache, uuid(id)); + } + + /** + * Initialisiert Gruppe mit Id, Creator und Zeit + */ + public GroupBuilder group() { + return apply(new CreateGroupEvent(groupid, "TEST", LocalDateTime.now())); + } + + /** + * Initialisiert TestAdmin + */ + public GroupBuilder testadmin() { + apply(new AddMemberEvent(groupid, "TEST", "TEST", new User("TEST"))); + return apply(new UpdateRoleEvent(groupid, "TEST", "TEST", Role.ADMIN)); + } + + /** + * Fügt Nutzer hinzu + */ + public GroupBuilder add(String userid) { + return apply(new AddMemberEvent(groupid, "TEST", userid, new User(userid))); + } + + /** + * Entfernt Nutzer + */ + public GroupBuilder kick(String userid) { + return apply(new KickMemberEvent(groupid, "TEST", userid)); + } + + public GroupBuilder limit(int i) { + return apply(new SetLimitEvent(groupid, "TEST", new Limit(i))); + } + + /** + * Macht Nutzer zu Admin + */ + public GroupBuilder admin(String userid) { + return apply(new UpdateRoleEvent(groupid, "TEST", userid, Role.ADMIN)); + } + + /** + * Macht Nutzer zu regulärem + */ + public GroupBuilder regular(String userid) { + return apply(new UpdateRoleEvent(groupid, "TEST", userid, Role.REGULAR)); + } + + /** + * Macht Gruppe öffentlich + */ + public GroupBuilder publik() { + return apply(new SetTypeEvent(groupid, "TEST", Type.PUBLIC)); + } + + /** + * Macht Gruppe privat + */ + public GroupBuilder privat() { + return apply(new SetTypeEvent(groupid, "TEST", Type.PRIVATE)); + } + + /** + * Macht Gruppe zu Veranstaltung + */ + public GroupBuilder lecture() { + return apply(new SetTypeEvent(groupid, "TEST", Type.LECTURE)); + } + + /** + * Setzt Beschreibung + */ + public GroupBuilder desc(String descr) { + return apply(new SetDescriptionEvent(groupid, "TEST", new Description(descr))); + } + + /** + * Setzt Titel + */ + public GroupBuilder title(String titl) { + return apply(new SetTitleEvent(groupid, "TEST", new Title(titl))); + } + + /** + * Setzt Link + */ + public GroupBuilder link(int lnk) { + return apply(new SetInviteLinkEvent(groupid, "TEST", new Link(uuid(lnk).toString()))); + } + + /** + * Löscht Gruppe + */ + public GroupBuilder destroy() { + return apply(new DestroyGroupEvent(groupid, "TEST")); + } + + public Group build() { + return group; + } + + private GroupBuilder apply(Event event) { + version++; + event.init(version); + event.apply(group, groupCache); + return this; + } +} diff --git a/src/test/java/mops/gruppen2/TestBuilder.java b/src/test/java/mops/gruppen2/TestBuilder.java deleted file mode 100644 index d1c666a..0000000 --- a/src/test/java/mops/gruppen2/TestBuilder.java +++ /dev/null @@ -1,297 +0,0 @@ -package mops.gruppen2; - -public class TestBuilder { - - /*private static final Faker faker = new Faker(); - - public static Account account(String name) { - return new Account(name, - "", - "", - "", - "", - null); - } - - public static Group apply(Group group, Event... events) { - for (Event event : events) { - event.apply(group); - } - - return group; - } - - public static Group apply(Event... events) { - return apply(new Group(), events); - } - - *//** - * Baut eine UUID. - * - * @param id Integer id - * - * @return UUID - *//* - public static UUID uuidMock(int id) { - String idString = String.valueOf(Math.abs(id + 1)); - return UUID.fromString("00000000-0000-0000-0000-" - + "0".repeat(11 - idString.length()) - + idString); - } - - *//** - * Generiert ein EventLog mit mehreren Gruppen und Usern. - * - * @param count Gruppenanzahl - * @param membercount Mitgliederanzahl pro Gruppe - * - * @return Eventliste - *//* - public static List completePublicGroups(int count, int membercount) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> completePublicGroup(membercount)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - public static List completePrivateGroups(int count, int membercount) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> completePrivateGroup(membercount)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - public static List completePublicGroup(int membercount) { - List eventList = new ArrayList<>(); - UUID groupId = UUID.randomUUID(); - - eventList.add(createPublicGroupEvent(groupId)); - eventList.add(updateGroupTitleEvent(groupId)); - eventList.add(updateGroupDescriptionEvent(groupId)); - eventList.add(new SetLimitEvent(groupId, "fgsadggas", new Limit(Long.MAX_VALUE))); - eventList.addAll(addUserEvents(membercount, groupId)); - - return eventList; - } - - public static List completePrivateGroup(int membercount) { - List eventList = new ArrayList<>(); - UUID groupId = UUID.randomUUID(); - - eventList.add(createPrivateGroupEvent(groupId)); - eventList.add(updateGroupTitleEvent(groupId)); - eventList.add(updateGroupDescriptionEvent(groupId)); - eventList.add(new SetLimitEvent(groupId, "fgsadggas", new Limit(Long.MAX_VALUE))); - eventList.addAll(addUserEvents(membercount, groupId)); - - return eventList; - } - - public static List completePublicGroup() { - return completePublicGroup(100); - } - - public static List completePrivateGroup() { - return completePrivateGroup(100); - } - - *//** - * Generiert mehrere CreateGroupEvents, 1 <= groupId <= count. - * - * @param count Anzahl der verschiedenen Gruppen - * - * @return Eventliste - *//* - public static List createPublicGroupEvents(int count) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> createPublicGroupEvent()) - .collect(Collectors.toList()); - } - - public static List createPrivateGroupEvents(int count) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> createPublicGroupEvent()) - .collect(Collectors.toList()); - } - - public static List createMixedGroupEvents(int count) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> faker.random().nextInt(0, 1) > 0.5 - ? createPublicGroupEvent() - : createPrivateGroupEvent()) - .collect(Collectors.toList()); - } - - public static List createPrivateGroupEvents(UUID groupId) { - return (new ArrayList<>()).addAll(createGroupEvent(groupId)), - new SetTypeEvent(groupId); - } - - public static Event createPrivateGroupEvent() { - return createPrivateGroupEvent(UUID.randomUUID()); - } - - public static Event createPublicGroupEvent(UUID groupId) { - return createGroupEvent(groupId, Type.PUBLIC); - } - - public static Event createPublicGroupEvent() { - return createPublicGroupEvent(UUID.randomUUID()); - } - - public static Event createGroupEvent(UUID groupId) { - return new CreateGroupEvent(groupId, - faker.random().hex(), - LocalDateTime.now()); - } - - public static Event createLectureEvent() { - return createLectureEvent(UUID.randomUUID()); - } - - public static Event createLectureEvent(UUID groupId) { - return new CreateGroupEvent( - groupId, - faker.random().hex(), - null, - Type.LECTURE - ); - } - - *//** - * Generiert mehrere AddUserEvents für eine Gruppe, 1 <= user_id <= count. - * - * @param count Anzahl der Mitglieder - * @param groupId Gruppe, zu welcher geaddet wird - * - * @return Eventliste - *//* - public static List addUserEvents(int count, UUID groupId) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> addUserEvent(groupId, String.valueOf(i))) - .collect(Collectors.toList()); - } - - public static Event addUserEvent(UUID groupId, String userId) { - String firstname = firstname(); - String lastname = lastname(); - - return new AddMemberEvent( - groupId, - userId, - firstname, - lastname, - firstname + "." + lastname + "@mail.de" - ); - } - - public static Event addUserEvent(UUID groupId) { - return addUserEvent(groupId, faker.random().hex()); - } - - // Am besten einfach nicht benutzen - public static List deleteUserEvents(int count, List eventList) { - List removeEvents = new ArrayList<>(); - List shuffle = eventList.parallelStream() - .filter(event -> event instanceof AddMemberEvent) - .collect(Collectors.toList()); - - Collections.shuffle(shuffle); - - for (Event event : shuffle) { - removeEvents.add(new KickMemberEvent(event.getGroupid(), event.getTarget())); - - if (removeEvents.size() >= count) { - break; - } - } - - return removeEvents; - } - - *//** - * Erzeugt mehrere DeleteUserEvents, sodass eine Gruppe komplett geleert wird. - * - * @param group Gruppe welche geleert wird - * - * @return Eventliste - *//* - public static List deleteUserEvents(Group group) { - return group.getMemberships().parallelStream() - .map(user -> deleteUserEvent(group.getGroupid(), user.getUserId())) - .collect(Collectors.toList()); - } - - public static Event deleteUserEvent(UUID groupId, String userId) { - return new KickMemberEvent( - groupId, - userId - ); - } - - public static Event updateGroupDescriptionEvent(UUID groupId) { - return updateGroupDescriptionEvent(groupId, quote()); - } - - public static Event updateGroupDescriptionEvent(UUID groupId, String description) { - return new SetDescriptionEvent( - groupId, - faker.random().hex(), - description - ); - } - - public static Event updateGroupTitleEvent(UUID groupId) { - return updateGroupTitleEvent(groupId, champion()); - } - - public static Event updateGroupTitleEvent(UUID groupId, String title) { - return new SetTitleEvent( - groupId, - faker.random().hex(), - title - ); - } - - public static Event updateUserLimitMaxEvent(UUID groupId) { - return new SetLimitEvent(groupId, firstname(), Long.MAX_VALUE); - } - - public static Event updateRoleEvent(UUID groupId, String userId, Role role) { - return new UpdateRoleEvent( - groupId, - userId, - role - ); - } - - public static Event deleteGroupEvent(UUID groupId) { - return new DestroyGroupEvent(groupId, faker.random().hex()); - } - - private static String firstname() { - return clean(faker.name().firstName()); - } - - private static String lastname() { - return clean(faker.name().lastName()); - } - - private static String champion() { - return clean(faker.leagueOfLegends().champion()); - } - - private static String quote() { - return clean(faker.leagueOfLegends().quote()); - } - - private static String clean(String string) { - return string.replaceAll("['\";,]", ""); - }*/ -} diff --git a/src/test/java/mops/gruppen2/TestHelper.java b/src/test/java/mops/gruppen2/TestHelper.java new file mode 100644 index 0000000..ad9968c --- /dev/null +++ b/src/test/java/mops/gruppen2/TestHelper.java @@ -0,0 +1,24 @@ +package mops.gruppen2; + +import mops.gruppen2.domain.event.Event; + +import java.util.List; +import java.util.UUID; + +public final class TestHelper { + + public static UUID uuid(int id) { + String num = String.valueOf(id); + String string = "00000000-0000-0000-0000-"; + string += "0".repeat(12 - num.length()); + string += num; + + return UUID.fromString(string); + } + + public static void initEvents(List events) { + for (int i = 1; i <= events.size(); i++) { + events.get(i - 1).init(i); + } + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/AddMemberEventTest.java b/src/test/java/mops/gruppen2/domain/event/AddMemberEventTest.java new file mode 100644 index 0000000..e48fbe3 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/AddMemberEventTest.java @@ -0,0 +1,76 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.TestHelper; +import mops.gruppen2.domain.exception.GroupFullException; +import mops.gruppen2.domain.exception.IdMismatchException; +import mops.gruppen2.domain.exception.UserExistsException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +class AddMemberEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void userMismatch() { + assertThatThrownBy(() -> new AddMemberEvent(TestHelper.uuid(1), "TEST", "TEST", new User("PETER"))) + .isInstanceOf(IdMismatchException.class); + } + + @Test + void apply() { + Group group = GroupBuilder.get(cache, 1).group().build(); + Event add = new AddMemberEvent(TestHelper.uuid(1), "TEST", "TEST", new User("TEST")); + add.init(2); + + add.apply(group); + + assertThat(group.getMembers()).containsExactly(new User("TEST")); + } + + @Test + void apply_cache() { + Group group = GroupBuilder.get(cache, 1).group().build(); + Event add = new AddMemberEvent(TestHelper.uuid(1), "TEST", "TEST", new User("TEST")); + add.init(2); + + add.apply(group, cache); + + assertThat(group.getMembers()).containsExactly(new User("TEST")); + assertThat(cache.userGroups("TEST")).containsExactly(group); + } + + @Test + void apply_userExists() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).build(); + Event add = new AddMemberEvent(TestHelper.uuid(1), "TEST", "TEST", new User("TEST")); + add.init(5); + + assertThatThrownBy(() -> add.apply(group, cache)) + .isInstanceOf(UserExistsException.class); + } + + @Test + void apply_groupFull() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().build(); + Event add = new AddMemberEvent(TestHelper.uuid(1), "TEST", "PETER", new User("PETER")); + add.init(4); + + assertThatThrownBy(() -> add.apply(group, cache)) + .isInstanceOf(GroupFullException.class); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/CreateGroupEventTest.java b/src/test/java/mops/gruppen2/domain/event/CreateGroupEventTest.java new file mode 100644 index 0000000..ae720de --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/CreateGroupEventTest.java @@ -0,0 +1,45 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class CreateGroupEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void apply() { + Group group = Group.EMPTY(); + Event add = new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()); + add.init(1); + + assertThat(group.exists()).isFalse(); + add.apply(group); + assertThat(group.exists()).isTrue(); + } + + @Test + void apply_cache() { + Group group = Group.EMPTY(); + Event add = new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()); + add.init(1); + + add.apply(group, cache); + assertThat(group.exists()).isTrue(); + assertThat(cache.groups()).hasSize(1); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/DestroyGroupEventTest.java b/src/test/java/mops/gruppen2/domain/event/DestroyGroupEventTest.java new file mode 100644 index 0000000..fa2634b --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/DestroyGroupEventTest.java @@ -0,0 +1,110 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.exception.NoAccessException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +class DestroyGroupEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void apply() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().build(); + Event destroy = new DestroyGroupEvent(uuid(1), "TEST"); + destroy.init(4); + + assertThat(group.exists()).isTrue(); + destroy.apply(group); + assertThat(group.exists()).isFalse(); + } + + @Test + void apply_noadmin() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(3).add("PETER").build(); + Event destroy = new DestroyGroupEvent(uuid(1), "PETER"); + destroy.init(6); + + assertThatThrownBy(() -> destroy.apply(group)) + .isInstanceOf(NoAccessException.class); + } + + @Test + void apply_noadmin_empty() { + Group group = GroupBuilder.get(cache, 1).group().build(); + Event destroy = new DestroyGroupEvent(uuid(1), "PETER"); + destroy.init(2); + + destroy.apply(group); + } + + @Test + void apply_cache_private() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().privat().build(); + Event destroy = new DestroyGroupEvent(uuid(1), "TEST"); + destroy.init(5); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userGroups("TEST")).hasSize(1); + assertThat(cache.privates()).hasSize(1); + destroy.apply(group, cache); + assertThat(cache.groups()).isEmpty(); + assertThat(cache.userGroups("TEST")).isEmpty(); + assertThat(cache.privates()).isEmpty(); + } + + @Test + void apply_cache_public() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().publik().build(); + Event destroy = new DestroyGroupEvent(uuid(1), "TEST"); + destroy.init(5); + + assertThat(cache.publics()).hasSize(1); + destroy.apply(group, cache); + assertThat(cache.publics()).isEmpty(); + } + + @Test + void apply_cache_lecture() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().lecture().build(); + Event destroy = new DestroyGroupEvent(uuid(1), "TEST"); + destroy.init(5); + + assertThat(cache.lectures()).hasSize(1); + destroy.apply(group, cache); + assertThat(cache.lectures()).isEmpty(); + } + + @Test + void apply_cache_multipleUsers() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().privat().limit(5).add("A").add("B").add("C").add("D").build(); + Event destroy = new DestroyGroupEvent(uuid(1), "TEST"); + destroy.init(10); + + assertThat(cache.userGroups("TEST")).hasSize(1); + assertThat(cache.userGroups("A")).hasSize(1); + assertThat(cache.userGroups("B")).hasSize(1); + assertThat(cache.userGroups("C")).hasSize(1); + assertThat(cache.userGroups("D")).hasSize(1); + destroy.apply(group, cache); + assertThat(cache.userGroups("TEST")).hasSize(0); + assertThat(cache.userGroups("A")).hasSize(0); + assertThat(cache.userGroups("B")).hasSize(0); + assertThat(cache.userGroups("C")).hasSize(0); + assertThat(cache.userGroups("D")).hasSize(0); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/EventTest.java b/src/test/java/mops/gruppen2/domain/event/EventTest.java new file mode 100644 index 0000000..6998d42 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/EventTest.java @@ -0,0 +1,76 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.exception.BadArgumentException; +import mops.gruppen2.domain.exception.IdMismatchException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class EventTest { + + @Test + void apply_smallVersion() { + Group group = GroupBuilder.get(Mockito.mock(GroupCache.class), 1).group().build(); + Event add = new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")); + add.init(1); + + assertThatThrownBy(() -> add.apply(group)) + .isInstanceOf(IdMismatchException.class); + } + + @Test + void apply_bigVersion() { + Group group = GroupBuilder.get(Mockito.mock(GroupCache.class), 1).group().build(); + Event add = new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")); + add.init(3); + + assertThatThrownBy(() -> add.apply(group)) + .isInstanceOf(IdMismatchException.class); + } + + @Test + void apply_notInitialized() { + Group group = GroupBuilder.get(Mockito.mock(GroupCache.class), 1).group().build(); + Event add = new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")); + + assertThatThrownBy(() -> add.apply(group)) + .isInstanceOf(BadArgumentException.class); + } + + @Test + void apply_wrongGroup() { + Group group = GroupBuilder.get(Mockito.mock(GroupCache.class), 1).group().build(); + Event add = new AddMemberEvent(uuid(2), "TEST", "TEST", new User("TEST")); + add.init(2); + + assertThatThrownBy(() -> add.apply(group)) + .isInstanceOf(IdMismatchException.class); + } + + @Test + void apply_updateVersion() { + Group group = GroupBuilder.get(Mockito.mock(GroupCache.class), 1).group().build(); + Event add = new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")); + add.init(2); + + assertThat(group.getVersion()).isEqualTo(1); + add.apply(group); + assertThat(group.getVersion()).isEqualTo(2); + } + + @Test + void init_alreadyInitialized() { + Event add = new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")); + add.init(2); + + assertThatThrownBy(() -> add.init(3)) + .isInstanceOf(BadArgumentException.class); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/KickMemberEventTest.java b/src/test/java/mops/gruppen2/domain/event/KickMemberEventTest.java new file mode 100644 index 0000000..51765f7 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/KickMemberEventTest.java @@ -0,0 +1,44 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class KickMemberEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void apply() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().build(); + Event kick = new KickMemberEvent(uuid(1), "TEST", "TEST"); + kick.init(4); + + assertThat(group.size()).isOne(); + kick.apply(group); + assertThat(group.size()).isZero(); + } + + @Test + void apply_cache() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().build(); + Event kick = new KickMemberEvent(uuid(1), "TEST", "TEST"); + kick.init(4); + + assertThat(cache.userGroups("TEST")).hasSize(1); + kick.apply(group, cache); + assertThat(cache.userGroups("TEST")).hasSize(0); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/SetInviteLinkEventTest.java b/src/test/java/mops/gruppen2/domain/event/SetInviteLinkEventTest.java new file mode 100644 index 0000000..d009529 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/SetInviteLinkEventTest.java @@ -0,0 +1,44 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Link; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class SetInviteLinkEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void apply() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().build(); + Event link = new SetInviteLinkEvent(uuid(1), "TEST", new Link(uuid(2).toString())); + link.init(4); + + link.apply(group); + assertThat(group.getLink()).isEqualTo(uuid(2).toString()); + } + + @Test + void apply_cache() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().build(); + Event link = new SetInviteLinkEvent(uuid(1), "TEST", new Link(uuid(2).toString())); + link.init(4); + + assertThat(cache.group(group.getLink())).isEqualTo(group); + link.apply(group, cache); + assertThat(cache.group(uuid(2).toString())).isEqualTo(group); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/SetLimitEventTest.java b/src/test/java/mops/gruppen2/domain/event/SetLimitEventTest.java new file mode 100644 index 0000000..c23ace3 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/SetLimitEventTest.java @@ -0,0 +1,34 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.exception.BadArgumentException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.wrapper.Limit; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +class SetLimitEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void apply_tooSmall() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").build(); + Event limit = new SetLimitEvent(uuid(1), "TEST", new Limit(1)); + limit.init(6); + + assertThatThrownBy(() -> limit.apply(group)) + .isInstanceOf(BadArgumentException.class); + } +} diff --git a/src/test/java/mops/gruppen2/domain/event/SetTypeEventTest.java b/src/test/java/mops/gruppen2/domain/event/SetTypeEventTest.java new file mode 100644 index 0000000..892325f --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/event/SetTypeEventTest.java @@ -0,0 +1,36 @@ +package mops.gruppen2.domain.event; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class SetTypeEventTest { + + GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(mock(EventStoreService.class)); + } + + @Test + void apply_cache() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().privat().build(); + Event type = new SetTypeEvent(uuid(1), "TEST", Type.LECTURE); + type.init(5); + + assertThat(cache.privates()).hasSize(1); + assertThat(cache.lectures()).isEmpty(); + type.apply(group, cache); + assertThat(cache.lectures()).hasSize(1); + assertThat(cache.privates()).isEmpty(); + } +} diff --git a/src/test/java/mops/gruppen2/domain/service/GroupServiceTest.java b/src/test/java/mops/gruppen2/domain/service/GroupServiceTest.java new file mode 100644 index 0000000..2effa8e --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/service/GroupServiceTest.java @@ -0,0 +1,267 @@ +package mops.gruppen2.domain.service; + +import mops.gruppen2.GroupBuilder; +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.UserExistsException; +import mops.gruppen2.domain.exception.UserNotFoundException; +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.Link; +import mops.gruppen2.domain.model.group.wrapper.Parent; +import mops.gruppen2.domain.model.group.wrapper.Title; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +class GroupServiceTest { + + private GroupService groupService; + + @BeforeEach + void setUp() { + groupService = new GroupService(mock(GroupCache.class), mock(EventStoreService.class)); + } + + @Test + void createGroup() { + Group group = groupService.createGroup("TEST"); + + assertThat(group.getId()).isNotNull(); + assertThat(group.creator()).isEqualTo("TEST"); + } + + @Test + void initGroupMembers() { + Group group = groupService.createGroup("TEST"); + groupService.initGroupMembers(group, "TEST", "TEST", new User("TEST"), new Limit(1)); + + assertThat(group.getMembers()).containsExactly(new User("TEST")); + assertThat(group.getLimit()).isEqualTo(1); + assertThat(group.isAdmin("TEST")).isTrue(); + } + + @Test + void initGroupMeta() { + Group group = groupService.createGroup("TEST"); + groupService.initGroupMembers(group, "TEST", "TEST", new User("TEST"), new Limit(1)); + groupService.initGroupMeta(group, "TEST", Type.PUBLIC, Parent.EMPTY()); + + assertThat(group.isPublic()).isTrue(); + assertThat(group.hasParent()).isFalse(); + } + + @Test + void initGroupText() { + Group group = groupService.createGroup("TEST"); + groupService.initGroupMembers(group, "TEST", "TEST", new User("TEST"), new Limit(1)); + groupService.initGroupMeta(group, "TEST", Type.PUBLIC, Parent.EMPTY()); + groupService.initGroupText(group, "TEST", new Title("TITLE"), new Description("DESCR")); + + assertThat(group.getTitle()).isEqualTo("TITLE"); + assertThat(group.getDescription()).isEqualTo("DESCR"); + } + + @Test + void addUsersToGroup() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.addUsersToGroup(group, "TEST", Arrays.asList( + new User("A"), + new User("B"), + new User("C"), + new User("C"))); + + assertThat(group.getLimit()).isEqualTo(4); + assertThat(group.size()).isEqualTo(4); + assertThat(group.getRegulars()).hasSize(3); + assertThat(group.getAdmins()).hasSize(1); + } + + @Test + void toggleMemberRole_lastAdmin_lastUser() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.toggleMemberRole(group, "TEST", "TEST"); + } + + @Test + void toggleMemberRole_lastAdmin() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(2).add("PETER").build(); + + assertThatThrownBy(() -> groupService.toggleMemberRole(group, "TEST", "TEST")) + .isInstanceOf(LastAdminException.class); + } + + @Test + void toggleMemberRole_noMember() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + assertThatThrownBy(() -> groupService.toggleMemberRole(group, "TEST", "PETER")) + .isInstanceOf(UserNotFoundException.class); + } + + @Test + void addMember_newMember() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(2).build(); + + groupService.addMember(group, "Test", "PETER", new User("PETER")); + + assertThat(group.size()).isEqualTo(2); + assertThat(group.getAdmins()).hasSize(1); + assertThat(group.getRegulars()).hasSize(1); + } + + @Test + void addMember_newMember_groupFull() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + assertThatThrownBy(() -> groupService.addMember(group, "Test", "PETER", new User("PETER"))) + .isInstanceOf(GroupFullException.class); + } + + @Test + void addMember_newMember_userExists() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(3).add("PETER").build(); + + assertThatThrownBy(() -> groupService.addMember(group, "Test", "PETER", new User("PETER"))) + .isInstanceOf(UserExistsException.class); + } + + @Test + void kickMember_noMember() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + assertThatThrownBy(() -> groupService.kickMember(group, "TEST", "PETER")) + .isInstanceOf(UserNotFoundException.class); + } + + @Test + void kickMember_lastMember() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.kickMember(group, "TEST", "TEST"); + + assertThat(group.exists()).isFalse(); + } + + @Test + void kickMember_lastAdmin() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(2).add("PETER").build(); + + assertThatThrownBy(() -> groupService.kickMember(group, "TEST", "TEST")) + .isInstanceOf(LastAdminException.class); + } + + @Test + void deleteGroup_noGroup() { + groupService.deleteGroup(Group.EMPTY(), "TEST"); + } + + @Test + void deleteGroup() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(2).add("PETER").build(); + + groupService.deleteGroup(group, "TEST"); + + assertThat(group.exists()).isFalse(); + } + + @Test + void deleteGroup_noAdmin() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(2).add("PETER").build(); + + assertThatThrownBy(() -> groupService.deleteGroup(group, "PETER")) + .isInstanceOf(NoAccessException.class); + assertThat(group.exists()).isTrue(); + } + + @Test + void setTitle() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.setTitle(group, "TEST", new Title("TITLE")); + + assertThat(group.getTitle()).isEqualTo("TITLE"); + } + + @Test + void setDescription() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.setDescription(group, "TEST", new Description("DESCR")); + + assertThat(group.getDescription()).isEqualTo("DESCR"); + } + + @Test + void setLimit_tooLow() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().limit(2).add("PETER").build(); + + assertThatThrownBy(() -> groupService.setLimit(group, "TEST", new Limit(1))) + .isInstanceOf(BadArgumentException.class); + + assertThat(group.getLimit()).isEqualTo(2); + } + + @Test + void setLimit() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.setLimit(group, "TEST", new Limit(8)); + + assertThat(group.getLimit()).isEqualTo(8); + } + + @Test + void setParent_sameGroup() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + assertThatThrownBy(() -> groupService.setParent(group, "TEST", new Parent(uuid(1).toString()))) + .isInstanceOf(BadArgumentException.class); + + assertThat(group.getParent()).isEqualTo(uuid(0)); + assertThat(group.hasParent()).isFalse(); + } + + @Test + void setParent() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.setParent(group, "TEST", new Parent(uuid(2).toString())); + + assertThat(group.getParent()).isEqualTo(uuid(2)); + assertThat(group.hasParent()).isTrue(); + } + + @Test + void setLink_sameAsGroupId() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + assertThatThrownBy(() -> groupService.setLink(group, "TEST", new Link(uuid(1).toString()))) + .isInstanceOf(BadArgumentException.class); + + assertThat(group.getLink()).isNotEqualTo(uuid(1).toString()); + } + + @Test + void setLink() { + Group group = GroupBuilder.get(mock(GroupCache.class), 1).group().testadmin().build(); + + groupService.setLink(group, "TEST", new Link(uuid(2).toString())); + + assertThat(group.getLink()).isEqualTo(uuid(2).toString()); + } +} diff --git a/src/test/java/mops/gruppen2/domain/service/SearchServiceTest.java b/src/test/java/mops/gruppen2/domain/service/SearchServiceTest.java new file mode 100644 index 0000000..848bfe2 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/service/SearchServiceTest.java @@ -0,0 +1,116 @@ +package mops.gruppen2.domain.service; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class SearchServiceTest { + + private GroupCache groupCache; + private SearchService searchService; + + @BeforeEach + void setUp() { + groupCache = new GroupCache(mock(EventStoreService.class)); + searchService = new SearchService(groupCache); + } + + @Test + void searchString_noResult() { + assertThat(searchService.searchString("", "TEST")).isEmpty(); + } + + @Test + void searchString_noResult_onePrivate_emptyString() { + GroupBuilder.get(groupCache, 1).group(); + + assertThat(searchService.searchString("", "TEST")).isEmpty(); + } + + @Test + void searchString_noResult_onePublic_emptyString_principalMember() { + GroupBuilder.get(groupCache, 1).group().testadmin().publik(); + + assertThat(searchService.searchString("", "TEST")).isEmpty(); + } + + @Test + void searchString_oneResult_onePublic_emptyString_principalNoMember() { + GroupBuilder.get(groupCache, 1).group().testadmin().publik(); + + assertThat(searchService.searchString("", "PETER")).hasSize(1); + } + + @Test + void searchString_oneResult_multiple_emptyString() { + Group groupA = GroupBuilder.get(groupCache, 1).group().testadmin().lecture().build(); + GroupBuilder.get(groupCache, 2).group().testadmin().publik().limit(2).add("PETER"); + GroupBuilder.get(groupCache, 3).group().testadmin().publik().limit(2).add("PETER"); + GroupBuilder.get(groupCache, 4).group().testadmin().privat(); + + assertThat(searchService.searchString("", "PETER")).containsExactly(groupA); + assertThat(searchService.searchString("", "TEST")).isEmpty(); + } + + @Test + void searchString_noPrivates() { + Group groupA = GroupBuilder.get(groupCache, 1).group().testadmin().lecture().build(); + Group groupB = GroupBuilder.get(groupCache, 2).group().testadmin().publik().limit(2).add("PETER").build(); + Group groupC = GroupBuilder.get(groupCache, 3).group().testadmin().publik().limit(2).add("PETER").build(); + GroupBuilder.get(groupCache, 4).group().testadmin().privat(); + GroupBuilder.get(groupCache, 5).group().testadmin().privat(); + GroupBuilder.get(groupCache, 6).group().testadmin().privat(); + + assertThat(searchService.searchString("", "PETER")).containsExactly(groupA); + assertThat(searchService.searchString("", "PRRR")).containsOnly(groupA, groupB, groupC); + assertThat(searchService.searchString("", "TEST")).isEmpty(); + } + + @Test + void searchString_matchString_title() { + Group groupA = GroupBuilder.get(groupCache, 1).group().testadmin().lecture().title("A").build(); + Group groupB = GroupBuilder.get(groupCache, 2).group().testadmin().lecture().title("B").build(); + Group groupC = GroupBuilder.get(groupCache, 3).group().testadmin().lecture().title("C").build(); + Group groupD = GroupBuilder.get(groupCache, 4).group().testadmin().lecture().title("CAESAR").build(); + + assertThat(searchService.searchString("C", "PETER")).containsExactly(groupC, groupD); + assertThat(searchService.searchString("C", "TEST")).isEmpty(); + } + + @Test + void searchString_matchString_desc() { + Group groupA = GroupBuilder.get(groupCache, 1).group().testadmin().lecture().desc("A").build(); + Group groupB = GroupBuilder.get(groupCache, 2).group().testadmin().lecture().desc("B").build(); + Group groupC = GroupBuilder.get(groupCache, 3).group().testadmin().lecture().desc("C").build(); + Group groupD = GroupBuilder.get(groupCache, 4).group().testadmin().lecture().desc("CAESAR").build(); + + assertThat(searchService.searchString("C", "PETER")).containsExactly(groupC, groupD); + assertThat(searchService.searchString("C", "TEST")).isEmpty(); + } + + @Test + void searchType_noGroup() { + assertThat(searchService.searchType(Type.LECTURE, "PETER")).isEmpty(); + assertThat(searchService.searchType(Type.PUBLIC, "PETER")).isEmpty(); + assertThat(searchService.searchType(Type.PRIVATE, "PETER")).isEmpty(); + } + + @Test + void searchType_noPrivates() { + GroupBuilder.get(groupCache, 1).group().testadmin().lecture(); + GroupBuilder.get(groupCache, 2).group().testadmin().publik(); + GroupBuilder.get(groupCache, 3).group().testadmin().privat(); + GroupBuilder.get(groupCache, 4).group().testadmin().privat(); + GroupBuilder.get(groupCache, 5).group().testadmin().lecture(); + + assertThat(searchService.searchType(Type.LECTURE, "PETER")).hasSize(2); + assertThat(searchService.searchType(Type.PUBLIC, "PETER")).hasSize(1); + assertThat(searchService.searchType(Type.PRIVATE, "PETER")).isEmpty(); + } +} diff --git a/src/test/java/mops/gruppen2/domain/service/helper/ProjectionHelperTest.java b/src/test/java/mops/gruppen2/domain/service/helper/ProjectionHelperTest.java new file mode 100644 index 0000000..ced85a4 --- /dev/null +++ b/src/test/java/mops/gruppen2/domain/service/helper/ProjectionHelperTest.java @@ -0,0 +1,243 @@ +package mops.gruppen2.domain.service.helper; + +import mops.gruppen2.domain.event.AddMemberEvent; +import mops.gruppen2.domain.event.CreateGroupEvent; +import mops.gruppen2.domain.event.Event; +import mops.gruppen2.domain.event.SetLimitEvent; +import mops.gruppen2.domain.event.UpdateRoleEvent; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Role; +import mops.gruppen2.domain.model.group.User; +import mops.gruppen2.domain.model.group.wrapper.Limit; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static mops.gruppen2.TestHelper.initEvents; +import static mops.gruppen2.TestHelper.uuid; +import static mops.gruppen2.domain.service.helper.ProjectionHelper.project; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class ProjectionHelperTest { + + @BeforeEach + void setUp() { + } + + @Test + void project_nocache_emptyList() { + assertThat(project(Collections.emptyList())).isEmpty(); + } + + @Test + void project_nocache_oneCreate() { + List events = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now())); + + initEvents(events); + + assertThat(project(events)).hasSize(1); + } + + @Test + void project_nocache_multipleCreate() { + List events = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(2), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(3), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(4), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(5), "TEST", LocalDateTime.now())); + + events.get(0).init(1); + events.get(1).init(1); + events.get(2).init(1); + events.get(3).init(1); + events.get(4).init(1); + + assertThat(project(events)).hasSize(5); + } + + @Test + void project_nocache_oneDetailed() { + List events = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(1), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(1), "TEST", new Limit(5))); + + initEvents(events); + + List groups = project(events); + + assertThat(groups).hasSize(1); + assertThat(groups.get(0).exists()).isTrue(); + assertThat(groups.get(0).getAdmins()).hasSize(1); + assertThat(groups.get(0).getLimit()).isEqualTo(5); + } + + @Test + void project_nocache_multipleDetailed() { + List eventsA = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(1), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(1), "TEST", new Limit(5))); + + List eventsB = Arrays.asList( + new CreateGroupEvent(uuid(2), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(2), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(2), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(2), "TEST", new Limit(15))); + + List eventsC = Arrays.asList( + new CreateGroupEvent(uuid(3), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(3), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(3), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(3), "TEST", new Limit(25))); + + List eventsD = Arrays.asList( + new CreateGroupEvent(uuid(4), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(4), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(4), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(4), "TEST", new Limit(35))); + + initEvents(eventsA); + initEvents(eventsB); + initEvents(eventsC); + initEvents(eventsD); + + List events = new ArrayList<>(); + events.addAll(eventsA); + events.addAll(eventsB); + events.addAll(eventsC); + events.addAll(eventsD); + + List groups = project(events); + + assertThat(groups).hasSize(4); + assertThat(groups.get(0).exists()).isTrue(); + assertThat(groups.get(0).getAdmins()).hasSize(1); + assertThat(groups.get(0).getLimit()).isEqualTo(5); + + assertThat(groups.get(1).exists()).isTrue(); + assertThat(groups.get(1).getAdmins()).hasSize(1); + assertThat(groups.get(1).getLimit()).isEqualTo(15); + + assertThat(groups.get(2).exists()).isTrue(); + assertThat(groups.get(2).getAdmins()).hasSize(1); + assertThat(groups.get(2).getLimit()).isEqualTo(25); + + assertThat(groups.get(3).exists()).isTrue(); + assertThat(groups.get(3).getAdmins()).hasSize(1); + assertThat(groups.get(3).getLimit()).isEqualTo(35); + } + + @Test + void project_cache_noGroups() { + Map groups = new HashMap<>(); + + project(groups, Collections.emptyList(), mock(GroupCache.class)); + + assertThat(groups).isEmpty(); + } + + @Test + void project_cache_oneCreate() { + Map groups = new HashMap<>(); + List events = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now())); + + initEvents(events); + project(groups, events, mock(GroupCache.class)); + + assertThat(groups).hasSize(1); + assertThat(groups.keySet()).containsExactly(uuid(1)); + } + + @Test + void project_cache_multipleCreate() { + Map groups = new HashMap<>(); + List events = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(2), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(3), "TEST", LocalDateTime.now()), + new CreateGroupEvent(uuid(4), "TEST", LocalDateTime.now())); + + events.get(0).init(1); + events.get(1).init(1); + events.get(2).init(1); + events.get(3).init(1); + project(groups, events, mock(GroupCache.class)); + + assertThat(groups).hasSize(4); + assertThat(groups.keySet()).containsExactly(uuid(1), uuid(2), uuid(3), uuid(4)); + } + + @Test + void project_cache_multipleDetailed() { + Map groups = new HashMap<>(); + List eventsA = Arrays.asList( + new CreateGroupEvent(uuid(1), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(1), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(1), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(1), "TEST", new Limit(5))); + + List eventsB = Arrays.asList( + new CreateGroupEvent(uuid(2), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(2), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(2), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(2), "TEST", new Limit(15))); + + List eventsC = Arrays.asList( + new CreateGroupEvent(uuid(3), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(3), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(3), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(3), "TEST", new Limit(25))); + + List eventsD = Arrays.asList( + new CreateGroupEvent(uuid(4), "TEST", LocalDateTime.now()), + new AddMemberEvent(uuid(4), "TEST", "TEST", new User("TEST")), + new UpdateRoleEvent(uuid(4), "TEST", "TEST", Role.ADMIN), + new SetLimitEvent(uuid(4), "TEST", new Limit(35))); + + initEvents(eventsA); + initEvents(eventsB); + initEvents(eventsC); + initEvents(eventsD); + + List events = new ArrayList<>(); + events.addAll(eventsA); + events.addAll(eventsB); + events.addAll(eventsC); + events.addAll(eventsD); + + project(groups, events, mock(GroupCache.class)); + + assertThat(groups).hasSize(4); + assertThat(groups.get(uuid(1)).exists()).isTrue(); + assertThat(groups.get(uuid(1)).getAdmins()).hasSize(1); + assertThat(groups.get(uuid(1)).getLimit()).isEqualTo(5); + + assertThat(groups.get(uuid(2)).exists()).isTrue(); + assertThat(groups.get(uuid(2)).getAdmins()).hasSize(1); + assertThat(groups.get(uuid(2)).getLimit()).isEqualTo(15); + + assertThat(groups.get(uuid(3)).exists()).isTrue(); + assertThat(groups.get(uuid(3)).getAdmins()).hasSize(1); + assertThat(groups.get(uuid(3)).getLimit()).isEqualTo(25); + + assertThat(groups.get(uuid(4)).exists()).isTrue(); + assertThat(groups.get(uuid(4)).getAdmins()).hasSize(1); + assertThat(groups.get(uuid(4)).getLimit()).isEqualTo(35); + } +} diff --git a/src/test/java/mops/gruppen2/infrastructure/GroupCacheTest.java b/src/test/java/mops/gruppen2/infrastructure/GroupCacheTest.java new file mode 100644 index 0000000..8031301 --- /dev/null +++ b/src/test/java/mops/gruppen2/infrastructure/GroupCacheTest.java @@ -0,0 +1,312 @@ +package mops.gruppen2.infrastructure; + +import mops.gruppen2.GroupBuilder; +import mops.gruppen2.domain.exception.GroupNotFoundException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.service.EventStoreService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static mops.gruppen2.TestHelper.uuid; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +// Kann nur indirket über events getestet werden, diese werden also "mitgetestet" +class GroupCacheTest { + + private GroupCache cache; + + @BeforeEach + void setUp() { + cache = new GroupCache(Mockito.mock(EventStoreService.class)); + } + + @Test + void groups_noGroups() { + assertThat(cache.groups()).isEmpty(); + } + + @Test + void group_groupNotFound() { + assertThatThrownBy(() -> cache.group(uuid(1))) + .isInstanceOf(GroupNotFoundException.class); + } + + @Test + void group_linkNotFound() { + assertThatThrownBy(() -> cache.group("00000000-0000-0000-0000-000000000000")) + .isInstanceOf(GroupNotFoundException.class); + } + + @Test + void group_groupFound() { + Group group = GroupBuilder.get(cache, 1).group().build(); + + assertThat(cache.group(uuid(1))).isEqualTo(group); + } + + @Test + void group_linkFound() { + Group group = GroupBuilder.get(cache, 1).group().build(); + + assertThat(cache.group(group.getLink())).isEqualTo(group); + } + + @Test + void userGroups_noGroups() { + assertThat(cache.userGroups("TEST")).isEmpty(); + } + + @Test + void userGroups_noUserGroups() { + Group group = GroupBuilder.get(cache, 1).group().add("PETER").build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userGroups("TEST")).isEmpty(); + } + + @Test + void userGroups_oneUserGroup() { + Group group = GroupBuilder.get(cache, 1).group().add("TEST").build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userGroups("TEST")).containsExactly(group); + } + + @Test + void userGroups_userGroup_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().add("TEST").build(); + Group groupB = GroupBuilder.get(cache, 2).group().add("PETER").build(); + Group groupC = GroupBuilder.get(cache, 3).group().add("TEST").build(); + Group groupD = GroupBuilder.get(cache, 4).group().add("PETER").build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.userGroups("PETER")).containsExactly(groupB, groupD); + } + + @Test + void userLectures_noGroups() { + assertThat(cache.userLectures("PETER")).isEmpty(); + } + + @Test + void userLectures_noLecture() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userLectures("PETER")).isEmpty(); + } + + @Test + void userLectures_oneLecture() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").lecture().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userLectures("PETER")).containsExactly(group); + } + + @Test + void userLectures_lecture_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").lecture().build(); + Group groupB = GroupBuilder.get(cache, 2).group().testadmin().limit(2).add("PETER").publik().build(); + Group groupC = GroupBuilder.get(cache, 3).group().testadmin().limit(2).add("PETER").privat().build(); + Group groupD = GroupBuilder.get(cache, 4).group().testadmin().lecture().build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.userLectures("PETER")).containsExactly(groupA); + } + + @Test + void userPublics_noGroups() { + assertThat(cache.userPublics("PETER")).isEmpty(); + } + + @Test + void userPublics_noPublic() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").publik().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userLectures("PETER")).isEmpty(); + } + + @Test + void userPublics_onePublic() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").lecture().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userLectures("PETER")).containsExactly(group); + } + + @Test + void userPublics_public_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").lecture().build(); + Group groupB = GroupBuilder.get(cache, 2).group().testadmin().limit(2).add("PETER").publik().build(); + Group groupC = GroupBuilder.get(cache, 3).group().testadmin().limit(2).add("PETER").privat().build(); + Group groupD = GroupBuilder.get(cache, 4).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.userPublics("PETER")).containsExactly(groupB); + } + + @Test + void userPrivates_noGroups() { + assertThat(cache.userPrivates("PETER")).isEmpty(); + } + + @Test + void userPrivates_noPrivate() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").publik().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userPrivates("PETER")).isEmpty(); + } + + @Test + void userPrivates_onePrivate() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").privat().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userPrivates("PETER")).containsExactly(group); + } + + @Test + void userPrivates_private_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().testadmin().limit(2).add("PETER").lecture().build(); + Group groupB = GroupBuilder.get(cache, 2).group().testadmin().limit(2).add("PETER").privat().build(); + Group groupC = GroupBuilder.get(cache, 3).group().testadmin().privat().build(); + Group groupD = GroupBuilder.get(cache, 4).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.userPrivates("PETER")).containsExactly(groupB); + } + + @Test + void publics_noGroups() { + assertThat(cache.publics()).isEmpty(); + } + + @Test + void publics_noPublic() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().privat().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.publics()).isEmpty(); + } + + @Test + void publics_onePublic() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.publics()).containsExactly(group); + } + + @Test + void publics_public_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().testadmin().lecture().build(); + Group groupB = GroupBuilder.get(cache, 2).group().testadmin().privat().build(); + Group groupC = GroupBuilder.get(cache, 3).group().testadmin().privat().build(); + Group groupD = GroupBuilder.get(cache, 4).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.publics()).containsExactly(groupD); + } + + @Test + void privates_noGroups() { + assertThat(cache.privates()).isEmpty(); + } + + @Test + void privates_noPrivate() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.privates()).isEmpty(); + } + + @Test + void privates_onePrivate() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().privat().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.privates()).containsExactly(group); + } + + @Test + void privates_private_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().testadmin().lecture().build(); + Group groupB = GroupBuilder.get(cache, 2).group().testadmin().privat().build(); + Group groupC = GroupBuilder.get(cache, 3).group().testadmin().privat().build(); + Group groupD = GroupBuilder.get(cache, 4).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.privates()).containsExactly(groupB, groupC); + } + + @Test + void lectures_noGroups() { + assertThat(cache.lectures()).isEmpty(); + } + + @Test + void lectures_noLecture() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().privat().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.lectures()).isEmpty(); + } + + @Test + void lectures_oneLecture() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().lecture().build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.lectures()).containsExactly(group); + } + + @Test + void lectures_lecture_multiple() { + Group groupA = GroupBuilder.get(cache, 1).group().testadmin().lecture().build(); + Group groupB = GroupBuilder.get(cache, 2).group().testadmin().privat().build(); + Group groupC = GroupBuilder.get(cache, 3).group().testadmin().lecture().build(); + Group groupD = GroupBuilder.get(cache, 4).group().testadmin().publik().build(); + + assertThat(cache.groups()).hasSize(4); + assertThat(cache.lectures()).containsExactly(groupA, groupC); + } + + //Indirekt: void usersPut() {} + + @Test + void usersRemove() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().lecture().limit(2).add("PETER").kick("PETER").build(); + + assertThat(cache.groups()).hasSize(1); + assertThat(cache.userLectures("PETER")).isEmpty(); + } + + //Indirekt: void groupsPut() {} + + @Test + void groupsRemove() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().lecture().destroy().build(); + + assertThat(cache.groups()).hasSize(0); + } + + + //Indirekt: void linksPut() {} + + @Test + void linksRemove() { + Group group = GroupBuilder.get(cache, 1).group().testadmin().lecture().link(2).build(); + + assertThat(cache.group(String.valueOf(uuid(2)))).isEqualTo(group); + } + + //Indirekt: void typesPut() {} + + //Indirekt: void typesRemove() {} +} diff --git a/src/test/java/mops/gruppen2/infrastructure/controller/APIControllerTest.java b/src/test/java/mops/gruppen2/infrastructure/controller/APIControllerTest.java new file mode 100644 index 0000000..e4cbf27 --- /dev/null +++ b/src/test/java/mops/gruppen2/infrastructure/controller/APIControllerTest.java @@ -0,0 +1,53 @@ +package mops.gruppen2.infrastructure.controller; + +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.infrastructure.GroupCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.security.test.context.support.WithMockUser; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class APIControllerTest { + + private EventStoreService store; + private GroupCache cache; + private APIController controller; + + @BeforeEach + void setUp() { + store = mock(EventStoreService.class); + cache = new GroupCache(store); + controller = new APIController(cache, store); + } + + @WithMockUser("ROLE_api_user") + @Test + void getApiUpdate_noEvents() { + when(store.findMaxEventId()).thenReturn(0L); + + assertThat(controller.getApiUpdate(0).getVersion()).isZero(); + assertThat(controller.getApiUpdate(0).getGroups()).isEmpty(); + } + + @Disabled + @WithMockUser("ROLE_api_user") + @Test + void getApiUpdate_noUpdate() { + } + + @Disabled + @WithMockUser("ROLE_api_user") + @Test + void getApiUserGroups() { + } + + @Disabled + @WithMockUser("ROLE_api_user") + @Test + void getApiGroup() { + } +} diff --git a/src/test/java/mops/gruppen2/infrastructure/controller/GroupCreationControllerTest.java b/src/test/java/mops/gruppen2/infrastructure/controller/GroupCreationControllerTest.java new file mode 100644 index 0000000..4d01119 --- /dev/null +++ b/src/test/java/mops/gruppen2/infrastructure/controller/GroupCreationControllerTest.java @@ -0,0 +1,19 @@ +package mops.gruppen2.infrastructure.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GroupCreationControllerTest { + + @BeforeEach + void setUp() { + } + + @Test + void getCreate() { + } + + @Test + void postCreateOrga() { + } +} diff --git a/src/test/java/mops/gruppen2/infrastructure/controller/GroupDetailsControllerTest.java b/src/test/java/mops/gruppen2/infrastructure/controller/GroupDetailsControllerTest.java new file mode 100644 index 0000000..42c7a9c --- /dev/null +++ b/src/test/java/mops/gruppen2/infrastructure/controller/GroupDetailsControllerTest.java @@ -0,0 +1,67 @@ +package mops.gruppen2.infrastructure.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GroupDetailsControllerTest { + + @BeforeEach + void setUp() { + } + + @Test + void getDetailsPage() { + } + + @Test + void postDetailsJoin() { + } + + @Test + void postDetailsLeave() { + } + + @Test + void getDetailsHistory() { + } + + @Test + void getDetailsExportHistoryPlain() { + } + + @Test + void getDetailsExportHistorySql() { + } + + @Test + void getDetailsExportMembers() { + } + + @Test + void getDetailsEdit() { + } + + @Test + void postDetailsEditMeta() { + } + + @Test + void postDetailsEditUserLimit() { + } + + @Test + void postDetailsEditCsv() { + } + + @Test + void postDetailsEditRole() { + } + + @Test + void postDetailsEditDelete() { + } + + @Test + void postDetailsEditDestroy() { + } +} diff --git a/src/test/java/mops/gruppen2/infrastructure/controller/SearchAndInviteControllerTest.java b/src/test/java/mops/gruppen2/infrastructure/controller/SearchAndInviteControllerTest.java new file mode 100644 index 0000000..edc1ad0 --- /dev/null +++ b/src/test/java/mops/gruppen2/infrastructure/controller/SearchAndInviteControllerTest.java @@ -0,0 +1,31 @@ +package mops.gruppen2.infrastructure.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SearchAndInviteControllerTest { + + @BeforeEach + void setUp() { + } + + @Test + void getSearch() { + } + + @Test + void postSearchString() { + } + + @Test + void getSearchAll() { + } + + @Test + void getSearchType() { + } + + @Test + void getJoin() { + } +}