1

refactor, templates need fixing

This commit is contained in:
Christoph
2020-04-14 02:19:27 +02:00
parent f5d668fba2
commit dbb60f30a7
99 changed files with 1612 additions and 1114 deletions

View File

@ -82,6 +82,7 @@ dependencies {
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
implementation 'com.github.javafaker:javafaker:1.0.2'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

View File

@ -1,15 +1,15 @@
CREATE TABLE event
(
event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
user_id VARCHAR(50),
event_type VARCHAR(36),
event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
user_id VARCHAR(50) NOT NULL,
event_type VARCHAR(32) NOT NULL,
event_payload JSON
);
CREATE TABLE invite
(
invite_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
invite_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
invite_link VARCHAR(36) NOT NULL
);

View File

@ -13,7 +13,7 @@ import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Log4j2
@Profile("dev")
@Profile({"dev", "docker"})
@Aspect
@Component
public class LogAspect {
@ -36,12 +36,12 @@ public class LogAspect {
@Before("@annotation(mops.gruppen2.aspect.annotation.Trace)")
public void logCustom(JoinPoint joinPoint) {
public static void logCustom(JoinPoint joinPoint) {
log.trace(((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Trace.class).value());
}
@Before("@annotation(mops.gruppen2.aspect.annotation.TraceMethodCall) || logMethodCalls()")
public void logMethodCall(JoinPoint joinPoint) {
public static void logMethodCall(JoinPoint joinPoint) {
log.trace("Methodenaufruf: {} ({})",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName().replace("mops.gruppen2.", ""));
@ -50,7 +50,7 @@ public class LogAspect {
}
@Around("@annotation(mops.gruppen2.aspect.annotation.TraceExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
public static Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
joinPoint.proceed();
long stop = System.currentTimeMillis();

View File

@ -0,0 +1,15 @@
package mops.gruppen2.config;
import mops.gruppen2.config.converter.StringToLimitConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLimitConverter());
}
}

View File

@ -0,0 +1,12 @@
package mops.gruppen2.config.converter;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import org.springframework.core.convert.converter.Converter;
public class StringToLimitConverter implements Converter<String, Limit> {
@Override
public Limit convert(String value) {
return new Limit(Long.parseLong(value));
}
}

View File

@ -0,0 +1,44 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.exception.UserAlreadyExistsException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.User;
/**
* Fügt einen einzelnen Nutzer einer Gruppe hinzu.
*/
@Log4j2
@Value
@AllArgsConstructor
public class AddMemberEvent extends Event {
@JsonProperty("user")
User user;
public AddMemberEvent(Group group, String exec, String target, User user) throws IdMismatchException {
super(group.getId(), exec, target);
this.user = user;
if (!target.equals(user.getId())) {
throw new IdMismatchException("Der User passt nicht zur angegebenen userid.");
}
}
@Override
protected void applyEvent(Group group) throws UserAlreadyExistsException, GroupFullException {
group.addMember(target, user);
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
}
@Override
public String getType() {
return EventType.ADDMEMBER.toString();
}
}

View File

@ -1,49 +0,0 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Role;
import mops.gruppen2.domain.model.User;
/**
* Fügt einen einzelnen Nutzer einer Gruppe hinzu.
*/
@Log4j2
@Value
@AllArgsConstructor
public class AddUserEvent extends Event {
@JsonProperty("givenname")
String givenname;
@JsonProperty("familyname")
String familyname;
@JsonProperty("email")
String email;
public AddUserEvent(Group group, User user) {
super(group.getGroupid(), user.getUserid());
givenname = user.getGivenname();
familyname = user.getFamilyname();
email = user.getEmail();
}
@Override
protected void applyEvent(Group group) throws EventException {
ValidationHelper.throwIfMember(group, new User(userid));
ValidationHelper.throwIfGroupFull(group);
group.getMembers().put(userid, new User(userid, givenname, familyname, email));
group.getRoles().put(userid, Role.MEMBER);
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles());
}
}

View File

@ -4,10 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.model.group.Group;
import java.time.LocalDateTime;
import java.util.UUID;
@Log4j2
@ -15,24 +15,30 @@ import java.util.UUID;
@AllArgsConstructor// Value generiert den allArgsConstrucot nur, wenn keiner explizit angegeben ist
public class CreateGroupEvent extends Event {
@JsonProperty("parent")
UUID parent;
@JsonProperty("date")
LocalDateTime date;
@JsonProperty("type")
Type type;
public CreateGroupEvent(UUID groupId, User user, UUID parent, Type type) {
super(groupId, user.getUserid());
this.parent = parent;
this.type = type;
public CreateGroupEvent(UUID groupId, String exec, LocalDateTime date) {
super(groupId, exec, null);
this.date = date;
}
@Override
protected void applyEvent(Group group) {
group.setGroupid(groupid);
group.setParent(parent);
group.setType(type);
protected void applyEvent(Group group) throws BadArgumentException {
group.setId(groupid);
group.setCreator(exec);
group.setCreationDate(date);
log.trace("\t\t\t\t\tNeue Gruppe: {}", group.toString());
}
@Override
public String getType() {
return EventType.CREATEGROUP.toString();
}
@Override
public String toString() {
return "(" + version + "," + groupid + "," + date + ")";
}
}

View File

@ -1,30 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.User;
@Log4j2
@Value
@AllArgsConstructor
public class DeleteGroupEvent extends Event {
public DeleteGroupEvent(Group group, User user) {
super(group.getGroupid(), user.getUserid());
}
@Override
protected void applyEvent(Group group) {
group.getRoles().clear();
group.getMembers().clear();
group.setTitle(null);
group.setDescription(null);
group.setType(null);
group.setParent(null);
group.setLimit(null);
log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group);
}
}

View File

@ -1,33 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.User;
/**
* Entfernt ein einzelnes Mitglied einer Gruppe.
*/
@Log4j2
@Value
@AllArgsConstructor
public class DeleteUserEvent extends Event {
public DeleteUserEvent(Group group, User user) {
super(group.getGroupid(), user.getUserid());
}
@Override
protected void applyEvent(Group group) throws EventException {
ValidationHelper.throwIfNoMember(group, new User(userid));
group.getMembers().remove(userid);
group.getRoles().remove(userid);
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles());
}
}

View File

@ -0,0 +1,29 @@
package mops.gruppen2.domain.event;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
@Log4j2
@Value
@AllArgsConstructor
public class DestroyGroupEvent extends Event {
public DestroyGroupEvent(Group group, String exec) {
super(group.getId(), exec, null);
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.destroy(exec);
log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group.toString());
}
@Override
public String getType() {
return EventType.DESTROYGROUP.toString();
}
}

View File

@ -1,59 +1,92 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.GroupIdMismatchException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.model.group.Group;
import java.util.UUID;
@Log4j2
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = AddUserEvent.class, name = "AddUserEvent"),
@JsonSubTypes.Type(value = CreateGroupEvent.class, name = "CreateGroupEvent"),
@JsonSubTypes.Type(value = DeleteUserEvent.class, name = "DeleteUserEvent"),
@JsonSubTypes.Type(value = UpdateGroupDescriptionEvent.class, name = "UpdateGroupDescriptionEvent"),
@JsonSubTypes.Type(value = UpdateGroupTitleEvent.class, name = "UpdateGroupTitleEvent"),
@JsonSubTypes.Type(value = UpdateRoleEvent.class, name = "UpdateRoleEvent"),
@JsonSubTypes.Type(value = DeleteGroupEvent.class, name = "DeleteGroupEvent"),
@JsonSubTypes.Type(value = UpdateUserLimitEvent.class, name = "UpdateUserLimitEvent")})
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "class")
@JsonSubTypes({@JsonSubTypes.Type(value = AddMemberEvent.class, name = "ADDMEMBER"),
@JsonSubTypes.Type(value = CreateGroupEvent.class, name = "CREATEGROUP"),
@JsonSubTypes.Type(value = DestroyGroupEvent.class, name = "DESTROYGROUP"),
@JsonSubTypes.Type(value = KickMemberEvent.class, name = "KICKMEMBER"),
@JsonSubTypes.Type(value = SetDescriptionEvent.class, name = "SETDESCRIPTION"),
@JsonSubTypes.Type(value = SetInviteLinkEvent.class, name = "SETLINK"),
@JsonSubTypes.Type(value = SetLimitEvent.class, name = "SETLIMIT"),
@JsonSubTypes.Type(value = SetParentEvent.class, name = "SETPARENT"),
@JsonSubTypes.Type(value = SetTitleEvent.class, name = "SETTITLE"),
@JsonSubTypes.Type(value = SetTypeEvent.class, name = "SETTYPE"),
@JsonSubTypes.Type(value = UpdateRoleEvent.class, name = "UPDATEROLE")})
@Getter
@AllArgsConstructor
@NoArgsConstructor // Lombok needs a default constructor in the base class
public abstract class Event {
@JsonProperty("groupid")
protected UUID groupid;
@JsonProperty("userid")
protected String userid;
@JsonProperty("version")
protected long version;
@JsonProperty("exec")
protected String exec;
@JsonProperty("target")
protected String target;
public Event(UUID groupid, String exec, String target) {
this.groupid = groupid;
this.exec = exec;
this.target = target;
}
public void init(long version) {
if (this.version != 0) {
throw new BadArgumentException("Event wurde schon initialisiert. (" + getType() + ")");
}
log.trace("Event wurde initialisiert. (" + getType() + "," + version + ")");
this.version = version;
}
public Group apply(Group group) throws EventException {
checkGroupIdMatch(group.getGroupid());
log.trace("Event wird angewendet:\t{}", this);
log.trace("Event angewendet:\t{}", this);
if (version == 0) {
throw new BadArgumentException("Event wurde nicht initialisiert.");
}
checkGroupIdMatch(group.getId());
group.update(version);
applyEvent(group);
return group;
}
private void checkGroupIdMatch(UUID groupId) {
private void checkGroupIdMatch(UUID groupId) throws IdMismatchException {
// CreateGroupEvents müssen die Id erst initialisieren
if (this instanceof CreateGroupEvent) {
return;
}
if (!groupid.equals(groupId)) {
throw new GroupIdMismatchException(getClass().toString());
throw new IdMismatchException("Das Event gehört zu einer anderen Gruppe");
}
}
protected abstract void applyEvent(Group group) throws EventException;
@JsonIgnore
public abstract String getType();
}

View File

@ -0,0 +1,15 @@
package mops.gruppen2.domain.event;
public enum EventType {
ADDMEMBER,
CREATEGROUP,
DESTROYGROUP,
KICKMEMBER,
SETDESCRIPTION,
SETLINK,
SETLIMIT,
SETPARENT,
SETTITLE,
SETTYPE,
UPDATEROLE
}

View File

@ -0,0 +1,33 @@
package mops.gruppen2.domain.event;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.group.Group;
/**
* Entfernt ein einzelnes Mitglied einer Gruppe.
*/
@Log4j2
@Value
@AllArgsConstructor
public class KickMemberEvent extends Event {
public KickMemberEvent(Group group, String exec, String target) {
super(group.getId(), exec, target);
}
@Override
protected void applyEvent(Group group) throws UserNotFoundException, LastAdminException {
group.kickMember(target);
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
}
@Override
public String getType() {
return EventType.KICKMEMBER.toString();
}
}

View File

@ -0,0 +1,40 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Description;
import javax.validation.Valid;
/**
* Ändert nur die Gruppenbeschreibung.
*/
@Log4j2
@Value
@AllArgsConstructor
public class SetDescriptionEvent extends Event {
@JsonProperty("desc")
Description description;
public SetDescriptionEvent(Group group, String exec, @Valid Description description) {
super(group.getId(), exec, null);
this.description = description;
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setDescription(exec, description);
log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription());
}
@Override
public String getType() {
return EventType.SETDESCRIPTION.toString();
}
}

View File

@ -0,0 +1,37 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Link;
import javax.validation.Valid;
@Log4j2
@Value
@AllArgsConstructor
public class SetInviteLinkEvent extends Event {
@JsonProperty("link")
Link link;
public SetInviteLinkEvent(Group group, String exec, @Valid Link link) {
super(group.getId(), exec, null);
this.link = link;
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setLink(exec, link);
log.trace("\t\t\t\t\tNeuer Link: {}", group.getLink());
}
@Override
public String getType() {
return EventType.SETLINK.toString();
}
}

View File

@ -0,0 +1,38 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import javax.validation.Valid;
@Log4j2
@Value
@AllArgsConstructor
public class SetLimitEvent extends Event {
@JsonProperty("limit")
Limit limit;
public SetLimitEvent(Group group, String exec, @Valid Limit limit) {
super(group.getId(), exec, null);
this.limit = limit;
}
@Override
protected void applyEvent(Group group) throws BadArgumentException, NoAccessException {
group.setLimit(exec, limit);
log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getLimit());
}
@Override
public String getType() {
return EventType.SETLIMIT.toString();
}
}

View File

@ -0,0 +1,37 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import javax.validation.Valid;
@Log4j2
@Value
@AllArgsConstructor
public class SetParentEvent extends Event {
@JsonProperty("parent")
Parent parent;
public SetParentEvent(Group group, String exec, @Valid Parent parent) {
super(group.getId(), exec, null);
this.parent = parent;
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setParent(exec, parent);
log.trace("\t\t\t\t\tNeues Parent: {}", group.getParent());
}
@Override
public String getType() {
return EventType.SETPARENT.toString();
}
}

View File

@ -0,0 +1,41 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Title;
import javax.validation.Valid;
/**
* Ändert nur den Gruppentitel.
*/
@Log4j2
@Value
@AllArgsConstructor
public class SetTitleEvent extends Event {
@JsonProperty("title")
Title title;
public SetTitleEvent(Group group, String exec, @Valid Title title) {
super(group.getId(), exec, null);
this.title = title;
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setTitle(exec, title);
log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle());
}
@Override
public String getType() {
return EventType.SETTITLE.toString();
}
}

View File

@ -0,0 +1,36 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import javax.validation.Valid;
@Log4j2
@Value
@AllArgsConstructor
public class SetTypeEvent extends Event {
@JsonProperty("type")
Type type;
public SetTypeEvent(Group group, String exec, @Valid Type type) {
super(group.getId(), exec, null);
this.type = type;
}
@Override
protected void applyEvent(Group group) throws EventException {
group.setType(exec, type);
}
@Override
public String getType() {
return EventType.SETTYPE.toString();
}
}

View File

@ -1,33 +0,0 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.Description;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.User;
/**
* Ändert nur die Gruppenbeschreibung.
*/
@Log4j2
@Value
@AllArgsConstructor
public class UpdateGroupDescriptionEvent extends Event {
@JsonProperty("desc")
Description description;
public UpdateGroupDescriptionEvent(Group group, User user, Description description) {
super(group.getGroupid(), user.getUserid());
this.description = description;
}
@Override
protected void applyEvent(Group group) {
group.setDescription(description);
log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription());
}
}

View File

@ -1,34 +0,0 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Title;
import mops.gruppen2.domain.model.User;
/**
* Ändert nur den Gruppentitel.
*/
@Log4j2
@Value
@AllArgsConstructor
public class UpdateGroupTitleEvent extends Event {
@JsonProperty("title")
Title title;
public UpdateGroupTitleEvent(Group group, User user, Title title) {
super(group.getGroupid(), user.getUserid());
this.title = title;
}
@Override
protected void applyEvent(Group group) {
group.setTitle(title);
log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle());
}
}

View File

@ -4,11 +4,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Role;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Role;
/**
* Aktualisiert die Gruppenrolle eines Teilnehmers.
@ -21,18 +20,21 @@ public class UpdateRoleEvent extends Event {
@JsonProperty("role")
Role role;
public UpdateRoleEvent(Group group, User user, Role tole) {
super(group.getGroupid(), user.getUserid());
role = tole;
public UpdateRoleEvent(Group group, String exec, String target, Role role) {
super(group.getId(), exec, target);
this.role = role;
}
@Override
protected void applyEvent(Group group) throws UserNotFoundException {
ValidationHelper.throwIfNoMember(group, new User(userid));
protected void applyEvent(Group group) throws UserNotFoundException, LastAdminException {
group.memberPutRole(target, role);
group.getRoles().put(userid, role);
log.trace("\t\t\t\t\tNeue Admin: {}", group.getAdmins());
}
log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles());
@Override
public String getType() {
return EventType.UPDATEROLE.toString();
}
}

View File

@ -1,36 +0,0 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Limit;
import mops.gruppen2.domain.model.User;
@Log4j2
@Value
@AllArgsConstructor
public class UpdateUserLimitEvent extends Event {
@JsonProperty("limit")
Limit limit;
public UpdateUserLimitEvent(Group group, User user, Limit limit) {
super(group.getGroupid(), user.getUserid());
this.limit = limit;
}
@Override
protected void applyEvent(Group group) throws EventException {
if (limit.getUserLimit() < group.getMembers().size()) {
throw new BadParameterException("Teilnehmerlimit zu klein.");
}
group.setLimit(limit);
log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getLimit());
}
}

View File

@ -0,0 +1,12 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class BadArgumentException extends EventException {
private static final long serialVersionUID = -6757742013238625595L;
public BadArgumentException(String info) {
super(HttpStatus.BAD_REQUEST, "Fehlerhafter Parameter.", info);
}
}

View File

@ -1,12 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class BadParameterException extends EventException {
private static final long serialVersionUID = -6757742013238625595L;
public BadParameterException(String info) {
super(HttpStatus.BAD_REQUEST, "Fehlerhafter Parameter angegeben!", info);
}
}

View File

@ -7,7 +7,7 @@ public class BadPayloadException extends EventException {
private static final long serialVersionUID = -3978242017847155629L;
public BadPayloadException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Payload konnte nicht übersetzt werden!", info);
super(HttpStatus.INTERNAL_SERVER_ERROR, "Payload konnte nicht übersetzt werden.", info);
}
}

View File

@ -7,7 +7,7 @@ public class GroupFullException extends EventException {
private static final long serialVersionUID = -4011141160467668713L;
public GroupFullException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Gruppe hat die maximale Mitgliederanzahl bereits erreicht!", info);
super(HttpStatus.INTERNAL_SERVER_ERROR, "Gruppe hat maximale Teilnehmeranzahl bereits erreicht.", info);
}
}

View File

@ -1,13 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class GroupIdMismatchException extends EventException {
private static final long serialVersionUID = 7944077617758922089L;
public GroupIdMismatchException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Falsche Gruppe für Event.", info);
}
}

View File

@ -7,7 +7,7 @@ public class GroupNotFoundException extends EventException {
private static final long serialVersionUID = -4738218416842951106L;
public GroupNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Die Gruppe existiert nicht oder wurde gelöscht.", info);
super(HttpStatus.NOT_FOUND, "Gruppe existiert nicht oder wurde gelöscht.", info);
}
}

View File

@ -0,0 +1,13 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class IdMismatchException extends EventException {
private static final long serialVersionUID = 7944077617758922089L;
public IdMismatchException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Ids stimmen nicht überein.", info);
}
}

View File

@ -7,7 +7,7 @@ public class InvalidInviteException extends EventException {
private static final long serialVersionUID = 2643001101459427944L;
public InvalidInviteException(String info) {
super(HttpStatus.NOT_FOUND, "Der Einladungslink ist ungültig oder die Gruppe wurde gelöscht.", info);
super(HttpStatus.NOT_FOUND, "Einladungslink ist ungültig oder Gruppe wurde gelöscht.", info);
}
}

View File

@ -0,0 +1,12 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class LastAdminException extends EventException {
private static final long serialVersionUID = 9059481382346544288L;
public LastAdminException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Gruppe braucht mindestens einen Admin.", info);
}
}

View File

@ -7,7 +7,7 @@ public class NoAccessException extends EventException {
private static final long serialVersionUID = 1696988497122834654L;
public NoAccessException(String info) {
super(HttpStatus.FORBIDDEN, "Hier hast du keinen Zugriff.", info);
super(HttpStatus.FORBIDDEN, "Kein Zugriff.", info);
}
}

View File

@ -1,12 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class NoAdminAfterActionException extends EventException {
private static final long serialVersionUID = 9059481382346544288L;
public NoAdminAfterActionException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Gruppe braucht einen Admin.", info);
}
}

View File

@ -7,7 +7,7 @@ public class PageNotFoundException extends EventException {
private static final long serialVersionUID = 2374509005158710104L;
public PageNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Die Seite wurde nicht gefunden!", info);
super(HttpStatus.NOT_FOUND, "Seite wurde nicht gefunden.", info);
}
}

View File

@ -7,7 +7,7 @@ public class UserAlreadyExistsException extends EventException {
private static final long serialVersionUID = -8150634358760194625L;
public UserAlreadyExistsException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Der User existiert bereits.", info);
super(HttpStatus.INTERNAL_SERVER_ERROR, "User existiert bereits.", info);
}
}

View File

@ -7,7 +7,7 @@ public class UserNotFoundException extends EventException {
private static final long serialVersionUID = 8347442921199785291L;
public UserNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Der User existiert nicht.", info);
super(HttpStatus.NOT_FOUND, "User existiert nicht.", info);
}
}

View File

@ -7,7 +7,7 @@ public class WrongFileException extends EventException {
private static final long serialVersionUID = -166192514348555116L;
public WrongFileException(String info) {
super(HttpStatus.BAD_REQUEST, "Die Datei ist keine valide CSV-Datei!", info);
super(HttpStatus.BAD_REQUEST, "Datei ist keine valide CSV-Datei.", info);
}
}

View File

@ -1,16 +1,18 @@
package mops.gruppen2.domain.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.web.api.GroupRequestWrapper;
import java.util.List;
//TODO: sinnvolles format
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class APIHelper {
private APIHelper() {}
public static GroupRequestWrapper wrap(long status, List<Group> groupList) {
return new GroupRequestWrapper(status, groupList);
}

View File

@ -0,0 +1,35 @@
package mops.gruppen2.domain.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.EventType;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class CommonHelper {
public static String[] eventTypesToString(EventType... types) {
String[] stringtypes = new String[types.length];
for (int i = 0; i < types.length; i++) {
stringtypes[i] = types[i].toString();
}
return stringtypes;
}
public static List<String> uuidsToString(List<UUID> ids) {
return ids.stream()
.map(UUID::toString)
.collect(Collectors.toList());
}
public static boolean uuidIsEmpty(UUID uuid) {
return "00000000-0000-0000-0000-000000000000".equals(uuid.toString());
}
}

View File

@ -3,10 +3,12 @@ package mops.gruppen2.domain.helper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.WrongFileException;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.User;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@ -16,10 +18,9 @@ import java.util.List;
import java.util.stream.Collectors;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class CsvHelper {
private CsvHelper() {}
public static List<User> readCsvFile(MultipartFile file) throws EventException {
if (file == null || file.isEmpty()) {
return Collections.emptyList();

View File

@ -1,49 +0,0 @@
package mops.gruppen2.domain.helper;
import lombok.extern.log4j.Log4j2;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Log4j2
public final class IdHelper {
private IdHelper() {}
public static List<UUID> stringsToUUID(List<String> groupIds) {
return groupIds.stream()
.map(IdHelper::stringToUUID)
.collect(Collectors.toList());
}
/**
* Wandelt einen String in eine UUID um.
* Dabei wird eine "leere" UUID generiert, falls der String leer ist.
*
* @param groupId Id als String
*
* @return Id als UUID
*/
public static UUID stringToUUID(String groupId) {
return groupId.isEmpty() ? emptyUUID() : UUID.fromString(groupId);
}
public static List<String> uuidsToString(List<UUID> groupIds) {
return groupIds.stream()
.map(UUID::toString)
.collect(Collectors.toList());
}
public static String uuidToString(UUID groupId) {
return groupId.toString();
}
public static boolean isEmpty(UUID id) {
return id == null || emptyUUID().equals(id);
}
public static UUID emptyUUID() {
return UUID.fromString("00000000-0000-0000-0000-000000000000");
}
}

View File

@ -2,6 +2,9 @@ package mops.gruppen2.domain.helper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.Event;
@ -9,10 +12,9 @@ import mops.gruppen2.domain.event.Event;
* Übersetzt JSON-Event-Payloads zu Java-Event-Repräsentationen und zurück.
*/
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class JsonHelper {
private JsonHelper() {}
/**
* Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload.
*
@ -24,7 +26,7 @@ public final class JsonHelper {
*/
public static String serializeEvent(Event event) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
String payload = mapper.writeValueAsString(event);
log.trace(payload);
return payload;
@ -40,7 +42,9 @@ public final class JsonHelper {
* @throws JsonProcessingException Bei JSON Fehler
*/
public static Event deserializeEvent(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, Event.class);
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
Event event = mapper.readValue(json, Event.class);
log.trace(event);
return event;
}
}

View File

@ -1,118 +1,105 @@
package mops.gruppen2.domain.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.exception.NoAdminAfterActionException;
import mops.gruppen2.domain.exception.UserAlreadyExistsException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.web.form.CreateForm;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import static mops.gruppen2.domain.model.Role.ADMIN;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ValidationHelper {
private ValidationHelper() {}
// ######################################## CHECK ############################################
/**
* Überprüft, ob ein User in einer Gruppe teilnimmt.
*/
public static boolean checkIfMember(Group group, User user) {
return group.getMembers().containsKey(user.getUserid());
public static boolean checkIfMember(Group group, String userid) {
return group.isMember(userid);
}
public static boolean checkIfLastMember(User user, Group group) {
return checkIfMember(group, user) && group.getMembers().size() == 1;
public static boolean checkIfLastMember(Group group, String userid) {
return checkIfMember(group, userid) && group.size() == 1;
}
/**
* Überprüft, ob eine Gruppe voll ist.
*/
public static boolean checkIfGroupFull(Group group) {
return group.getMembers().size() >= group.getLimit().getUserLimit();
return group.isFull();
}
/**
* Überprüft, ob eine Gruppe leer ist.
*/
public static boolean checkIfGroupEmpty(Group group) {
return group.getMembers().isEmpty();
return group.isEmpty();
}
/**
* Überprüft, ob ein User in einer Gruppe Admin ist.
*/
public static boolean checkIfAdmin(Group group, User user) {
if (checkIfMember(group, user)) {
return group.getRoles().get(user.getUserid()) == ADMIN;
public static boolean checkIfAdmin(Group group, String userid) {
if (checkIfMember(group, userid)) {
return group.isAdmin(userid);
}
return false;
}
public static boolean checkIfLastAdmin(User user, Group group) {
return checkIfAdmin(group, user) && group.getRoles().values().stream()
.filter(role -> role == ADMIN)
.count() == 1;
public static boolean checkIfLastAdmin(Group group, String userid) {
return checkIfAdmin(group, userid) && group.getAdmins().size() == 1;
}
// ######################################## THROW ############################################
public static void throwIfMember(Group group, User user) {
if (checkIfMember(group, user)) {
log.error("Benutzer {} ist schon in Gruppe {}", user, group);
throw new UserAlreadyExistsException(user.toString());
public static void throwIfMember(Group group, String userid) throws UserAlreadyExistsException {
if (checkIfMember(group, userid)) {
log.error("Benutzer {} ist schon in Gruppe {}", userid, group);
throw new UserAlreadyExistsException(userid);
}
}
public static void throwIfNoMember(Group group, User user) {
if (!checkIfMember(group, user)) {
log.error("Benutzer {} ist nicht in Gruppe {}!", user, group);
throw new UserNotFoundException(user.toString());
public static void throwIfNoMember(Group group, String userid) throws UserNotFoundException {
if (!checkIfMember(group, userid)) {
log.error("Benutzer {} ist nicht in Gruppe {}!", userid, group);
throw new UserNotFoundException(userid);
}
}
public static void throwIfNoAdmin(Group group, User user) {
if (!checkIfAdmin(group, user)) {
log.error("User {} ist kein Admin in Gruppe {}!", user, group);
throw new NoAccessException(group.toString());
public static void throwIfNoAdmin(Group group, String userid) throws NoAccessException {
if (!checkIfAdmin(group, userid)) {
log.error("User {} ist kein Admin in Gruppe {}!", userid, group);
throw new NoAccessException(group.getId().toString());
}
}
/**
* Schmeißt keine Exception, wenn der User der letzte User ist.
*/
public static void throwIfLastAdmin(User user, Group group) {
if (!checkIfLastMember(user, group) && checkIfLastAdmin(user, group)) {
throw new NoAdminAfterActionException("Du bist letzter Admin!");
public static void throwIfLastAdmin(Group group, String userid) throws LastAdminException {
if (!checkIfLastMember(group, userid) && checkIfLastAdmin(group, userid)) {
throw new LastAdminException("Du bist letzter Admin!");
}
}
public static void throwIfGroupFull(Group group) {
public static void throwIfGroupFull(Group group) throws GroupFullException {
if (checkIfGroupFull(group)) {
log.error("Die Gruppe {} ist voll!", group);
throw new GroupFullException(group.toString());
throw new GroupFullException(group.getId().toString());
}
}
// ##################################### VALIDATE FIELDS #####################################
public static void validateCreateForm(KeycloakAuthenticationToken token, CreateForm form) {
if (!token.getAccount().getRoles().contains("orga")
&& form.getType() == Type.LECTURE) {
throw new BadParameterException("Eine Veranstaltung kann nur von ORGA erstellt werden.");
public static void validateCreateForm(KeycloakAuthenticationToken token, Type type) {
if (!token.getAccount().getRoles().contains("orga") && type == Type.LECTURE) {
throw new BadArgumentException("Nur Orga kann Veranstaltungen erstellen.");
}
}
}

View File

@ -1,39 +0,0 @@
package mops.gruppen2.domain.model;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Repräsentiert den aggregierten Zustand einer Gruppe.
*/
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
public class Group {
@EqualsAndHashCode.Include
private UUID groupid;
@ToString.Exclude
private UUID parent;
private Type type;
private Title title;
private Description description;
@ToString.Exclude
private Limit limit = new Limit(1); // Add initial user
@ToString.Exclude
private final Map<String, User> members = new HashMap<>();
@ToString.Exclude
private final Map<String, Role> roles = new HashMap<>();
}

View File

@ -1,10 +0,0 @@
package mops.gruppen2.domain.model;
public enum Role {
ADMIN,
MEMBER;
public Role toggle() {
return this == ADMIN ? MEMBER : ADMIN;
}
}

View File

@ -0,0 +1,293 @@
package mops.gruppen2.domain.model.group;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.exception.UserAlreadyExistsException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.group.wrapper.Body;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Link;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.domain.model.group.wrapper.Title;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Repräsentiert den aggregierten Zustand einer Gruppe.
*
* <p>
* Muss beim Start gesetzt werden: groupid, meta
*/
@Log4j2
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(onlyExplicitlyIncluded = true)
public class Group {
// Metainformationen
@EqualsAndHashCode.Include
@ToString.Include
private UUID groupid;
@Getter
private Type type = Type.PRIVATE;
private Parent parent = Parent.EMPTY();
private Limit limit = Limit.DEFAULT(); // Add initial user
private Link link = Link.RANDOM();
@ToString.Include
private GroupMeta meta = GroupMeta.EMPTY();
private GroupOptions options = GroupOptions.DEFAULT();
//@ToString.Exclude
//private LocalDateTime age;
// Inhalt
private Title title;
private Description description;
private Body body;
// Integrationen
// Teilnehmer
private Map<String, Membership> memberships = new HashMap<>();
// ####################################### Members ###########################################
public List<User> getMembers() {
return SortHelper.sortByMemberRole(new ArrayList<>(memberships.values())).stream()
.map(Membership::getUser)
.collect(Collectors.toList());
}
public List<User> getRegulars() {
return memberships.values().stream()
.map(Membership::getUser)
.filter(member -> isRegular(member.getId()))
.collect(Collectors.toList());
}
public List<User> getAdmins() {
return memberships.values().stream()
.map(Membership::getUser)
.filter(member -> isAdmin(member.getId()))
.collect(Collectors.toList());
}
public Role getRole(String userid) {
return memberships.get(userid).getRole();
}
public void addMember(String target, User user) throws UserAlreadyExistsException, GroupFullException {
ValidationHelper.throwIfMember(this, target);
ValidationHelper.throwIfGroupFull(this);
memberships.put(target, new Membership(user, Role.REGULAR));
}
public void kickMember(String target) throws UserNotFoundException, LastAdminException {
ValidationHelper.throwIfNoMember(this, target);
ValidationHelper.throwIfLastAdmin(this, target);
memberships.remove(target);
}
public void memberPutRole(String target, Role role) throws UserNotFoundException, LastAdminException {
ValidationHelper.throwIfNoMember(this, target);
if (role == Role.REGULAR) {
ValidationHelper.throwIfLastAdmin(this, target);
}
memberships.put(target, memberships.get(target).setRole(role));
}
public boolean isMember(String target) {
return memberships.containsKey(target);
}
public boolean isAdmin(String target) throws UserNotFoundException {
ValidationHelper.throwIfNoMember(this, target);
return memberships.get(target).getRole() == Role.ADMIN;
}
public boolean isRegular(String target) throws UserNotFoundException {
ValidationHelper.throwIfNoMember(this, target);
return memberships.get(target).getRole() == Role.REGULAR;
}
// ######################################### Getters #########################################
public UUID getId() {
return groupid;
}
public UUID getParent() {
return parent.getGroupid();
}
public long getLimit() {
return limit.getValue();
}
public String getTitle() {
return title.toString();
}
public String getDescription() {
return description.getValue();
}
public String getLink() {
return link.getValue();
}
public String creator() {
return meta.getCreator();
}
public long version() {
return meta.getVersion();
}
public LocalDateTime creationDate() {
return meta.getCreationDate();
}
public int size() {
return memberships.size();
}
public boolean isFull() {
return size() >= limit.getValue();
}
public boolean isEmpty() {
return size() == 0;
}
public boolean isPublic() {
return type == Type.PUBLIC;
}
public boolean isPrivate() {
return type == Type.PRIVATE;
}
public boolean isLecture() {
return type == Type.LECTURE;
}
// ######################################## Setters ##########################################
public void setId(UUID groupid) throws BadArgumentException {
if (this.groupid != null) {
throw new BadArgumentException("GruppenId bereits gesetzt.");
}
this.groupid = groupid;
}
public void setType(String exec, Type type) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.type = type;
}
public void setTitle(String exec, @Valid Title title) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.title = title;
}
public void setDescription(String exec, @Valid Description description) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.description = description;
}
public void setLimit(String exec, @Valid Limit limit) throws NoAccessException, BadArgumentException {
ValidationHelper.throwIfNoAdmin(this, exec);
if (size() > limit.getValue()) {
throw new BadArgumentException("Das Userlimit ist zu klein für die Gruppe.");
}
this.limit = limit;
}
public void setParent(String exec, @Valid Parent parent) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.parent = parent;
}
public void setLink(String exec, @Valid Link link) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.link = link;
}
public void update(long version) throws IdMismatchException {
meta = meta.setVersion(version);
}
public void setCreator(String target) throws BadArgumentException {
meta = meta.setCreator(target);
}
public void setCreationDate(LocalDateTime date) throws BadArgumentException {
meta = meta.setCreationDate(date);
}
// ######################################### Util ############################################
public void destroy(String userid) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, userid);
groupid = null;
parent = null;
type = null;
title = null;
description = null;
limit = null;
memberships = null;
link = null;
meta = null;
options = null;
body = null;
}
public String format() {
return title + " " + description;
}
}

View File

@ -0,0 +1,47 @@
package mops.gruppen2.domain.model.group;
import lombok.ToString;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.IdMismatchException;
import java.time.LocalDateTime;
@Log4j2
@Value
@ToString
public class GroupMeta {
long version;
String creator;
LocalDateTime creationDate;
public GroupMeta setVersion(long version) throws IdMismatchException {
if (this.version >= version) {
throw new IdMismatchException("Die Gruppe ist bereits auf einem neueren Stand.");
}
return new GroupMeta(version, creator, creationDate);
}
public GroupMeta setCreator(String userid) throws BadArgumentException {
if (creator != null) {
throw new BadArgumentException("Gruppe hat schon einen Ersteller.");
}
return new GroupMeta(version, userid, creationDate);
}
public GroupMeta setCreationDate(LocalDateTime date) throws BadArgumentException {
if (creationDate != null) {
throw new BadArgumentException("Gruppe hat schon ein Erstellungsdatum.");
}
return new GroupMeta(version, creator, date);
}
public static GroupMeta EMPTY() {
return new GroupMeta(0, null, null);
}
}

View File

@ -0,0 +1,36 @@
package mops.gruppen2.domain.model.group;
import lombok.Value;
//TODO: doooooodododo
@Value
class GroupOptions {
// Gruppe
boolean showClearname;
boolean hasBody;
boolean isLeavable;
boolean hasLink;
String customLogo;
String customBackground;
String customTitle;
// Integrations
boolean hasMaterialIntegration;
boolean hasTermineIntegration;
boolean hasPortfolioIntegration;
static GroupOptions DEFAULT() {
return new GroupOptions(true,
false,
true,
false,
null,
null,
null,
true,
true,
true);
}
}

View File

@ -0,0 +1,23 @@
package mops.gruppen2.domain.model.group;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Membership {
User user;
Role role;
// LocalDateTime age;
@Override
public String toString() {
return user.format() + ": " + role;
}
public Membership setRole(Role role) {
return new Membership(user, role);
}
}

View File

@ -0,0 +1,10 @@
package mops.gruppen2.domain.model.group;
public enum Role {
ADMIN,
REGULAR;
public Role toggle() {
return this == ADMIN ? REGULAR : ADMIN;
}
}

View File

@ -0,0 +1,45 @@
package mops.gruppen2.domain.model.group;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SortHelper {
/**
* Sortiert die übergebene Liste an Gruppen, sodass Veranstaltungen am Anfang der Liste sind.
*
* @param groups Die Liste von Gruppen die sortiert werden soll
*/
public static List<Group> sortByGroupType(List<Group> groups) {
groups.sort((Group g1, Group g2) -> {
if (g1.getType() == Type.LECTURE) {
return -1;
}
if (g2.getType() == Type.LECTURE) {
return 1;
}
return 0;
});
return groups;
}
public static List<Membership> sortByMemberRole(List<Membership> 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;
}
}

View File

@ -1,4 +1,4 @@
package mops.gruppen2.domain.model;
package mops.gruppen2.domain.model.group;
public enum Type {
PUBLIC,

View File

@ -1,26 +1,32 @@
package mops.gruppen2.domain.model;
package mops.gruppen2.domain.model.group;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.Getter;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
@Log4j2
@Value
@AllArgsConstructor
@ToString
public class User {
@EqualsAndHashCode.Include
@Getter(AccessLevel.NONE)
@JsonProperty("id")
String userid;
@JsonProperty("givenname")
String givenname;
@ToString.Exclude
@JsonProperty("familyname")
String familyname;
@ToString.Exclude
@JsonProperty("mail")
String email;
public User(KeycloakAuthenticationToken token) {
@ -42,4 +48,12 @@ public class User {
familyname = "";
email = "";
}
public String getId() {
return userid;
}
public String format() {
return givenname + " " + familyname;
}
}

View File

@ -0,0 +1,8 @@
package mops.gruppen2.domain.model.group.wrapper;
import lombok.Value;
//TODO: do it
@Value
public class Body {
}

View File

@ -1,21 +1,21 @@
package mops.gruppen2.domain.model;
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Value
public class Description {
@NotBlank
@NotNull
@Size(min = 4, max = 512)
@JsonProperty("desc")
String groupDescription;
@JsonProperty("value")
String value;
@Override
public String toString() {
return groupDescription;
return value;
}
}

View File

@ -1,21 +1,27 @@
package mops.gruppen2.domain.model;
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Value
public class Limit {
@NotNull
@Min(1)
@Max(999_999)
@JsonProperty("limit")
long userLimit;
@JsonProperty("value")
long value;
public static Limit DEFAULT() {
return new Limit(1);
}
@Override
public String toString() {
return String.valueOf(userLimit);
return String.valueOf(value);
}
}

View File

@ -0,0 +1,21 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.UUID;
@Value
public class Link {
@NotNull
@Size(min = 36, max = 36)
@JsonProperty("value")
String value;
public static Link RANDOM() {
return new Link(UUID.randomUUID().toString());
}
}

View File

@ -0,0 +1,34 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import mops.gruppen2.domain.helper.CommonHelper;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.beans.ConstructorProperties;
import java.util.UUID;
@Value
public class Parent {
@NotNull
@JsonProperty("id")
UUID groupid;
@ConstructorProperties("id")
public Parent(@NotBlank @Size(min = 36, max = 36) String parentid) {
groupid = UUID.fromString(parentid);
}
public static Parent EMPTY() {
return new Parent("00000000-0000-0000-0000-000000000000");
}
@JsonIgnore
public boolean isEmpty() {
return CommonHelper.uuidIsEmpty(groupid);
}
}

View File

@ -1,21 +1,21 @@
package mops.gruppen2.domain.model;
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Value
public class Title {
@NotBlank
@NotNull
@Size(min = 4, max = 128)
@JsonProperty("title")
String groupTitle;
@JsonProperty("value")
String value;
@Override
public String toString() {
return groupTitle;
return value;
}
}

View File

@ -1,35 +1,40 @@
package mops.gruppen2.domain.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.event.AddMemberEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.event.EventType;
import mops.gruppen2.domain.exception.BadPayloadException;
import mops.gruppen2.domain.helper.IdHelper;
import mops.gruppen2.domain.exception.InvalidInviteException;
import mops.gruppen2.domain.helper.JsonHelper;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.persistance.EventRepository;
import mops.gruppen2.persistance.dto.EventDTO;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
import static mops.gruppen2.domain.event.EventType.CREATEGROUP;
import static mops.gruppen2.domain.event.EventType.DESTROYGROUP;
import static mops.gruppen2.domain.event.EventType.SETLINK;
import static mops.gruppen2.domain.helper.CommonHelper.eventTypesToString;
import static mops.gruppen2.domain.helper.CommonHelper.uuidsToString;
@Log4j2
@RequiredArgsConstructor
@Service
@TraceMethodCalls
public class EventStoreService {
private final EventRepository eventStore;
public EventStoreService(EventRepository eventStore) {
this.eventStore = eventStore;
}
//########################################### SAVE ###########################################
@ -67,7 +72,7 @@ public class EventStoreService {
//########################################### DTOs ###########################################
static List<EventDTO> getDTOsFromEvents(List<Event> events) {
private static List<EventDTO> getDTOsFromEvents(List<Event> events) {
return events.stream()
.map(EventStoreService::getDTOFromEvent)
.collect(Collectors.toList());
@ -80,13 +85,15 @@ public class EventStoreService {
*
* @return EventDTO (Neues DTO)
*/
static EventDTO getDTOFromEvent(Event event) {
private static EventDTO getDTOFromEvent(Event event) {
try {
String payload = JsonHelper.serializeEvent(event);
return new EventDTO(null,
event.getGroupid().toString(),
event.getUserid(),
getEventType(event),
event.getVersion(),
event.getExec(),
event.getTarget(),
event.getType(),
payload);
} catch (JsonProcessingException e) {
log.error("Event ({}) konnte nicht serialisiert werden!", event, e);
@ -116,19 +123,6 @@ public class EventStoreService {
}
}
/**
* Gibt den Eventtyp als String wieder.
*
* @param event Event dessen Typ abgefragt werden soll
*
* @return Der Name des Typs des Events
*/
private static String getEventType(Event event) {
int lastDot = event.getClass().getName().lastIndexOf('.');
return event.getClass().getName().substring(lastDot + 1);
}
// ######################################## QUERIES ##########################################
@ -176,8 +170,8 @@ public class EventStoreService {
* @return GruppenIds (UUID) als Liste
*/
List<UUID> findExistingGroupIds() {
List<Event> createEvents = findLatestEventsFromGroupsByType("CreateGroupEvent",
"DeleteGroupEvent");
List<Event> createEvents = findLatestEventsFromGroupsByType(CREATEGROUP,
DESTROYGROUP);
return createEvents.stream()
.filter(event -> event instanceof CreateGroupEvent)
@ -188,11 +182,18 @@ public class EventStoreService {
/**
* Liefert Gruppen-Ids von existierenden (ungelöschten) Gruppen, in welchen der User teilnimmt.
*
* <p>
* Vorgang:
* Finde für jede Gruppe das letzte Add- oder Kick-Event, welches den User betrifft
* Finde für jede Gruppe das letzte Destroy-Event
* Entferne alle alle Events von Gruppen, welche ein Destroy-Event haben
* Gebe die Gruppen zurück, auf welche sich die Add-Events beziehen
*
* @return GruppenIds (UUID) als Liste
*/
public List<UUID> findExistingUserGroups(User user) {
List<Event> userEvents = findLatestEventsFromGroupsByUser(user);
List<UUID> deletedIds = findLatestEventsFromGroupsByType("DeleteGroupEvent")
public List<UUID> findExistingUserGroups(String userid) {
List<Event> userEvents = findLatestEventsFromGroupsByUser(userid);
List<UUID> deletedIds = findLatestEventsFromGroupsByType(DESTROYGROUP)
.stream()
.map(Event::getGroupid)
.collect(Collectors.toList());
@ -200,11 +201,25 @@ public class EventStoreService {
userEvents.removeIf(event -> deletedIds.contains(event.getGroupid()));
return userEvents.stream()
.filter(event -> event instanceof AddUserEvent)
.filter(event -> event instanceof AddMemberEvent)
.map(Event::getGroupid)
.collect(Collectors.toList());
}
public UUID findGroupByLink(String link) {
List<Event> groupEvents = findEventsByType(eventTypesToString(SETLINK));
if (groupEvents.size() > 1) {
throw new InvalidInviteException("Es existieren mehrere Gruppen mit demselben Link.");
}
if (groupEvents.isEmpty()) {
throw new InvalidInviteException("Link nicht gefunden.");
}
return groupEvents.get(0).getGroupid();
}
// #################################### SIMPLE QUERIES #######################################
@ -219,32 +234,32 @@ public class EventStoreService {
return eventStore.findMaxEventId();
} catch (NullPointerException e) {
log.debug("Keine Events vorhanden!");
return 0;
return 1;
}
}
List<Event> findEventsByType(String... types) {
return getEventsFromDTOs(eventStore.findEventDTOsByType(Arrays.asList(types)));
return getEventsFromDTOs(eventStore.findEventDTOsByType(types));
}
List<Event> findEventsByType(String type) {
return getEventsFromDTOs(eventStore.findEventDTOsByType(Collections.singletonList(type)));
return getEventsFromDTOs(eventStore.findEventDTOsByType(type));
}
List<Event> findEventsByGroupAndType(List<UUID> groupIds, String... types) {
return getEventsFromDTOs(eventStore.findEventDTOsByGroupAndType(Arrays.asList(types),
IdHelper.uuidsToString(groupIds)));
return getEventsFromDTOs(eventStore.findEventDTOsByGroupAndType(uuidsToString(groupIds),
types));
}
/**
* Sucht zu jeder Gruppe das letzte Add- oder DeleteUserEvent heraus, welches den übergebenen User betrifft.
*
* @param user User, zu welchem die Events gesucht werden
* @param userid User, zu welchem die Events gesucht werden
*
* @return Eine Liste von einem Add- oder DeleteUserEvent pro Gruppe
*/
private List<Event> findLatestEventsFromGroupsByUser(User user) {
return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(user.getUserid()));
private List<Event> findLatestEventsFromGroupsByUser(String userid) {
return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(userid));
}
@ -255,7 +270,7 @@ public class EventStoreService {
*
* @return Eine Liste von einem Event pro Gruppe
*/
private List<Event> findLatestEventsFromGroupsByType(String... types) {
return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByType(Arrays.asList(types)));
private List<Event> findLatestEventsFromGroupsByType(EventType... types) {
return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByType(eventTypesToString(types)));
}
}

View File

@ -1,26 +1,33 @@
package mops.gruppen2.domain.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.domain.event.AddMemberEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.DeleteGroupEvent;
import mops.gruppen2.domain.event.DeleteUserEvent;
import mops.gruppen2.domain.event.DestroyGroupEvent;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.event.UpdateGroupDescriptionEvent;
import mops.gruppen2.domain.event.UpdateGroupTitleEvent;
import mops.gruppen2.domain.event.KickMemberEvent;
import mops.gruppen2.domain.event.SetDescriptionEvent;
import mops.gruppen2.domain.event.SetInviteLinkEvent;
import mops.gruppen2.domain.event.SetLimitEvent;
import mops.gruppen2.domain.event.SetParentEvent;
import mops.gruppen2.domain.event.SetTitleEvent;
import mops.gruppen2.domain.event.SetTypeEvent;
import mops.gruppen2.domain.event.UpdateRoleEvent;
import mops.gruppen2.domain.event.UpdateUserLimitEvent;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Description;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Limit;
import mops.gruppen2.domain.model.Role;
import mops.gruppen2.domain.model.Title;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Role;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Link;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.domain.model.group.wrapper.Title;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@ -28,51 +35,47 @@ import java.util.UUID;
* Behandelt Aufgaben, welche sich auf eine Gruppe beziehen.
* Es werden übergebene Gruppen bearbeitet und dementsprechend Events erzeugt und gespeichert.
*/
@Service
@Log4j2
@RequiredArgsConstructor
@Service
public class GroupService {
private final EventStoreService eventStoreService;
private final InviteService inviteService;
public GroupService(EventStoreService eventStoreService, InviteService inviteService) {
this.eventStoreService = eventStoreService;
this.inviteService = inviteService;
}
// ################################# GRUPPE ERSTELLEN ########################################
/**
* Erzeugt eine neue Gruppe und erzeugt nötige Events für die Initiale Setzung der Attribute.
*
* @param user Keycloak-Account
* @param title Gruppentitel
* @param description Gruppenbeschreibung
*/
public Group createGroup(User user,
Title title,
Description description,
Type type,
Limit userLimit,
UUID parent) {
public Group createGroup(String exec) {
return createGroup(UUID.randomUUID(), exec, LocalDateTime.now());
}
// Regeln:
// isPrivate -> !isLecture
// isLecture -> !isPrivate
Group group = createGroup(user, parent, type);
public void initGroupMembers(Group group,
String exec,
String target,
User user,
Limit limit) {
// Die Reihenfolge ist wichtig, da der ausführende User Admin sein muss
addUser(user, group);
updateRole(user, group, Role.ADMIN);
updateTitle(user, group, title);
updateDescription(user, group, description);
updateUserLimit(user, group, userLimit);
addMember(group, exec, target, user);
updateRole(group, exec, target, Role.ADMIN);
setLimit(group, exec, limit);
}
inviteService.createLink(group);
public void initGroupMeta(Group group,
String exec,
Type type,
Parent parent) {
return group;
setType(group, exec, type);
setParent(group, exec, parent);
}
public void initGroupText(Group group,
String exec,
Title title,
Description description) {
setTitle(group, exec, title);
setDescription(group, exec, description);
}
@ -87,12 +90,12 @@ public class GroupService {
*
* @param newUsers Userliste
* @param group Gruppe
* @param user Ausführender User
* @param exec Ausführender User
*/
public void addUsersToGroup(List<User> newUsers, Group group, User user) {
updateUserLimit(user, group, getAdjustedUserLimit(newUsers, group));
public void addUsersToGroup(Group group, String exec, List<User> newUsers) {
setLimit(group, exec, getAdjustedUserLimit(newUsers, group));
newUsers.forEach(newUser -> addUserSilent(newUser, group));
newUsers.forEach(newUser -> addUserSilent(group, exec, newUser.getId(), newUser));
}
/**
@ -106,24 +109,20 @@ public class GroupService {
* @return Das neue Teilnehmermaximum
*/
private static Limit getAdjustedUserLimit(List<User> newUsers, Group group) {
return new Limit(Math.max((long) group.getMembers().size() + newUsers.size(), group.getLimit().getUserLimit()));
return new Limit(Math.max((long) group.size() + newUsers.size(), group.getLimit()));
}
/**
* Wechselt die Rolle eines Teilnehmers von Admin zu Member oder andersherum.
* Überprüft, ob der User Mitglied ist und ob er der letzte Admin ist.
*
* @param user Teilnehmer, welcher geändert wird
* @param group Gruppe, in welcher sih der Teilnehmer befindet
* @param target Teilnehmer, welcher geändert wird
* @param group Gruppe, in welcher sih der Teilnehmer befindet
*
* @throws EventException Falls der User nicht gefunden wird
*/
public void toggleMemberRole(User user, Group group) throws EventException {
ValidationHelper.throwIfNoMember(group, user);
ValidationHelper.throwIfLastAdmin(user, group);
Role role = group.getRoles().get(user.getUserid());
updateRole(user, group, role.toggle());
public void toggleMemberRole(Group group, String exec, String target) {
updateRole(group, exec, target, group.getRole(target).toggle());
}
@ -134,59 +133,44 @@ public class GroupService {
/**
* Erzeugt eine Gruppe, speichert diese und gibt diese zurück.
*/
private Group createGroup(User user, UUID parent, Type type) {
Event event = new CreateGroupEvent(UUID.randomUUID(),
user,
parent,
type);
private Group createGroup(UUID groupid, String exec, LocalDateTime date) {
Event event = new CreateGroupEvent(groupid,
exec,
date);
Group group = new Group();
event.apply(group);
eventStoreService.saveEvent(event);
applyAndSave(group, event);
return group;
}
/**
* Erzeugt, speichert ein AddUserEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer schon Mitglied ist und ob Gruppe voll ist.
*/
public void addUser(User user, Group group) {
ValidationHelper.throwIfMember(group, user);
ValidationHelper.throwIfGroupFull(group);
Event event = new AddUserEvent(group, user);
event.apply(group);
eventStoreService.saveEvent(event);
}
/**
* Dasselbe wie addUser(), aber exceptions werden abgefangen und nicht geworfen.
*/
private void addUserSilent(User user, Group group) {
private void addUserSilent(Group group, String exec, String target, User user) {
try {
addUser(user, group);
addMember(group, exec, target, user);
} catch (Exception e) {
log.debug("Doppelter User {} wurde nicht zu Gruppe {} hinzugefügt!", user, group);
}
}
/**
* Erzeugt, speichert ein AddUserEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer schon Mitglied ist und ob Gruppe voll ist.
*/
public void addMember(Group group, String exec, String target, User user) {
applyAndSave(group, new AddMemberEvent(group, exec, target, user));
}
/**
* Erzeugt, speichert ein DeleteUserEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Mitglied ist und ob er der letzte Admin ist.
*/
public void deleteUser(User user, Group group) throws EventException {
ValidationHelper.throwIfNoMember(group, user);
ValidationHelper.throwIfLastAdmin(user, group);
public void deleteUser(Group group, String exec, String target) {
applyAndSave(group, new KickMemberEvent(group, exec, target));
if (ValidationHelper.checkIfGroupEmpty(group)) {
deleteGroup(user, group);
} else {
Event event = new DeleteUserEvent(group, user);
event.apply(group);
eventStoreService.saveEvent(event);
deleteGroup(group, target);
}
}
@ -194,14 +178,8 @@ public class GroupService {
* Erzeugt, speichert ein DeleteGroupEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Admin ist.
*/
public void deleteGroup(User user, Group group) {
ValidationHelper.throwIfNoAdmin(group, user);
Event event = new DeleteGroupEvent(group, user);
event.apply(group);
inviteService.destroyLink(group);
eventStoreService.saveEvent(event);
public void deleteGroup(Group group, String exec) {
applyAndSave(group, new DestroyGroupEvent(group, exec));
}
/**
@ -209,17 +187,8 @@ public class GroupService {
* Prüft, ob der Nutzer Admin ist und ob der Titel valide ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
public void updateTitle(User user, Group group, Title title) {
ValidationHelper.throwIfNoAdmin(group, user);
if (title.equals(group.getTitle())) {
return;
}
Event event = new UpdateGroupTitleEvent(group, user, title);
event.apply(group);
eventStoreService.saveEvent(event);
public void setTitle(Group group, String exec, Title title) {
applyAndSave(group, new SetTitleEvent(group, exec, title));
}
/**
@ -227,17 +196,8 @@ public class GroupService {
* Prüft, ob der Nutzer Admin ist und ob die Beschreibung valide ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
public void updateDescription(User user, Group group, Description description) {
ValidationHelper.throwIfNoAdmin(group, user);
if (description.equals(group.getDescription())) {
return;
}
Event event = new UpdateGroupDescriptionEvent(group, user, description);
event.apply(group);
eventStoreService.saveEvent(event);
public void setDescription(Group group, String exec, Description description) {
applyAndSave(group, new SetDescriptionEvent(group, exec, description));
}
/**
@ -245,17 +205,8 @@ public class GroupService {
* Prüft, ob der Nutzer Mitglied ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
private void updateRole(User user, Group group, Role role) {
ValidationHelper.throwIfNoMember(group, user);
if (role == group.getRoles().get(user.getUserid())) {
return;
}
Event event = new UpdateRoleEvent(group, user, role);
event.apply(group);
eventStoreService.saveEvent(event);
private void updateRole(Group group, String exec, String target, Role role) {
applyAndSave(group, new UpdateRoleEvent(group, exec, target, role));
}
/**
@ -263,20 +214,24 @@ public class GroupService {
* Prüft, ob der Nutzer Admin ist und ob das Limit valide ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
public void updateUserLimit(User user, Group group, Limit userLimit) {
ValidationHelper.throwIfNoAdmin(group, user);
public void setLimit(Group group, String exec, Limit userLimit) {
applyAndSave(group, new SetLimitEvent(group, exec, userLimit));
}
if (userLimit == group.getLimit()) {
return;
}
public void setParent(Group group, String exec, Parent parent) {
applyAndSave(group, new SetParentEvent(group, exec, parent));
}
Event event;
if (userLimit.getUserLimit() < group.getMembers().size()) {
event = new UpdateUserLimitEvent(group, user, new Limit(group.getMembers().size()));
} else {
event = new UpdateUserLimitEvent(group, user, userLimit);
}
public void setLink(Group group, String exec, Link link) {
applyAndSave(group, new SetInviteLinkEvent(group, exec, link));
}
private void setType(Group group, String exec, Type type) {
applyAndSave(group, new SetTypeEvent(group, exec, type));
}
private void applyAndSave(Group group, Event event) throws EventException {
event.init(group.version() + 1);
event.apply(group);
eventStoreService.saveEvent(event);

View File

@ -1,54 +0,0 @@
package mops.gruppen2.domain.service;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.InvalidInviteException;
import mops.gruppen2.domain.exception.NoInviteExistException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.persistance.InviteRepository;
import mops.gruppen2.persistance.dto.InviteLinkDTO;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@Log4j2
public class InviteService {
private final InviteRepository inviteRepository;
public InviteService(InviteRepository inviteRepository) {
this.inviteRepository = inviteRepository;
}
void createLink(Group group) {
inviteRepository.save(new InviteLinkDTO(null,
group.getGroupid().toString(),
UUID.randomUUID().toString()));
log.debug("Link wurde erzeugt! (Gruppe: {})", group.getGroupid());
}
void destroyLink(Group group) {
inviteRepository.deleteLinkOfGroup(group.getGroupid().toString());
log.debug("Link wurde zerstört! (Gruppe: {})", group.getGroupid());
}
public UUID getGroupIdFromLink(String link) {
try {
return UUID.fromString(inviteRepository.findGroupIdByLink(link));
} catch (Exception e) {
log.error("Gruppe zu Link ({}) konnte nicht gefunden werden!", link, e);
throw new InvalidInviteException(link);
}
}
public String getLinkByGroup(Group group) {
try {
return inviteRepository.findLinkByGroupId(group.getGroupid().toString());
} catch (Exception e) {
log.error("Link zu Gruppe ({}) konnte nicht gefunden werden!", group.getGroupid(), e);
throw new NoInviteExistException(group.getGroupid().toString());
}
}
}

View File

@ -1,37 +1,41 @@
package mops.gruppen2.domain.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.GroupNotFoundException;
import mops.gruppen2.domain.helper.IdHelper;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.helper.CommonHelper;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import static mops.gruppen2.domain.event.EventType.CREATEGROUP;
import static mops.gruppen2.domain.event.EventType.SETDESCRIPTION;
import static mops.gruppen2.domain.event.EventType.SETLIMIT;
import static mops.gruppen2.domain.event.EventType.SETTITLE;
import static mops.gruppen2.domain.helper.CommonHelper.eventTypesToString;
/**
* Liefert verschiedene Projektionen auf Gruppen.
* Benötigt ausschließlich den EventStoreService.
*/
@Service
@Log4j2
@RequiredArgsConstructor
@Service
public class ProjectionService {
private final EventStoreService eventStoreService;
public ProjectionService(EventStoreService eventStoreService) {
this.eventStoreService = eventStoreService;
}
// ################################## STATISCHE PROJEKTIONEN #################################
@ -121,11 +125,16 @@ public class ProjectionService {
@Cacheable("groups")
public List<Group> projectPublicGroups() throws EventException {
List<UUID> groupIds = eventStoreService.findExistingGroupIds();
if (groupIds.isEmpty()) {
return Collections.emptyList();
}
List<Event> events = eventStoreService.findEventsByGroupAndType(groupIds,
"CreateGroupEvent",
"UpdateGroupDescriptionEvent",
"UpdateGroupTitleEvent",
"UpdateUserMaxEvent");
eventTypesToString(CREATEGROUP,
SETDESCRIPTION,
SETTITLE,
SETLIMIT));
List<Group> groups = projectGroups(events);
@ -143,9 +152,14 @@ public class ProjectionService {
@Cacheable("groups")
public List<Group> projectLectures() {
List<UUID> groupIds = eventStoreService.findExistingGroupIds();
if (groupIds.isEmpty()) {
return Collections.emptyList();
}
List<Event> events = eventStoreService.findEventsByGroupAndType(groupIds,
"CreateGroupEvent",
"UpdateGroupTitleEvent");
eventTypesToString(CREATEGROUP,
SETTITLE));
List<Group> lectures = projectGroups(events);
@ -158,17 +172,22 @@ public class ProjectionService {
* Projiziert Gruppen, in welchen der User aktuell teilnimmt.
* Die Gruppen enthalten nur Metainformationen: Titel und Beschreibung.
*
* @param user Die Id
* @param userid Die Id
*
* @return Liste aus Gruppen
*/
@Cacheable("groups")
public List<Group> projectUserGroups(User user) {
List<UUID> groupIds = eventStoreService.findExistingUserGroups(user);
public List<Group> projectUserGroups(String userid) {
List<UUID> groupIds = eventStoreService.findExistingUserGroups(userid);
if (groupIds.isEmpty()) {
return Collections.emptyList();
}
List<Event> groupEvents = eventStoreService.findEventsByGroupAndType(groupIds,
"CreateGroupEvent",
"UpdateGroupTitleEvent",
"UpdateGroupDescriptionEvent");
eventTypesToString(CREATEGROUP,
SETTITLE,
SETDESCRIPTION));
return projectGroups(groupEvents);
}
@ -185,10 +204,6 @@ public class ProjectionService {
* @throws GroupNotFoundException Wenn die Gruppe nicht gefunden wird
*/
public Group projectSingleGroup(UUID groupId) throws GroupNotFoundException {
if (IdHelper.isEmpty(groupId)) {
throw new GroupNotFoundException(groupId + ": " + ProjectionService.class);
}
try {
List<Event> events = eventStoreService.findGroupEvents(groupId);
return projectSingleGroup(events);
@ -198,27 +213,28 @@ public class ProjectionService {
}
}
/**
* Projiziert eine einzelne Gruppe, welche leer sein darf.
*/
public Group projectParent(UUID parentId) {
if (IdHelper.isEmpty(parentId)) {
public Group projectParent(UUID parent) {
if (CommonHelper.uuidIsEmpty(parent)) {
return new Group();
}
return projectSingleGroup(parentId);
return projectSingleGroup(parent);
}
/**
* Entfernt alle Gruppen, in welchen ein User teilnimmt, aus einer Gruppenliste.
*
* @param groups Gruppenliste, aus der entfernt wird
* @param user User, welcher teilnimmt
* @param userid User, welcher teilnimmt
*/
void removeUserGroups(List<Group> groups, User user) {
List<UUID> userGroups = eventStoreService.findExistingUserGroups(user);
void removeUserGroups(List<Group> groups, String userid) {
List<UUID> userGroups = eventStoreService.findExistingUserGroups(userid);
groups.removeIf(group -> userGroups.contains(group.getGroupid()));
groups.removeIf(group -> userGroups.contains(group.getId()));
}
public Group projectGroupByLink(String link) {
return projectSingleGroup(eventStoreService.findGroupByLink(link));
}
}

View File

@ -2,9 +2,8 @@ package mops.gruppen2.domain.service;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.SortHelper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -33,10 +32,10 @@ public class SearchService {
* @throws EventException Projektionsfehler
*/
@Cacheable("groups")
public List<Group> searchPublicGroups(String search, User user) throws EventException {
public List<Group> searchPublicGroups(String search, String userid) {
List<Group> groups = projectionService.projectPublicGroups();
projectionService.removeUserGroups(groups, user);
sortByGroupType(groups);
projectionService.removeUserGroups(groups, userid);
SortHelper.sortByGroupType(groups);
if (search.isEmpty()) {
return groups;
@ -45,25 +44,8 @@ public class SearchService {
log.debug("Es wurde gesucht nach: {}", search);
return groups.stream()
.filter(group -> group.toString().toLowerCase().contains(search.toLowerCase()))
.filter(group -> group.format().toLowerCase().contains(search.toLowerCase()))
.collect(Collectors.toList());
}
/**
* Sortiert die übergebene Liste an Gruppen, sodass Veranstaltungen am Anfang der Liste sind.
*
* @param groups Die Liste von Gruppen die sortiert werden soll
*/
private static void sortByGroupType(List<Group> groups) {
groups.sort((Group g1, Group g2) -> {
if (g1.getType() == Type.LECTURE) {
return -1;
}
if (g2.getType() == Type.LECTURE) {
return 0;
}
return 1;
});
}
}

View File

@ -30,28 +30,28 @@ public interface EventRepository extends CrudRepository<EventDTO, Long> {
@Query("SELECT * FROM event"
+ " WHERE group_id IN (:userIds) ")
List<EventDTO> findEventDTOsByUser(@Param("groupIds") List<String> userIds);
List<EventDTO> findEventDTOsByUser(@Param("groupIds") String... userIds);
@Query("SELECT * FROM event"
+ " WHERE event_type IN (:types)")
List<EventDTO> findEventDTOsByType(@Param("types") List<String> types);
List<EventDTO> findEventDTOsByType(@Param("types") String... types);
@Query("SELECT * FROM event"
+ " WHERE event_type IN (:types) AND group_id IN (:groupIds)")
List<EventDTO> findEventDTOsByGroupAndType(@Param("types") List<String> types,
@Param("groupIds") List<String> groupIds);
List<EventDTO> findEventDTOsByGroupAndType(@Param("groupIds") List<String> groupIds,
@Param("types") String... types);
@Query("SELECT * FROM event"
+ " WHERE event_type IN (:types) AND user_id = :userId")
List<EventDTO> findEventDTOsByUserAndType(@Param("types") List<String> types,
@Param("userId") String userId);
List<EventDTO> findEventDTOsByUserAndType(@Param("userId") String userId,
@Param("types") String... types);
// ################################ LATEST EVENT DTOs ########################################
@Query("WITH ranked_events AS ("
+ "SELECT *, ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY event_id DESC) AS rn"
+ " FROM event"
+ " WHERE user_id = :userId AND event_type IN ('AddUserEvent', 'DeleteUserEvent')"
+ " WHERE user_id = :userId AND event_type IN ('ADDMEMBER', 'KICKMEMBER')"
+ ")"
+ "SELECT * FROM ranked_events WHERE rn = 1;")
List<EventDTO> findLatestEventDTOsPartitionedByGroupByUser(@Param("userId") String userId);
@ -62,7 +62,7 @@ public interface EventRepository extends CrudRepository<EventDTO, Long> {
+ " WHERE event_type IN (:types)"
+ ")"
+ "SELECT * FROM ranked_events WHERE rn = 1;")
List<EventDTO> findLatestEventDTOsPartitionedByGroupByType(@Param("types") List<String> types);
List<EventDTO> findLatestEventDTOsPartitionedByGroupByType(@Param("types") String... types);
// ######################################### COUNT ###########################################

View File

@ -1,22 +0,0 @@
package mops.gruppen2.persistance;
import mops.gruppen2.persistance.dto.InviteLinkDTO;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface InviteRepository extends CrudRepository<InviteLinkDTO, Long> {
@Query("SELECT group_id FROM invite WHERE invite_link = :link")
String findGroupIdByLink(@Param("link") String link);
@Modifying
@Query("DELETE FROM invite WHERE group_id = :group")
void deleteLinkOfGroup(@Param("group") String group);
@Query("SELECT invite_link FROM invite WHERE group_id = :group")
String findLinkByGroupId(@Param("group") String group);
}

View File

@ -12,8 +12,13 @@ public class EventDTO {
@Id
Long event_id;
String group_id;
String user_id;
long group_version;
String exec_user_id;
String target_user_id;
String event_type;
String event_payload;
}

View File

@ -1,17 +0,0 @@
package mops.gruppen2.persistance.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
@Table("invite")
@Getter
@AllArgsConstructor
public class InviteLinkDTO {
@Id
Long invite_id;
String group_id;
String invite_link;
}

View File

@ -7,9 +7,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.helper.APIHelper;
import mops.gruppen2.domain.helper.IdHelper;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.helper.CommonHelper;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.service.EventStoreService;
import mops.gruppen2.domain.service.ProjectionService;
import mops.gruppen2.web.api.GroupRequestWrapper;
@ -60,7 +59,7 @@ public class APIController {
public List<String> getApiUserGroups(@ApiParam("Nutzer-Id")
@PathVariable("id") String userId) {
return IdHelper.uuidsToString(eventStoreService.findExistingUserGroups(new User(userId)));
return CommonHelper.uuidsToString(eventStoreService.findExistingUserGroups(userId));
}
/**

View File

@ -4,16 +4,16 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.helper.CsvHelper;
import mops.gruppen2.domain.helper.IdHelper;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Description;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Limit;
import mops.gruppen2.domain.model.Title;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.domain.service.GroupService;
import mops.gruppen2.domain.service.ProjectionService;
import mops.gruppen2.web.form.CreateForm;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Controller;
@ -21,6 +21,8 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.security.RolesAllowed;
import javax.validation.Valid;
@ -49,27 +51,27 @@ public class GroupCreationController {
@PostMapping("/create")
@CacheEvict(value = "groups", allEntries = true)
public String postCreateOrga(KeycloakAuthenticationToken token,
@Valid CreateForm create,
@Valid Title title,
@Valid Description description,
@Valid Limit limit) {
@RequestParam("type") Type type,
@RequestParam("parent") @Valid Parent parent,
@RequestParam("title") @Valid Title title,
@RequestParam("description") @Valid Description description,
@RequestParam("limit") @Valid Limit limit,
@RequestParam(value = "file", required = false) MultipartFile file) {
// Zusätzlicher check: studentin kann keine lecture erstellen
ValidationHelper.validateCreateForm(token, create);
ValidationHelper.validateCreateForm(token, type);
User user = new User(token);
Group group = groupService.createGroup(user,
title,
description,
create.getType(),
limit,
create.getParent());
String principal = token.getName();
Group group = groupService.createGroup(principal);
groupService.initGroupMembers(group, principal, principal, new User(token), limit);
groupService.initGroupMeta(group, principal, type, parent);
groupService.initGroupText(group, principal, title, description);
// ROLE_studentin kann kein CSV importieren
if (token.getAccount().getRoles().contains("orga")) {
groupService.addUsersToGroup(CsvHelper.readCsvFile(create.getFile()), group, user);
groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file));
}
return "redirect:/gruppen2/details/" + IdHelper.uuidToString(group.getGroupid());
return "redirect:/gruppen2/details/" + group.getId();
}
}

View File

@ -4,15 +4,13 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.helper.CsvHelper;
import mops.gruppen2.domain.helper.IdHelper;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Description;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Limit;
import mops.gruppen2.domain.model.Title;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.domain.service.GroupService;
import mops.gruppen2.domain.service.InviteService;
import mops.gruppen2.domain.service.ProjectionService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.cache.annotation.CacheEvict;
@ -38,7 +36,6 @@ import java.util.UUID;
@RequestMapping("/gruppen2")
public class GroupDetailsController {
private final InviteService inviteService;
private final GroupService groupService;
private final ProjectionService projectionService;
@ -48,18 +45,17 @@ public class GroupDetailsController {
Model model,
@PathVariable("id") String groupId) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
// Parent Badge
UUID parentId = group.getParent();
Group parent = projectionService.projectParent(parentId);
Group parent = projectionService.projectParent(group.getParent());
model.addAttribute("group", group);
model.addAttribute("parent", parent);
// Detailseite für nicht-Mitglieder
if (!ValidationHelper.checkIfMember(group, user)) {
if (!ValidationHelper.checkIfMember(group, principal)) {
return "preview";
}
@ -72,14 +68,14 @@ public class GroupDetailsController {
public String postDetailsJoin(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
if (ValidationHelper.checkIfMember(group, user)) {
if (ValidationHelper.checkIfMember(group, principal)) {
return "redirect:/gruppen2/details/" + groupId;
}
groupService.addUser(user, group);
groupService.addMember(group, principal, principal, new User(token));
return "redirect:/gruppen2/details/" + groupId;
}
@ -90,12 +86,10 @@ public class GroupDetailsController {
public String postDetailsLeave(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
ValidationHelper.throwIfNoMember(group, user);
groupService.deleteUser(user, group);
groupService.deleteUser(group, principal, principal);
return "redirect:/gruppen2";
}
@ -107,15 +101,15 @@ public class GroupDetailsController {
HttpServletRequest request,
@PathVariable("id") String groupId) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
// Invite Link
String actualURL = request.getRequestURL().toString();
String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/"));
String link = serverURL + "gruppen2/join/" + inviteService.getLinkByGroup(group);
String link = serverURL + "gruppen2/join/" + group.getLink();
ValidationHelper.throwIfNoAdmin(group, user);
ValidationHelper.throwIfNoAdmin(group, principal);
model.addAttribute("group", group);
model.addAttribute("link", link);
@ -131,11 +125,11 @@ public class GroupDetailsController {
@Valid Title title,
@Valid Description description) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
groupService.updateTitle(user, group, title);
groupService.updateDescription(user, group, description);
groupService.setTitle(group, principal, title);
groupService.setDescription(group, principal, description);
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@ -146,10 +140,10 @@ public class GroupDetailsController {
public String postDetailsEditUserLimit(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@Valid Limit limit) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
groupService.updateUserLimit(user, group, limit);
groupService.setLimit(group, principal, limit);
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@ -161,10 +155,10 @@ public class GroupDetailsController {
@PathVariable("id") String groupId,
@RequestParam(value = "file", required = false) MultipartFile file) {
User user = new User(token);
Group group = projectionService.projectSingleGroup(IdHelper.stringToUUID(groupId));
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
groupService.addUsersToGroup(CsvHelper.readCsvFile(file), group, user);
groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file));
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@ -174,17 +168,17 @@ public class GroupDetailsController {
@CacheEvict(value = "groups", allEntries = true)
public String postDetailsEditRole(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@PathVariable("userid") String userId) {
@PathVariable("userid") String target) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
ValidationHelper.throwIfNoAdmin(group, user);
ValidationHelper.throwIfNoAdmin(group, principal);
groupService.toggleMemberRole(new User(userId), group);
groupService.toggleMemberRole(group, principal, target);
// Falls sich der User selbst die Rechte genommen hat
if (!ValidationHelper.checkIfAdmin(group, user)) {
if (!ValidationHelper.checkIfAdmin(group, principal)) {
return "redirect:/gruppen2/details/" + groupId;
}
@ -196,16 +190,16 @@ public class GroupDetailsController {
@CacheEvict(value = "groups", allEntries = true)
public String postDetailsEditDelete(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@PathVariable("userid") String userId) {
@PathVariable("userid") String target) {
User user = new User(token);
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
ValidationHelper.throwIfNoAdmin(group, user);
ValidationHelper.throwIfNoAdmin(group, principal);
// Der eingeloggte User kann sich nicht selbst entfernen (er kann aber verlassen)
if (!userId.equals(user.getUserid())) {
groupService.deleteUser(new User(userId), group);
if (!principal.equals(target)) {
groupService.deleteUser(group, principal, target);
}
return "redirect:/gruppen2/details/" + groupId + "/edit";
@ -215,12 +209,12 @@ public class GroupDetailsController {
@PostMapping("/details/{id}/edit/destroy")
@CacheEvict(value = "groups", allEntries = true)
public String postDetailsEditDestroy(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId) {
@PathVariable("id") String groupid) {
User user = new User(token);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
String principal = token.getName();
Group group = projectionService.projectSingleGroup(UUID.fromString(groupid));
groupService.deleteGroup(user, group);
groupService.deleteGroup(group, principal);
return "redirect:/gruppen2";
}

View File

@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCall;
import mops.gruppen2.domain.exception.PageNotFoundException;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.service.ProjectionService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.stereotype.Controller;
@ -35,9 +34,8 @@ public class GruppenfindungController {
public String getIndexPage(KeycloakAuthenticationToken token,
Model model) {
User user = new User(token);
model.addAttribute("groups", projectionService.projectUserGroups(user));
String principal = token.getName();
model.addAttribute("groups", projectionService.projectUserGroups(principal));
return "index";
}

View File

@ -1,9 +1,9 @@
package mops.gruppen2.web;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.model.Role;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Role;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ -20,15 +20,15 @@ public class ModelAttributeControllerAdvice {
// Prevent NullPointerException if not logged in
if (token != null) {
model.addAttribute("account", new Account(token));
model.addAttribute("user", new User(token));
model.addAttribute("principal", new User(token));
}
// Add enums
model.addAttribute("member", Role.MEMBER);
model.addAttribute("admin", Role.ADMIN);
model.addAttribute("public", Type.PUBLIC);
model.addAttribute("private", Type.PRIVATE);
model.addAttribute("lecture", Type.LECTURE);
model.addAttribute("REGULAR", Role.REGULAR);
model.addAttribute("ADMIN", Role.ADMIN);
model.addAttribute("PUBLIC", Type.PUBLIC);
model.addAttribute("PRIVATE", Type.PRIVATE);
model.addAttribute("LECTURE", Type.LECTURE);
}
}

View File

@ -4,10 +4,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.helper.ValidationHelper;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.service.InviteService;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.service.ProjectionService;
import mops.gruppen2.domain.service.SearchService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
@ -31,7 +29,6 @@ import java.util.List;
@RequestMapping("/gruppen2")
public class SearchAndInviteController {
private final InviteService inviteService;
private final ProjectionService projectionService;
private final SearchService searchService;
@ -50,8 +47,8 @@ public class SearchAndInviteController {
Model model,
@RequestParam("string") String search) {
User user = new User(token);
List<Group> groups = searchService.searchPublicGroups(search, user);
String principal = token.getName();
List<Group> groups = searchService.searchPublicGroups(search, principal);
model.addAttribute("groups", groups);
@ -64,19 +61,19 @@ public class SearchAndInviteController {
Model model,
@PathVariable("link") String link) {
User user = new User(token);
Group group = projectionService.projectSingleGroup(inviteService.getGroupIdFromLink(link));
String principal = token.getName();
Group group = projectionService.projectGroupByLink(link);
model.addAttribute("group", group);
// Gruppe öffentlich
if (group.getType() == Type.PUBLIC) {
return "redirect:/gruppen2/details/" + group.getGroupid();
return "redirect:/gruppen2/details/" + group.getId();
}
// Bereits Mitglied
if (ValidationHelper.checkIfMember(group, user)) {
return "redirect:/gruppen2/details/" + group.getGroupid();
if (ValidationHelper.checkIfMember(group, principal)) {
return "redirect:/gruppen2/details/" + group.getId();
}
return "link";

View File

@ -2,7 +2,7 @@ package mops.gruppen2.web.api;
import lombok.AllArgsConstructor;
import lombok.Getter;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.group.Group;
import java.util.List;

View File

@ -1,29 +0,0 @@
package mops.gruppen2.web.form;
import lombok.Data;
import mops.gruppen2.domain.helper.IdHelper;
import mops.gruppen2.domain.model.Type;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotBlank;
import java.util.UUID;
@Data
public class CreateForm {
@NotBlank
String type;
@NotBlank
String parent;
MultipartFile file;
public Type getType() {
return Type.valueOf(type);
}
public UUID getParent() {
return getType() == Type.LECTURE ? IdHelper.emptyUUID() : IdHelper.stringToUUID(parent);
}
}

View File

@ -2,7 +2,7 @@
logging.application.name = gruppen2
logging.pattern.console = [${logging.application.name}], %magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS},\t%blue(%msg)\t%thread,%logger.%M%n
spring.output.ansi.enabled = always
logging.level.mops.gruppen2 = info
logging.level.mops.gruppen2 = trace
logging.level.org.springframework.jdbc.core = info
# Database

View File

@ -2,18 +2,11 @@ DROP TABLE IF EXISTS event;
CREATE TABLE event
(
event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
user_id VARCHAR(50),
event_type VARCHAR(32),
event_payload VARCHAR(2500)
);
DROP TABLE IF EXISTS invite;
CREATE TABLE invite
(
invite_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
invite_link VARCHAR(36) NOT NULL
event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
group_version INT NOT NULL,
exec_user_id VARCHAR(50) NOT NULL,
target_user_id VARCHAR(50),
event_type VARCHAR(32) NOT NULL,
event_payload VARCHAR(2500) NOT NULL
);

View File

@ -27,7 +27,7 @@
<!--Spacer-->
<span class="col"></span>
<form method="post" th:action="@{/gruppen2/details/{id}/leave(id=${group.getGroupid()})}">
<form method="post" th:action="@{/gruppen2/details/{id}/leave(id=${group.getId()})}">
<button class="btn btn-danger btn-bar" type="submit">Gruppe verlassen
</button>
</form>
@ -40,13 +40,13 @@
<!--Anzahl Text-->
<div class="mb-2">
<span>Teilnehmer: </span>
<span th:text="${group.getMembers().size() + ' von ' + group.getLimit().getUserLimit()}"></span>
<span th:text="${group.size() + ' von ' + group.getLimit()}"></span>
</div>
<!--Bearbeiten-Button-->
<div class="mb-2" th:if="${group.getRoles().get(user.getUserid()) == admin}">
<div class="mb-2" th:if="${group.isAdmin(user.getId())}">
<form method="get"
th:action="@{/gruppen2/details/{id}/edit(id=${group.getGroupid()})}">
th:action="@{/gruppen2/details/{id}/edit(id=${group.getId()})}">
<button class="btn btn-secondary btn-block">Gruppe verwalten</button>
</form>
</div>
@ -55,8 +55,8 @@
<div class="members">
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between"
th:each="member : ${group.getMembers().values()}">
<span th:text="${member.getGivenname() + ' ' + member.getFamilyname().charAt(0) + '.'}"></span>
th:each="member : ${group.getMembers()}">
<span th:text="${member}"></span>
<span th:replace="~{fragments/groups :: userbadges}"></span>
</li>
</ul>

View File

@ -15,14 +15,14 @@
<!--Fertig oder löschen-->
<div class="content">
<div class="row">
<form method="get" th:action="@{/gruppen2/details/{id}(id=${group.getGroupid()})}">
<form method="get" th:action="@{/gruppen2/details/{id}(id=${group.getId()})}">
<button class="btn btn-primary">Fertig</button>
</form>
<!--Spacer-->
<span class="col"></span>
<form method="post" th:action="@{/gruppen2/details/{id}/edit/destroy(id=${group.getGroupid()})}">
<form method="post" th:action="@{/gruppen2/details/{id}/edit/destroy(id=${group.getId()})}">
<button class="btn btn-danger btn-bar" type="submit">Gruppe löschen
</button>
</form>
@ -50,7 +50,7 @@
<!--Beschreibung + Titel-->
<div class="content-text">
<form th:action="@{/gruppen2/details/{id}/edit/meta(id=${group.getGroupid()})}"
<form th:action="@{/gruppen2/details/{id}/edit/meta(id=${group.getId()})}"
method="post">
<div th:replace="~{fragments/forms :: meta}"></div>
@ -63,7 +63,7 @@
<!--Userlimit-->
<div class="content-text-in">
<form th:action="@{/gruppen2/details/{id}/edit/userlimit(id=${group.getGroupid()})}"
<form th:action="@{/gruppen2/details/{id}/edit/userlimit(id=${group.getId()})}"
method="post">
<div th:replace="~{fragments/forms :: userlimit}"></div>
@ -76,7 +76,7 @@
<!--CSV Import-->
<div class="content-text mb-0">
<form th:action="@{/gruppen2/details/{id}/edit/csv(id=${group.getGroupid()})}"
<form th:action="@{/gruppen2/details/{id}/edit/csv(id=${group.getId()})}"
th:if="${account.getRoles().contains('orga')}"
enctype="multipart/form-data" method="post">
@ -96,20 +96,20 @@
</div>
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between" th:each="member : ${group.getMembers().values()}">
<li class="list-group-item d-flex justify-content-between" th:each="member : ${group.getMembers()}">
<div>
<span th:text="${member.getGivenname() + ' ' + member.getFamilyname().charAt(0) + '.'}"></span>
<span th:replace="~{fragments/groups :: userbadges}"></span>
</div>
<div class="d-flex">
<form th:action="@{/gruppen2/details/{id}/edit/delete/{userid}(id=${group.getGroupid()}, userid=${member.getUserid()})}"
th:unless="${member.getUserid() == account.getName()}"
<form th:action="@{/gruppen2/details/{id}/edit/delete/{userid}(id=${group.getId()}, userid=${member.getId()})}"
th:unless="${member.getId() == account.getName()}"
method="post">
<button type="submit" class="btn btn-danger mr-2">Entfernen</button>
</form>
<form th:action="@{/gruppen2/details/{id}/edit/role/{userid}(id=${group.getGroupid()}, userid=${member.getUserid()})}"
<form th:action="@{/gruppen2/details/{id}/edit/role/{userid}(id=${group.getId()}, userid=${member.getId()})}"
method="post">
<button type="submit" class="btn btn-warning">Rolle ändern</button>
</form>

View File

@ -10,7 +10,7 @@
<div class="input-group-prepend">
<span class="input-group-text text-monospace">Gruppentitel:</span>
</div>
<input type="text" class="form-control" name="groupTitle" minlength="4" maxlength="128"
<input type="text" class="form-control" name="title" minlength="4" maxlength="128"
th:value="${group?.getTitle()}" required>
</div>
@ -20,8 +20,8 @@
<div class="input-group-prepend">
<span class="input-group-text text-monospace">Beschreibung:</span>
</div>
<textarea class="form-control" name="groupDescription" minlength="4" maxlength="512"
th:text="${group?.getDescription()?.getGroupDescription()}" required></textarea>
<textarea class="form-control" name="description" minlength="4" maxlength="512"
th:text="${group?.getDescription()}" required></textarea>
</div>
</th:block>
@ -50,7 +50,7 @@
<input type="hidden" id="parentdummy" name="parent" value="00000000-0000-0000-0000-000000000000" disabled>
<select class="custom-select" id="parentselect" name="parent">
<option value="00000000-0000-0000-0000-000000000000" selected>Keiner</option>
<option th:each="lecture : ${lectures}" th:value="${lecture.getGroupid()}"
<option th:each="lecture : ${lectures}" th:value="${lecture.getId()}"
th:text="${lecture.getTitle()}"></option>
</select>
</div>
@ -61,17 +61,17 @@
<th:block th:fragment="userlimit">
<label for="userlimit">Teilnehmeranzahl:</label>
<div class="btn-toolbar row mx-0" id="userlimit">
<input type="hidden" name="userLimit" id="limit" value="999999"
th:disabled="${group != null && group.getLimit().getUserLimit() < 999999} ? 'disabled' : 'false'">
<input type="hidden" name="limit" id="limit" value="999999"
th:disabled="${group != null && group.getLimit() < 999999} ? 'disabled' : 'false'">
<div class="btn-group btn-group-toggle col-sm-4 px-0" data-toggle="buttons">
<label class="btn btn-secondary active" onclick="disable('#limitselect'); enable('#limit')">
<input type="radio"
th:checked="${group != null && group.getLimit().getUserLimit() < 999999} ? 'false' : 'checked'">
th:checked="${group != null && group.getLimit() < 999999} ? 'false' : 'checked'">
Unbegrenzt
</label>
<label class="btn btn-secondary" onclick="enable('#limitselect'); disable('#limit')">
<input type="radio"
th:checked="${group != null && group.getLimit().getUserLimit() < 999999} ? 'checked' : 'false'">
th:checked="${group != null && group.getLimit() < 999999} ? 'checked' : 'false'">
Begrenzt
</label>
</div>
@ -81,10 +81,10 @@
<div class="input-group-prepend">
<span class="input-group-text text-monospace">Limit:</span>
</div>
<input type="number" class="form-control" name="userLimit"
th:value="${group != null} ? ${group.getLimit().getUserLimit()} : '999999'"
<input type="number" class="form-control" name="limit"
th:value="${group != null} ? ${group.getLimit()} : '999999'"
min="1" max="999999" id="limitselect" required
th:disabled="${group != null && group.getLimit().getUserLimit() < 999999} ? 'false' : 'disabled'">
th:disabled="${group != null && group.getLimit() < 999999} ? 'false' : 'disabled'">
<div class="input-group-append">
<span class="input-group-text text-monospace">Teilnehmer</span>
</div>
@ -99,7 +99,7 @@
<span class="input-group-text text-monospace">CSV:</span>
</div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="file" th:name="file">
<input type="file" class="custom-file-input" id="file" name="file">
<label class="custom-file-label" for="file">Datei auswählen</label>
</div>
<script>

View File

@ -1,21 +1,20 @@
<!DOCTYPE HTML>
<!--suppress ALL -->
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<!--Grouptype Badges-->
<th:block th:fragment="badges">
<span class="badge badge-pill private"
title="Kann nicht über die Suche gefunden werden, beitritt ist per Einladungslink möglich."
th:if='${group.getType() == private}'>Privat</span>
th:if='${group.isPrivate()}'>Privat</span>
<span class="badge badge-pill public"
title="Kann über die Suche gefunden werden, jeder kann beitreten."
th:if="${group.getType() == public}">Öffentlich</span>
th:if="${group.isPublic()}">Öffentlich</span>
<span class="badge badge-pill lecture"
title="Offizielle Veranstaltung"
th:if='${group.getType() == lecture}'>Veranstaltung</span>
th:if='${group.isLecture()}'>Veranstaltung</span>
<span class="badge badge-pill parent"
th:unless="${parent == null || parent?.getTitle() == null|| parent?.getTitle() == ''}"
th:if="${parent != null && parent?.getTitle() != null && parent?.getTitle() != ''}"
th:title="${'Die Gruppe gehört zur Veranstaltung ' + parent.getTitle() + '.'}"
th:text="${parent.getTitle()}">Parent</span>
@ -28,7 +27,7 @@
<!--User Badges-->
<th:block th:fragment="userbadges">
<span class="badge badge-success align-self-start ml-2"
th:if="${group.getRoles().get(member.getUserid()) == admin}">Admin</span>
th:if="${group.isAdmin(member.getUserid())}">Admin</span>
</th:block>
<th:block th:fragment="groupcontent">
@ -39,30 +38,30 @@
<!--Description-->
<div class="content-text-in">
<span th:text="${group.getDescription().getGroupDescription()}"></span>
<span th:text="${group.getDescription()}"></span>
</div>
<!--<div class="content-text-in" th:if="${group.getMembers().contains(user.getId())}"></div>-->
<!--<div class="body-text-in" th:if="${group.getMembers().contains(user.getId())}"></div>-->
</th:block>
<!--Buttonbar zum Gruppe beitreten-->
<th:block th:fragment="joingroup">
<div class="content-heading">
<span th:if="${group.getMembers().size() < group.getLimit().getUserLimit()}">
<span th:unless="${group.isFull()}">
Möchtest du dieser Gruppe beitreten?
</span>
<span th:unless="${group.getMembers().size() < group.getLimit().getUserLimit()}">
<span th:if="${group.isFull()}">
Diese Gruppe hat ihre maximale Teilnehmeranzahl erreicht.
</span>
</div>
<div class="row">
<form method="post" th:action="@{/gruppen2/details/{id}/join(id = ${group.getGroupId()})}"
th:if="${group.getMembers().size() < group.getLimit().getUserLimit()}">
th:unless="${group.isFull()}">
<button class="btn btn-success" type="submit">Gruppe beitreten.</button>
</form>
<div class="col" th:if="${group.getMembers().size() < group.getLimit().getUserLimit()}"></div>
<div class="col" th:unless="${group.isFull()}"></div>
<a class="btn btn-primary" href="/gruppen2"
type="submit">Startseite.</a>

View File

@ -16,13 +16,13 @@
<!--Gruppenliste belegte Gruppen-->
<div class="content" th:each="group: ${groups}">
<div class="content-heading row">
<a class="link col" th:href="@{/gruppen2/details/{id}(id=${group.getGroupid()})}"
<a class="link col" th:href="@{/gruppen2/details/{id}(id=${group.getId()})}"
th:text="${group.getTitle()}"></a>
<span th:replace="~{fragments/groups :: badges}"></span>
</div>
<div class="content-text-in">
<span th:text="${group.getDescription().getGroupDescription()}"></span>
<span th:text="${group.getDescription()}"></span>
</div>
</div>

View File

@ -14,10 +14,10 @@
<div class="content" th:insert="~{fragments/groups :: groupcontent}"></div>
<div class="content" th:unless="${group.getType() == private}"
<div class="content" th:unless="${group.isPrivate()}"
th:insert="~{fragments/groups :: joingroup}"></div>
<div class="content row" th:if="${group.getType() == private}">
<div class="content row" th:if="${group.isPrivate()}">
<span class="col"></span>
<a class="btn btn-primary" href="/gruppen2">Startseite.</a>

View File

@ -31,11 +31,11 @@
<div class="content-heading row">
<span th:replace="~{fragments/groups :: badges}"></span>
<a class="link col" th:href="@{/gruppen2/details/{id}(id=${group.getGroupid()})}"
<a class="link col" th:href="@{/gruppen2/details/{id}(id=${group.getId()})}"
th:text="${group.getTitle()}"></a>
</div>
<div class="content-text-in">
<span th:text="${group.getDescription().getGroupDescription()}"></span>
<span th:text="${group.getDescription()}"></span>
</div>
</div>

View File

@ -2,18 +2,17 @@ package mops.gruppen2;
import com.github.javafaker.Faker;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.DeleteGroupEvent;
import mops.gruppen2.domain.event.DeleteUserEvent;
import mops.gruppen2.domain.event.AddMemberEvent;
import mops.gruppen2.domain.event.DestroyGroupEvent;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.event.UpdateGroupDescriptionEvent;
import mops.gruppen2.domain.event.UpdateGroupTitleEvent;
import mops.gruppen2.domain.event.KickMemberEvent;
import mops.gruppen2.domain.event.SetDescriptionEvent;
import mops.gruppen2.domain.event.SetLimitEvent;
import mops.gruppen2.domain.event.SetTitleEvent;
import mops.gruppen2.domain.event.UpdateRoleEvent;
import mops.gruppen2.domain.event.UpdateUserLimitEvent;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Role;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Role;
import mops.gruppen2.domain.model.group.Type;
import java.util.ArrayList;
import java.util.Collection;
@ -93,7 +92,7 @@ public class TestBuilder {
eventList.add(createPublicGroupEvent(groupId));
eventList.add(updateGroupTitleEvent(groupId));
eventList.add(updateGroupDescriptionEvent(groupId));
eventList.add(new UpdateUserLimitEvent(groupId, "fgsadggas", Long.MAX_VALUE));
eventList.add(new SetLimitEvent(groupId, "fgsadggas", Long.MAX_VALUE));
eventList.addAll(addUserEvents(membercount, groupId));
return eventList;
@ -106,7 +105,7 @@ public class TestBuilder {
eventList.add(createPrivateGroupEvent(groupId));
eventList.add(updateGroupTitleEvent(groupId));
eventList.add(updateGroupDescriptionEvent(groupId));
eventList.add(new UpdateUserLimitEvent(groupId, "fgsadggas", Long.MAX_VALUE));
eventList.add(new SetLimitEvent(groupId, "fgsadggas", Long.MAX_VALUE));
eventList.addAll(addUserEvents(membercount, groupId));
return eventList;
@ -207,7 +206,7 @@ public class TestBuilder {
String firstname = firstname();
String lastname = lastname();
return new AddUserEvent(
return new AddMemberEvent(
groupId,
userId,
firstname,
@ -224,13 +223,13 @@ public class TestBuilder {
public static List<Event> deleteUserEvents(int count, List<Event> eventList) {
List<Event> removeEvents = new ArrayList<>();
List<Event> shuffle = eventList.parallelStream()
.filter(event -> event instanceof AddUserEvent)
.filter(event -> event instanceof AddMemberEvent)
.collect(Collectors.toList());
Collections.shuffle(shuffle);
for (Event event : shuffle) {
removeEvents.add(new DeleteUserEvent(event.getGroupid(), event.getUserid()));
removeEvents.add(new KickMemberEvent(event.getGroupid(), event.getTarget()));
if (removeEvents.size() >= count) {
break;
@ -248,13 +247,13 @@ public class TestBuilder {
* @return Eventliste
*/
public static List<Event> deleteUserEvents(Group group) {
return group.getMembers().parallelStream()
return group.getMemberships().parallelStream()
.map(user -> deleteUserEvent(group.getGroupid(), user.getUserId()))
.collect(Collectors.toList());
}
public static Event deleteUserEvent(UUID groupId, String userId) {
return new DeleteUserEvent(
return new KickMemberEvent(
groupId,
userId
);
@ -265,7 +264,7 @@ public class TestBuilder {
}
public static Event updateGroupDescriptionEvent(UUID groupId, String description) {
return new UpdateGroupDescriptionEvent(
return new SetDescriptionEvent(
groupId,
faker.random().hex(),
description
@ -277,7 +276,7 @@ public class TestBuilder {
}
public static Event updateGroupTitleEvent(UUID groupId, String title) {
return new UpdateGroupTitleEvent(
return new SetTitleEvent(
groupId,
faker.random().hex(),
title
@ -285,7 +284,7 @@ public class TestBuilder {
}
public static Event updateUserLimitMaxEvent(UUID groupId) {
return new UpdateUserLimitEvent(groupId, firstname(), Long.MAX_VALUE);
return new SetLimitEvent(groupId, firstname(), Long.MAX_VALUE);
}
public static Event updateRoleEvent(UUID groupId, String userId, Role role) {
@ -297,7 +296,7 @@ public class TestBuilder {
}
public static Event deleteGroupEvent(UUID groupId) {
return new DeleteGroupEvent(groupId, faker.random().hex());
return new DestroyGroupEvent(groupId, faker.random().hex());
}
private static String firstname() {

View File

@ -2,7 +2,7 @@ package mops.gruppen2.domain.event;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.UserAlreadyExistsException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.group.Group;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.addUserEvent;
@ -10,23 +10,22 @@ import static mops.gruppen2.TestBuilder.apply;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.updateUserLimitMaxEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class AddUserEventTest {
class AddMemberEventTest {
@Test
void applyEvent() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEvent = new AddUserEvent(uuidMock(0), "A", "Thomas", "Tom", "tho@mail.de");
Event addEvent = new AddMemberEvent(uuidMock(0), "A", "Thomas", "Tom", "tho@mail.de");
Group group = apply(createEvent, updateLimitEvent, addEvent);
assertThat(group.getMembers()).hasSize(1);
assertThat(group.getMembers().get(0).getGivenname()).isEqualTo("Thomas");
assertThat(group.getMembers().get(0).getFamilyname()).isEqualTo("Tom");
assertThat(group.getMembers().get(0).getEmail()).isEqualTo("tho@mail.de");
assertThat(group.getMemberships()).hasSize(1);
assertThat(group.getMemberships().get(0).getGivenname()).isEqualTo("Thomas");
assertThat(group.getMemberships().get(0).getFamilyname()).isEqualTo("Tom");
assertThat(group.getMemberships().get(0).getEmail()).isEqualTo("tho@mail.de");
}
@Test
@ -41,13 +40,13 @@ class AddUserEventTest {
assertThrows(UserAlreadyExistsException.class, () -> addEventA.apply(group));
assertThrows(UserAlreadyExistsException.class, () -> addEventC.apply(group));
assertThat(group.getMembers()).hasSize(2);
assertThat(group.getMemberships()).hasSize(2);
}
@Test
void applyEvent_groupFull() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event maxSizeEvent = new UpdateUserLimitEvent(uuidMock(0), "A", 2L);
Event maxSizeEvent = new SetLimitEvent(uuidMock(0), "A", 2L);
Event addEventA = addUserEvent(uuidMock(0), "A");
Event addEventB = addUserEvent(uuidMock(0), "B");
Event addEventC = addUserEvent(uuidMock(0), "C");
@ -55,6 +54,6 @@ class AddUserEventTest {
Group group = apply(createEvent, maxSizeEvent, addEventA, addEventB);
assertThrows(GroupFullException.class, () -> addEventC.apply(group));
assertThat(group.getMembers()).hasSize(2);
assertThat(group.getMemberships()).hasSize(2);
}
}

View File

@ -1,8 +1,8 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.uuidMock;
@ -19,7 +19,7 @@ class CreateGroupEventTest {
Group group = TestBuilder.apply(createEvent);
assertThat(group.getMembers()).hasSize(0);
assertThat(group.getMemberships()).hasSize(0);
assertThat(group.getType()).isEqualTo(Type.PUBLIC);
assertThat(group.getGroupid()).isEqualTo(uuidMock(0));
assertThat(group.getParent()).isEqualTo(uuidMock(1));

View File

@ -1,14 +1,14 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
class DeleteGroupEventTest {
class DestroyGroupEventTest {
@Test
void applyEvent() {
@ -16,11 +16,11 @@ class DeleteGroupEventTest {
"A",
uuidMock(1),
Type.PUBLIC);
Event deleteEvent = new DeleteGroupEvent(uuidMock(0), "A");
Event deleteEvent = new DestroyGroupEvent(uuidMock(0), "A");
Group group = TestBuilder.apply(createEvent, deleteEvent);
assertThat(group.getMembers()).isEmpty();
assertThat(group.getMemberships()).isEmpty();
assertThat(group.getType()).isEqualTo(null);
assertThat(group.getLimit()).isEqualTo(0);
assertThat(group.getGroupid()).isEqualTo(uuidMock(0));

View File

@ -1,8 +1,8 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.exception.GroupIdMismatchException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.model.group.Group;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
@ -18,7 +18,7 @@ class EventTest {
Group group = TestBuilder.apply(createEvent);
assertThrows(GroupIdMismatchException.class, () -> addEvent.apply(group));
assertThrows(IdMismatchException.class, () -> addEvent.apply(group));
}
}

View File

@ -2,28 +2,27 @@ package mops.gruppen2.domain.event;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.group.Group;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.addUserEvent;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.updateUserLimitMaxEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class DeleteUserEventTest {
class KickMemberEventTest {
@Test
void applyEvent() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEvent = addUserEvent(uuidMock(0), "A");
Event deleteEvent = new DeleteUserEvent(uuidMock(0), "A");
Event deleteEvent = new KickMemberEvent(uuidMock(0), "A");
Group group = TestBuilder.apply(createEvent, updateLimitEvent, addEvent, deleteEvent);
assertThat(group.getMembers()).hasSize(0);
assertThat(group.getMemberships()).hasSize(0);
}
@Test
@ -31,11 +30,11 @@ class DeleteUserEventTest {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEvent = addUserEvent(uuidMock(0), "A");
Event deleteEvent = new DeleteUserEvent(uuidMock(0), "B");
Event deleteEvent = new KickMemberEvent(uuidMock(0), "B");
Group group = TestBuilder.apply(createEvent, updateLimitEvent, addEvent);
assertThrows(UserNotFoundException.class, () -> deleteEvent.apply(group));
assertThat(group.getMembers()).hasSize(1);
assertThat(group.getMemberships()).hasSize(1);
}
}

View File

@ -1,21 +1,20 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.model.group.Group;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class UpdateGroupDescriptionEventTest {
class SetDescriptionEventTest {
@Test
void applyEvent() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEvent = new UpdateGroupDescriptionEvent(uuidMock(0), "A", "desc.");
Event updateEvent = new SetDescriptionEvent(uuidMock(0), "A", "desc.");
Group group = TestBuilder.apply(createEvent, updateEvent);
@ -25,10 +24,10 @@ class UpdateGroupDescriptionEventTest {
@Test
void applyEvent_badDescription() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEventA = new UpdateGroupDescriptionEvent(uuidMock(0), "A", "");
Event updateEventA = new SetDescriptionEvent(uuidMock(0), "A", "");
Group group = TestBuilder.apply(createEvent);
assertThrows(BadParameterException.class, () -> updateEventA.apply(group));
assertThrows(BadArgumentException.class, () -> updateEventA.apply(group));
}
}

View File

@ -1,22 +1,21 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.model.group.Group;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.addUserEvent;
import static mops.gruppen2.TestBuilder.apply;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class UpdateUserLimitEventTest {
class SetLimitEventTest {
@Test
void applyEvent() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEvent = new UpdateUserLimitEvent(uuidMock(0), "A", 5L);
Event updateEvent = new SetLimitEvent(uuidMock(0), "A", 5L);
Group group = apply(createEvent, updateEvent);
@ -26,24 +25,24 @@ class UpdateUserLimitEventTest {
@Test
void applyEvent_badParameter_negative() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEvent = new UpdateUserLimitEvent(uuidMock(0), "A", -5L);
Event updateEvent = new SetLimitEvent(uuidMock(0), "A", -5L);
Group group = apply(createEvent);
assertThrows(BadParameterException.class, () -> updateEvent.apply(group));
assertThrows(BadArgumentException.class, () -> updateEvent.apply(group));
}
@Test
void applyEvent_badParameter_tooSmall() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEventA = new UpdateUserLimitEvent(uuidMock(0), "A", 5L);
Event updateEventA = new SetLimitEvent(uuidMock(0), "A", 5L);
Event addEventA = addUserEvent(uuidMock(0));
Event addEventB = addUserEvent(uuidMock(0));
Event addEventC = addUserEvent(uuidMock(0));
Event updateEventB = new UpdateUserLimitEvent(uuidMock(0), "A", 2L);
Event updateEventB = new SetLimitEvent(uuidMock(0), "A", 2L);
Group group = apply(createEvent, updateEventA, addEventA, addEventB, addEventC);
assertThrows(BadParameterException.class, () -> updateEventB.apply(group));
assertThrows(BadArgumentException.class, () -> updateEventB.apply(group));
}
}

View File

@ -1,21 +1,20 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.model.group.Group;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class UpdateGroupTitleEventTest {
class SetTitleEventTest {
@Test
void applyEvent() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEvent = new UpdateGroupTitleEvent(uuidMock(0), "A", "title.");
Event updateEvent = new SetTitleEvent(uuidMock(0), "A", "title.");
Group group = TestBuilder.apply(createEvent, updateEvent);
@ -25,10 +24,10 @@ class UpdateGroupTitleEventTest {
@Test
void applyEvent_badDescription() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateEventA = new UpdateGroupTitleEvent(uuidMock(0), "A", "");
Event updateEventA = new SetTitleEvent(uuidMock(0), "A", "");
Group group = TestBuilder.apply(createEvent);
assertThrows(BadParameterException.class, () -> updateEventA.apply(group));
assertThrows(BadArgumentException.class, () -> updateEventA.apply(group));
}
}

View File

@ -1,8 +1,8 @@
package mops.gruppen2.domain.event;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Role;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Role;
import org.junit.jupiter.api.Test;
import static mops.gruppen2.TestBuilder.addUserEvent;
@ -10,7 +10,6 @@ import static mops.gruppen2.TestBuilder.apply;
import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.updateUserLimitMaxEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
class UpdateRoleEventTest {

View File

@ -2,7 +2,7 @@ package mops.gruppen2.domain.service;
import mops.gruppen2.Gruppen2Application;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.persistance.EventRepository;
import mops.gruppen2.persistance.dto.EventDTO;
import org.junit.jupiter.api.BeforeEach;
@ -73,7 +73,7 @@ class EventStoreServiceTest {
EventDTO dto = EventStoreService.getDTOFromEvent(event);
assertThat(dto.getGroup_id()).isEqualTo(event.getGroupid().toString());
assertThat(dto.getUser_id()).isEqualTo(event.getUserid());
assertThat(dto.getUser_id()).isEqualTo(event.getTarget());
assertThat(dto.getEvent_id()).isEqualTo(null);
assertThat(dto.getEvent_type()).isEqualTo("CreateGroupEvent");
}

View File

@ -3,9 +3,9 @@ package mops.gruppen2.domain.service;
import mops.gruppen2.Gruppen2Application;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.model.Group;
import mops.gruppen2.domain.model.Type;
import mops.gruppen2.domain.model.User;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.persistance.EventRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@ -82,7 +82,7 @@ class GroupServiceTest {
List<Group> groups = ProjectionService.projectGroups(eventList);
assertThat(groups).hasSize(1);
assertThat(groups.get(0).getMembers()).hasSize(5);
assertThat(groups.get(0).getMemberships()).hasSize(5);
assertThat(groups.get(0).getType()).isEqualTo(Type.PRIVATE);
}
@ -95,7 +95,7 @@ class GroupServiceTest {
List<Group> groups = ProjectionService.projectGroups(eventList);
assertThat(groups).hasSize(20);
assertThat(groups.stream().map(group -> group.getMembers().size()).reduce(Integer::sum).get()).isEqualTo(70);
assertThat(groups.stream().map(group -> group.getMemberships().size()).reduce(Integer::sum).get()).isEqualTo(70);
}
//TODO: EventStoreServiceTest