1

"finish" service refactor + fix obvious bugs after refactor (everything should run pretty much)

This commit is contained in:
Christoph
2020-04-08 20:48:42 +02:00
parent 9da2d51897
commit 482dde7960
48 changed files with 655 additions and 520 deletions

View File

@ -3,7 +3,9 @@ package mops.gruppen2.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.api.GroupRequestWrapper;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.service.APIService;
@ -25,6 +27,7 @@ import java.util.UUID;
//TODO: API-Service?
@RestController
@RequestMapping("/gruppen2/api")
@Log4j2
public class APIController {
private final EventStoreService eventStoreService;
@ -40,6 +43,7 @@ public class APIController {
@ApiOperation("Gibt alle Gruppen zurück, in denen sich etwas geändert hat")
public GroupRequestWrapper updateGroups(@ApiParam("Letzter Status des Anfragestellers")
@PathVariable long lastEventId) throws EventException {
log.info("ApiRequest to /updateGroups\n");
return APIService.wrap(eventStoreService.findMaxEventId(),
projectionService.projectNewGroups(lastEventId));
}
@ -49,7 +53,8 @@ public class APIController {
@ApiOperation("Gibt alle Gruppen zurück, in denen sich ein Teilnehmer befindet")
public List<String> getGroupIdsOfUser(@ApiParam("Teilnehmer dessen groupIds zurückgegeben werden sollen")
@PathVariable String userId) {
return IdService.uuidToString(eventStoreService.findExistingUserGroups(userId));
log.info("ApiRequest to /getGroupIdsOfUser\n");
return IdService.uuidsToString(eventStoreService.findExistingUserGroups(new User(userId)));
}
@GetMapping("/getGroup/{groupId}")
@ -57,6 +62,7 @@ public class APIController {
@ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück")
public Group getGroupById(@ApiParam("GruppenId der gefordeten Gruppe")
@PathVariable String groupId) throws EventException {
log.info("ApiRequest to /getGroup\n");
return projectionService.projectSingleGroup(UUID.fromString(groupId));
}

View File

@ -1,5 +1,6 @@
package mops.gruppen2.controller;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType;
@ -21,25 +22,23 @@ import org.springframework.web.context.annotation.SessionScope;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.security.RolesAllowed;
import java.util.UUID;
import static mops.gruppen2.service.ControllerService.getGroupType;
import static mops.gruppen2.service.ControllerService.getParent;
import static mops.gruppen2.service.ControllerService.getUserLimit;
import static mops.gruppen2.service.ControllerService.getVisibility;
import static mops.gruppen2.service.IdService.uuidToString;
@Controller
@SessionScope
@RequestMapping("/gruppen2")
@Log4j2
public class GroupCreationController {
private final GroupService groupService;
private final ValidationService validationService;
private final ProjectionService projectionService;
public GroupCreationController(GroupService groupService, ValidationService validationService, ProjectionService projectionService) {
public GroupCreationController(GroupService groupService, ProjectionService projectionService) {
this.groupService = groupService;
this.validationService = validationService;
this.projectionService = projectionService;
}
@ -48,9 +47,9 @@ public class GroupCreationController {
public String createGroupAsOrga(KeycloakAuthenticationToken token,
Model model) {
Account account = new Account(token);
log.info("GET to /createOrga\n");
model.addAttribute("account", account);
model.addAttribute("account", new Account(token));
model.addAttribute("lectures", projectionService.projectLectures());
return "createOrga";
@ -66,25 +65,25 @@ public class GroupCreationController {
@RequestParam("lecture") boolean isLecture,
@RequestParam("maxInfiniteUsers") boolean isInfinite,
@RequestParam("userMaximum") long userLimit,
@RequestParam(value = "parent", required = false) String parent,
@RequestParam("parent") String parent,
@RequestParam(value = "file", required = false) MultipartFile file) {
validationService.checkFields(description, title, userLimit, isInfinite);
log.info("POST to /createOrga\n");
Account account = new Account(token);
User user = new User(account);
UUID parentUUID = IdService.stringToUUID(parent);
Group group = groupService.createGroup(user,
title,
description,
getVisibility(isPrivate),
getGroupType(isLecture),
getUserLimit(isInfinite, userLimit),
parentUUID);
getParent(parent, isLecture));
groupService.addUsersToGroup(CsvService.readCsvFile(file), group, user);
return "redirect:/gruppen2/details/" + uuidToString(group.getId());
return "redirect:/gruppen2/details/" + IdService.uuidToString(group.getId());
}
@RolesAllowed("ROLE_studentin")
@ -92,6 +91,8 @@ public class GroupCreationController {
public String createGroupAsStudent(KeycloakAuthenticationToken token,
Model model) {
log.info("GET to /createStudent\n");
model.addAttribute("account", new Account(token));
model.addAttribute("lectures", projectionService.projectLectures());
@ -107,21 +108,23 @@ public class GroupCreationController {
@RequestParam("visibility") boolean isPrivate,
@RequestParam("maxInfiniteUsers") boolean isInfinite,
@RequestParam("userMaximum") long userLimit,
@RequestParam(value = "parent", required = false) String parent) {
@RequestParam("parent") String parent) {
validationService.checkFields(description, title, userLimit, isInfinite);
log.info("POST to /createStudent\n");
ValidationService.validateTitle(title);
ValidationService.validateDescription(description);
Account account = new Account(token);
User user = new User(account);
UUID parentUUID = IdService.stringToUUID(parent);
Group group = groupService.createGroup(user,
title,
description,
getVisibility(isPrivate),
GroupType.SIMPLE,
getUserLimit(isInfinite, userLimit),
parentUUID);
getParent(parent, false));
return "redirect:/gruppen2/details/" + uuidToString(group.getId());
return "redirect:/gruppen2/details/" + IdService.uuidToString(group.getId());
}
}

View File

@ -1,5 +1,6 @@
package mops.gruppen2.controller;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Role;
@ -30,15 +31,14 @@ import java.util.UUID;
@Controller
@SessionScope
@RequestMapping("/gruppen2")
@Log4j2
public class GroupDetailsController {
private final ValidationService validationService;
private final InviteService inviteService;
private final GroupService groupService;
private final ProjectionService projectionService;
public GroupDetailsController(ValidationService validationService, InviteService inviteService, GroupService groupService, ProjectionService projectionService) {
this.validationService = validationService;
public GroupDetailsController(InviteService inviteService, GroupService groupService, ProjectionService projectionService) {
this.inviteService = inviteService;
this.groupService = groupService;
this.projectionService = projectionService;
@ -51,35 +51,37 @@ public class GroupDetailsController {
HttpServletRequest request,
@PathVariable("id") String groupId) {
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
log.info("GET to /details\n");
Account account = new Account(token);
User user = new User(account);
UUID parentId = group.getParent();
String actualURL = request.getRequestURL().toString();
String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/"));
Group parent = projectionService.projectSingleGroup(parentId);
validationService.throwIfGroupNotExisting(group.getTitle());
model.addAttribute("account", account);
if (!validationService.checkIfUserInGroup(group, user)) {
validationService.throwIfNoAccessToPrivate(group, user);
model.addAttribute("group", group);
model.addAttribute("parentId", parentId);
model.addAttribute("parent", parent);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
model.addAttribute("group", group);
// Parent Badge
UUID parentId = group.getParent();
Group parent = projectionService.projectParent(parentId);
// Detailseite für private Gruppen
if (!ValidationService.checkIfGroupAccess(group, user)) {
return "detailsNoMember";
}
model.addAttribute("parentId", parentId);
model.addAttribute("parent", parent);
model.addAttribute("group", group);
model.addAttribute("roles", group.getRoles());
model.addAttribute("user", user);
model.addAttribute("admin", Role.ADMIN);
model.addAttribute("public", Visibility.PUBLIC);
model.addAttribute("private", Visibility.PRIVATE);
model.addAttribute("parent", parent);
// Invitelink Anzeige für Admins
if (ValidationService.checkIfAdmin(group, user)) {
String actualURL = request.getRequestURL().toString();
String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/"));
if (validationService.checkIfAdmin(group, user)) {
model.addAttribute("link", serverURL + "gruppen2/acceptinvite/" + inviteService.getLinkByGroup(group));
}
@ -92,11 +94,13 @@ public class GroupDetailsController {
Model model,
@PathVariable("id") String groupId) {
log.info("GET to /details/changeMetadata\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, user);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
ValidationService.throwIfNoAdmin(group, user);
model.addAttribute("account", account);
model.addAttribute("title", group.getTitle());
@ -117,13 +121,13 @@ public class GroupDetailsController {
@RequestParam("description") String description,
@RequestParam("groupId") String groupId) {
log.info("POST to /details/changeMetadata\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, user);
validationService.checkFields(title, description);
ValidationService.throwIfNoAdmin(group, user);
groupService.updateTitle(user, group, title);
groupService.updateDescription(user, group, description);
@ -136,11 +140,13 @@ public class GroupDetailsController {
Model model,
@PathVariable("id") String groupId) {
log.info("GET to /details/members\n");
Account account = new Account(token);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
User user = new User(account);
validationService.throwIfNoAdmin(group, user);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
ValidationService.throwIfNoAdmin(group, user);
model.addAttribute("account", account);
model.addAttribute("members", group.getMembers());
@ -157,15 +163,17 @@ public class GroupDetailsController {
@RequestParam("group_id") String groupId,
@RequestParam("user_id") String userId) {
log.info("POST to /details/members/changeRole\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
User principle = new User(account);
User user = new User(userId);
ValidationService.throwIfNoAdmin(group, user);
groupService.toggleMemberRole(new User(userId), group);
validationService.throwIfNoAdmin(group, principle);
groupService.toggleMemberRole(user, group);
if (!validationService.checkIfAdmin(group, principle)) {
// Falls sich der User selbst die Rechte genommen hat
if (!ValidationService.checkIfAdmin(group, user)) {
return "redirect:/gruppen2/details/" + groupId;
}
@ -179,12 +187,12 @@ public class GroupDetailsController {
@RequestParam("maximum") long userLimit,
@RequestParam("group_id") String groupId) {
log.info("POST to /details/members/changeMaximum\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
validationService.throwIfNewUserLimitIsValid(userLimit, group);
groupService.updateUserLimit(user, group, userLimit);
return "redirect:/gruppen2/details/members/" + groupId;
@ -197,17 +205,15 @@ public class GroupDetailsController {
@RequestParam("group_id") String groupId,
@RequestParam("user_id") String userId) {
log.info("POST to /details/members/deleteUser\n");
Account account = new Account(token);
User principle = new User(account);
User user = new User(userId, "", "", "");
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
User user = new User(account);
validationService.throwIfNoAdmin(group, principle);
groupService.deleteUser(user, group);
if (!validationService.checkIfUserInGroup(group, principle)) {
return "redirect:/gruppen2";
// Der eingeloggte User kann sich nicht selbst entfernen
if (!userId.equals(user.getId())) {
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
groupService.deleteUser(new User(userId), group);
}
return "redirect:/gruppen2/details/members/" + groupId;
@ -220,17 +226,16 @@ public class GroupDetailsController {
Model model,
@RequestParam("id") String groupId) {
log.info("POST to /detailsBeitreten\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
validationService.throwIfUserAlreadyInGroup(group, user);
validationService.throwIfGroupFull(group);
groupService.addUser(user, group);
model.addAttribute("account", account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
groupService.addUser(user, group);
return "redirect:/gruppen2";
}
@ -240,10 +245,12 @@ public class GroupDetailsController {
public String leaveGroup(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId) {
log.info("POST to /leaveGroup\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
groupService.deleteUser(user, group);
return "redirect:/gruppen2";
@ -255,12 +262,12 @@ public class GroupDetailsController {
public String deleteGroup(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId) {
log.info("POST to /deleteGroup\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, user);
groupService.deleteGroup(user, group);
return "redirect:/gruppen2";
@ -273,10 +280,12 @@ public class GroupDetailsController {
@RequestParam("group_id") String groupId,
@RequestParam(value = "file", required = false) MultipartFile file) {
log.info("POST to /details/members/addUsersFromCsv\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(IdService.stringToUUID(groupId));
Group group = projectionService.projectSingleGroup(IdService.stringToUUID(groupId));
groupService.addUsersToGroup(CsvService.readCsvFile(file), group, user);
return "redirect:/gruppen2/details/members/" + groupId;

View File

@ -1,6 +1,8 @@
package mops.gruppen2.controller;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.PageNotFoundException;
import mops.gruppen2.service.ProjectionService;
@ -14,6 +16,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@Controller
@Log4j2
public class GruppenfindungController {
private final ProjectionService projectionService;
@ -32,12 +35,15 @@ public class GruppenfindungController {
public String index(KeycloakAuthenticationToken token,
Model model) {
log.info("GET to /gruppen2\n");
Account account = new Account(token);
User user = new User(account);
model.addAttribute("account", account);
model.addAttribute("gruppen", projectionService.projectUserGroups(user.getId()));
model.addAttribute("gruppen", projectionService.projectUserGroups(user));
model.addAttribute("user", user);
model.addAttribute("lecture", GroupType.LECTURE);
return "index";
}

View File

@ -1,12 +1,15 @@
package mops.gruppen2.controller;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.service.GroupService;
import mops.gruppen2.service.InviteService;
import mops.gruppen2.service.ProjectionService;
import mops.gruppen2.service.SearchService;
import mops.gruppen2.service.ValidationService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.cache.annotation.CacheEvict;
@ -20,40 +23,61 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.annotation.SessionScope;
import javax.annotation.security.RolesAllowed;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@Controller
@SessionScope
@RequestMapping("/gruppen2")
@Log4j2
public class SearchAndInviteController {
private final ValidationService validationService;
private final InviteService inviteService;
private final GroupService groupService;
private final ProjectionService projectionService;
private final SearchService searchService;
public SearchAndInviteController(ValidationService validationService, InviteService inviteService, GroupService groupService, ProjectionService projectionService) {
this.validationService = validationService;
public SearchAndInviteController(InviteService inviteService, GroupService groupService, ProjectionService projectionService, SearchService searchService) {
this.inviteService = inviteService;
this.groupService = groupService;
this.projectionService = projectionService;
this.searchService = searchService;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/findGroup")
@GetMapping("/searchPage")
public String findGroup(KeycloakAuthenticationToken token,
Model model,
@RequestParam(value = "suchbegriff", required = false) String search) {
Model model) {
log.info("GET to /searchPage\n");
Account account = new Account(token);
List<Group> groups = new ArrayList<>();
groups = validationService.checkSearch(search, groups, account);
User user = new User(account);
model.addAttribute("account", account);
model.addAttribute("gruppen", Collections.emptyList()); // TODO: verschönern
model.addAttribute("inviteService", inviteService); //TODO: don't inject service
return "search";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/search")
public String search(KeycloakAuthenticationToken token,
Model model,
@RequestParam("suchbegriff") String search) {
log.info("GET to /search\n");
Account account = new Account(token);
User user = new User(account);
List<Group> groups = searchService.searchPublicGroups(search, user);
model.addAttribute("account", account);
model.addAttribute("gruppen", groups);
model.addAttribute("inviteService", inviteService);
model.addAttribute("inviteService", inviteService); //TODO: don't inject service
return "search";
}
@ -64,20 +88,26 @@ public class SearchAndInviteController {
Model model,
@RequestParam("id") String groupId) {
log.info("GET to /detailsSearch\n");
Account account = new Account(token);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
UUID parentId = group.getParent();
Group parent = projectionService.projectSingleGroup(parentId);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
// Parent Badge
UUID parentId = group.getParent();
Group parent = projectionService.projectParent(parentId);
model.addAttribute("account", account);
if (validationService.checkIfUserInGroup(group, user)) {
if (ValidationService.checkIfMember(group, user)) {
return "redirect:/gruppen2/details/" + groupId;
}
model.addAttribute("group", group);
model.addAttribute("parentId", parentId);
model.addAttribute("parent", parent);
model.addAttribute("lecture", GroupType.LECTURE);
return "detailsNoMember";
}
@ -88,17 +118,26 @@ public class SearchAndInviteController {
Model model,
@PathVariable("link") String link) {
log.info("GET to /acceptInvite\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(inviteService.getGroupIdFromLink(link));
validationService.throwIfGroupNotExisting(group.getTitle());
model.addAttribute("account", new Account(token));
model.addAttribute("account", account);
model.addAttribute("group", group);
// Gruppe öffentlich
if (group.getVisibility() == Visibility.PUBLIC) {
return "redirect:/gruppen2/details/" + group.getId();
}
// Bereits Mitglied
if (ValidationService.checkIfMember(group, user)) {
return "redirect:/gruppen2/details/" + group.getId();
}
return "joinprivate";
}
@ -108,12 +147,14 @@ public class SearchAndInviteController {
public String postAcceptInvite(KeycloakAuthenticationToken token,
@RequestParam("id") String groupId) {
log.info("POST to /acceptInvite\n");
Account account = new Account(token);
User user = new User(account);
Group group = projectionService.projectSingleGroup(UUID.fromString(groupId));
validationService.throwIfUserAlreadyInGroup(group, user);
validationService.throwIfGroupFull(group);
ValidationService.throwIfMember(group, user);
ValidationService.throwIfGroupFull(group);
groupService.addUser(user, group);

View File

@ -3,6 +3,7 @@ package mops.gruppen2.domain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.HashMap;
@ -16,26 +17,29 @@ import java.util.UUID;
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
public class Group {
@EqualsAndHashCode.Include
private UUID id;
@ToString.Exclude
private UUID parent;
//TODO: Single Type for Public/Private/Lecture?
private GroupType type;
private Visibility visibility;
private String title;
private String description;
private long userLimit;
// Default + Minimum: 1
@ToString.Exclude
private long userLimit = 1;
//TODO: List to Hashmap
@ToString.Exclude
private final List<User> members = new ArrayList<>();
@ToString.Exclude
private final Map<String, Role> roles = new HashMap<>();
@Override
public String toString() {
return title + ": " + description;
}
}

View File

@ -4,18 +4,22 @@ import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@AllArgsConstructor
@NoArgsConstructor // Für Jackson: CSV-Import
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
public class User {
@EqualsAndHashCode.Include
private String id;
private String givenname;
@ToString.Exclude
private String familyname;
@ToString.Exclude
private String email;
public User(Account account) {

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.User;
@ -16,6 +18,8 @@ import java.util.UUID;
*/
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class AddUserEvent extends Event {
private String givenname;
@ -50,5 +54,8 @@ public class AddUserEvent extends Event {
group.getMembers().add(user);
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

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.Visibility;
@ -10,19 +12,19 @@ import java.util.UUID;
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class CreateGroupEvent extends Event {
private Visibility groupVisibility;
private UUID groupParent;
private GroupType groupType;
private long groupUserLimit;
public CreateGroupEvent(UUID groupId, String userId, UUID parent, GroupType type, Visibility visibility, long userLimit) {
public CreateGroupEvent(UUID groupId, String userId, UUID parent, GroupType type, Visibility visibility) {
super(groupId, userId);
groupParent = parent;
groupType = type;
groupVisibility = visibility;
groupUserLimit = userLimit;
}
@Override
@ -31,6 +33,7 @@ public class CreateGroupEvent extends Event {
group.setParent(groupParent);
group.setType(groupType);
group.setVisibility(groupVisibility);
group.setUserLimit(groupUserLimit);
log.trace("\t\t\t\t\tNeue Gruppe: {}", group.toString());
}
}

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
@ -9,6 +11,8 @@ import java.util.UUID;
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class DeleteGroupEvent extends Event {
public DeleteGroupEvent(UUID groupId, String userId) {
@ -29,5 +33,7 @@ public class DeleteGroupEvent extends Event {
group.setType(null);
group.setParent(null);
group.setUserLimit(0L);
log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group);
}
}

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.EventException;
@ -14,6 +16,8 @@ import java.util.UUID;
*/
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class DeleteUserEvent extends Event {
public DeleteUserEvent(UUID groupId, String userId) {
@ -24,12 +28,17 @@ public class DeleteUserEvent extends Event {
super(group.getId(), user.getId());
}
//TODO: what the fuck use List.remove
@Override
protected void applyEvent(Group group) throws EventException {
for (User user : group.getMembers()) {
if (user.getId().equals(userId)) {
group.getMembers().remove(user);
group.getRoles().remove(user.getId());
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles());
return;
}
}

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.GroupIdMismatchException;
@ -12,6 +13,7 @@ import mops.gruppen2.domain.exception.GroupIdMismatchException;
import java.util.UUID;
@Log4j2
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type"
@ -34,17 +36,25 @@ public abstract class Event {
protected UUID groupId;
protected String userId;
public void apply(Group group) throws EventException {
public Group apply(Group group) throws EventException {
checkGroupIdMatch(group.getId());
log.trace("Event angewendet:\t{}", this);
applyEvent(group);
return group;
}
private void checkGroupIdMatch(UUID groupId) {
if (groupId == null || this.groupId.equals(groupId)) {
// CreateGroupEvents müssen die Id erst initialisieren
if (this instanceof CreateGroupEvent) {
return;
}
throw new GroupIdMismatchException(getClass().toString());
if (!this.groupId.equals(groupId)) {
throw new GroupIdMismatchException(getClass().toString());
}
}
protected abstract void applyEvent(Group group) throws EventException;

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.BadParameterException;
@ -13,6 +15,8 @@ import java.util.UUID;
*/
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class UpdateGroupDescriptionEvent extends Event {
private String newGroupDescription;
@ -34,5 +38,7 @@ public class UpdateGroupDescriptionEvent extends Event {
}
group.setDescription(newGroupDescription);
log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription());
}
}

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.BadParameterException;
@ -13,6 +15,8 @@ import java.util.UUID;
*/
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class UpdateGroupTitleEvent extends Event {
private String newGroupTitle;
@ -34,6 +38,8 @@ public class UpdateGroupTitleEvent extends Event {
}
group.setTitle(newGroupTitle);
log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle());
}
}

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.User;
@ -14,6 +16,8 @@ import java.util.UUID;
*/
@Getter
@NoArgsConstructor // For Jackson
@ToString
@Log4j2
public class UpdateRoleEvent extends Event {
private Role newRole;
@ -32,6 +36,9 @@ public class UpdateRoleEvent extends Event {
protected void applyEvent(Group group) throws UserNotFoundException {
if (group.getRoles().containsKey(userId)) {
group.getRoles().put(userId, newRole);
log.trace("\t\t\t\t\tNeue Rollen: {}", group.getRoles());
return;
}

View File

@ -2,6 +2,8 @@ package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.BadParameterException;
@ -11,6 +13,8 @@ import java.util.UUID;
@Getter
@NoArgsConstructor
@ToString
@Log4j2
public class UpdateUserLimitEvent extends Event {
private long userLimit;
@ -32,5 +36,7 @@ public class UpdateUserLimitEvent extends Event {
}
group.setUserLimit(userLimit);
log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getUserLimit());
}
}

View File

@ -5,6 +5,6 @@ import org.springframework.http.HttpStatus;
public class UserNotFoundException extends EventException {
public UserNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Der User wurde nicht gefunden.", info);
super(HttpStatus.NOT_FOUND, "Der User existiert nicht.", info);
}
}

View File

@ -5,6 +5,8 @@ import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.Visibility;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@Log4j2
@ -33,4 +35,10 @@ public final class ControllerService {
return isInfinite ? Long.MAX_VALUE : userLimit;
}
/**
* Ermittelt die UUID des Parents, falls vorhanden.
*/
public static UUID getParent(String parent, boolean isLecture) {
return isLecture ? IdService.emptyUUID() : IdService.stringToUUID(parent);
}
}

View File

@ -23,22 +23,19 @@ public final class CsvService {
private CsvService() {}
public static List<User> readCsvFile(MultipartFile file) throws EventException {
if (file == null) {
if (file == null || file.isEmpty()) {
return Collections.emptyList();
}
if (!file.isEmpty()) {
try {
List<User> userList = read(file.getInputStream());
return userList.stream()
.distinct()
.collect(Collectors.toList()); //filter duplicates from list
} catch (IOException e) {
log.error("File konnte nicht gelesen werden:\n{}", e.getMessage());
e.printStackTrace();
throw new WrongFileException(file.getOriginalFilename());
}
try {
List<User> userList = read(file.getInputStream());
return userList.stream()
.distinct()
.collect(Collectors.toList()); //filter duplicates from list
} catch (IOException e) {
log.error("File konnte nicht gelesen werden!", e);
throw new WrongFileException(file.getOriginalFilename());
}
return Collections.emptyList();
}
private static List<User> read(InputStream stream) throws IOException {

View File

@ -1,11 +0,0 @@
package mops.gruppen2.service;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
public final class EventBuilderService {
private EventBuilderService() {}
}

View File

@ -2,6 +2,7 @@ package mops.gruppen2.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.dto.EventDTO;
import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
@ -86,8 +87,7 @@ public class EventStoreService {
getEventType(event),
payload);
} catch (JsonProcessingException e) {
log.error("Event ({}) konnte nicht serialisiert werden!", e.getMessage());
e.printStackTrace();
log.error("Event ({}) konnte nicht serialisiert werden!", event, e);
throw new BadPayloadException(EventStoreService.class.toString());
}
}
@ -109,8 +109,7 @@ public class EventStoreService {
try {
return JsonService.deserializeEvent(dto.getEvent_payload());
} catch (JsonProcessingException e) {
log.error("Payload\n {}\n konnte nicht deserialisiert werden!", e.getMessage());
e.printStackTrace();
log.error("Payload {} konnte nicht deserialisiert werden!", dto.getEvent_payload(), e);
throw new BadPayloadException(EventStoreService.class.toString());
}
}
@ -164,7 +163,7 @@ public class EventStoreService {
List<String> changedGroupIds = eventStore.findGroupIdsWhereEventIdGreaterThanStatus(status);
List<EventDTO> groupEventDTOS = eventStore.findEventDTOsByGroup(changedGroupIds);
log.trace("Seit Event {} haben sich {} Gruppen geändert!", status, changedGroupIds.size());
log.debug("Seit Event {} haben sich {} Gruppen geändert!", status, changedGroupIds.size());
return getEventsFromDTOs(groupEventDTOS);
}
@ -189,8 +188,14 @@ public class EventStoreService {
*
* @return GruppenIds (UUID) als Liste
*/
public List<UUID> findExistingUserGroups(String userId) {
List<Event> userEvents = findLatestEventsFromGroupsByUser(userId);
public List<UUID> findExistingUserGroups(User user) {
List<Event> userEvents = findLatestEventsFromGroupsByUser(user);
List<UUID> deletedIds = findLatestEventsFromGroupsByType("DeleteGroupEvent")
.stream()
.map(Event::getGroupId)
.collect(Collectors.toList());
userEvents.removeIf(event -> deletedIds.contains(event.getGroupId()));
return userEvents.stream()
.filter(event -> event instanceof AddUserEvent)
@ -211,8 +216,7 @@ public class EventStoreService {
try {
return eventStore.findMaxEventId();
} catch (NullPointerException e) {
log.trace("Eine maxId von 0 wurde zurückgegeben, da keine Events vorhanden sind.");
e.printStackTrace();
log.debug("Keine Events vorhanden!");
return 0;
}
}
@ -227,18 +231,18 @@ public class EventStoreService {
List<Event> findEventsByGroupAndType(List<UUID> groupIds, String... types) {
return getEventsFromDTOs(eventStore.findEventDTOsByGroupAndType(Arrays.asList(types),
IdService.uuidToString(groupIds)));
IdService.uuidsToString(groupIds)));
}
/**
* Sucht zu jeder Gruppe das letzte Add- oder DeleteUserEvent heraus, welches den übergebenen User betrifft.
*
* @param userId User, zu welchem die Events gesucht werden
* @param user User, zu welchem die Events gesucht werden
*
* @return Eine Liste von einem Add- oder DeleteUserEvent pro Gruppe
*/
List<Event> findLatestEventsFromGroupsByUser(String userId) {
return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(userId));
List<Event> findLatestEventsFromGroupsByUser(User user) {
return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(user.getId()));
}

View File

@ -18,7 +18,6 @@ import mops.gruppen2.domain.event.UpdateUserLimitEvent;
import mops.gruppen2.domain.exception.EventException;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -31,12 +30,10 @@ import java.util.UUID;
public class GroupService {
private final EventStoreService eventStoreService;
private final ValidationService validationService;
private final InviteService inviteService;
public GroupService(EventStoreService eventStoreService, ValidationService validationService, InviteService inviteService) {
public GroupService(EventStoreService eventStoreService, InviteService inviteService) {
this.eventStoreService = eventStoreService;
this.validationService = validationService;
this.inviteService = inviteService;
}
@ -45,9 +42,7 @@ public class GroupService {
/**
* Erzeugt eine neue Gruppe, fügt den User, der die Gruppe erstellt hat, hinzu und setzt seine Rolle als Admin fest.
* Zudem wird der Gruppentitel und die Gruppenbeschreibung erzeugt, welche vorher der Methode übergeben wurden.
* Aus diesen Event-Objekten wird eine Liste erzeugt, welche daraufhin mithilfe des EventServices gesichert wird.
* Erzeugt eine neue Gruppe und erzeugt nötige Events für die Initiale Setzung der Attribute.
*
* @param user Keycloak-Account
* @param title Gruppentitel
@ -61,12 +56,18 @@ public class GroupService {
long userLimit,
UUID parent) {
Group group = createGroup(user, parent, groupType, visibility, userLimit);
// Regeln:
// isPrivate -> !isLecture
// isLecture -> !isPrivate
ValidationService.validateFlags(visibility, groupType);
Group group = createGroup(user, parent, groupType, visibility);
// 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);
updateRole(user, group, Role.ADMIN);
updateUserLimit(user, group, userLimit);
inviteService.createLink(group);
@ -78,7 +79,7 @@ public class GroupService {
/**
* Fügt eine Liste von Usern zu einer Gruppe hinzu (in der Datenbank).
* Fügt eine Liste von Usern zu einer Gruppe hinzu.
* Duplikate werden übersprungen, die erzeugten Events werden gespeichert.
* Dabei wird das Teilnehmermaximum eventuell angehoben.
*
@ -103,7 +104,7 @@ public class GroupService {
* @return Das neue Teilnehmermaximum
*/
private static long getAdjustedUserLimit(List<User> newUsers, Group group) {
return Math.max(group.getMembers().size() + newUsers.size(), group.getMembers().size());
return Math.max(group.getMembers().size() + newUsers.size(), group.getUserLimit());
}
/**
@ -115,7 +116,8 @@ public class GroupService {
* @throws EventException Falls der User nicht gefunden wird
*/
public void toggleMemberRole(User user, Group group) throws EventException {
validationService.throwIfNotInGroup(group, user);
ValidationService.throwIfNoMember(group, user);
ValidationService.throwIfLastAdmin(user, group);
Role role = group.getRoles().get(user.getId());
updateRole(user, group, role.toggle());
@ -126,25 +128,26 @@ public class GroupService {
// Spezifische Events werden erzeugt, validiert, auf die Gruppe angewandt und gespeichert
//TODO: more validation
private Group createGroup(User user, UUID parent, GroupType groupType, Visibility visibility, long userLimit) {
/**
* Erzeugt eine Gruppe, speichert diese und gibt diese zurück.
*/
private Group createGroup(User user, UUID parent, GroupType groupType, Visibility visibility) {
Event event = new CreateGroupEvent(UUID.randomUUID(),
user.getId(),
parent,
groupType, visibility,
userLimit);
Group group = ProjectionService.projectSingleGroup(Collections.singletonList(event));
log.trace("Es wurde eine Gruppe erstellt. ({}, {})", visibility, group.getId());
groupType,
visibility);
Group group = new Group();
event.apply(group);
eventStoreService.saveEvent(event);
return group;
}
//TODO: test if exception interrupts runtime
public void addUser(User user, Group group) {
validationService.throwIfUserAlreadyInGroup(group, user);
ValidationService.throwIfMember(group, user);
ValidationService.throwIfGroupFull(group);
Event event = new AddUserEvent(group, user);
event.apply(group);
@ -159,45 +162,48 @@ public class GroupService {
try {
addUser(user, group);
} catch (Exception e) {
log.trace("Doppelter User wurde nicht hinzugefügt ({})!", user.getId());
log.debug("Doppelter User {} wurde nicht zu Gruppe {} hinzugefügt!", user, group);
}
}
public void deleteUser(User user, Group group) throws EventException {
validationService.throwIfNotInGroup(group, user);
validationService.throwIfLastAdmin(user, group);
ValidationService.throwIfNoMember(group, user);
ValidationService.throwIfLastAdmin(user, group);
Event event = new DeleteUserEvent(group, user);
event.apply(group);
eventStoreService.saveEvent(event);
if (validationService.checkIfGroupEmpty(group)) {
if (ValidationService.checkIfGroupEmpty(group)) {
deleteGroup(user, group);
} else {
Event event = new DeleteUserEvent(group, user);
event.apply(group);
eventStoreService.saveEvent(event);
}
}
public void deleteGroup(User user, Group group) {
inviteService.destroyLink(group);
log.trace("Eine Gruppe wurde gelöscht ({})", group.getId());
ValidationService.throwIfNoAdmin(group, user);
Event event = new DeleteGroupEvent(group, user);
event.apply(group);
inviteService.destroyLink(group);
eventStoreService.saveEvent(event);
}
//TODO: Validate title
public void updateTitle(User user, Group group, String title) {
ValidationService.throwIfNoAdmin(group, user);
ValidationService.validateTitle(title);
Event event = new UpdateGroupTitleEvent(group, user, title);
event.apply(group);
eventStoreService.saveEvent(event);
}
//TODO: Validate description
public void updateDescription(User user, Group group, String description) {
ValidationService.throwIfNoAdmin(group, user);
ValidationService.validateDescription(description);
Event event = new UpdateGroupDescriptionEvent(group, user, description);
event.apply(group);
@ -205,14 +211,22 @@ public class GroupService {
}
public void updateRole(User user, Group group, Role role) {
ValidationService.throwIfNoMember(group, user);
Event event = new UpdateRoleEvent(group, user, role);
event.apply(group);
eventStoreService.saveEvent(event);
}
//TODO: Validate limit
public void updateUserLimit(User user, Group group, long userLimit) {
ValidationService.throwIfNoAdmin(group, user);
ValidationService.validateUserLimit(userLimit, group);
if (userLimit == group.getUserLimit()) {
return;
}
Event event = new UpdateUserLimitEvent(group, user, userLimit);
event.apply(group);

View File

@ -13,7 +13,7 @@ public final class IdService {
private IdService() {}
public static List<UUID> stringToUUIDs(List<String> groupIds) {
public static List<UUID> stringsToUUID(List<String> groupIds) {
return groupIds.stream()
.map(IdService::stringToUUID)
.collect(Collectors.toList());
@ -28,14 +28,10 @@ public final class IdService {
* @return Id als UUID
*/
public static UUID stringToUUID(String groupId) {
if (groupId == null || groupId.isEmpty()) {
return UUID.fromString("00000000-0000-0000-0000-000000000000");
}
return UUID.fromString(groupId);
return groupId.isEmpty() ? emptyUUID() : UUID.fromString(groupId);
}
public static List<String> uuidToString(List<UUID> groupIds) {
public static List<String> uuidsToString(List<UUID> groupIds) {
return groupIds.stream()
.map(UUID::toString)
.collect(Collectors.toList());
@ -45,11 +41,11 @@ public final class IdService {
return groupId.toString();
}
public static boolean idIsEmpty(UUID id) {
if (id == null) {
return true;
}
public static boolean isEmpty(UUID id) {
return id == null || emptyUUID().equals(id);
}
return "00000000-0000-0000-0000-000000000000".equals(id.toString());
public static UUID emptyUUID() {
return UUID.fromString("00000000-0000-0000-0000-000000000000");
}
}

View File

@ -25,21 +25,20 @@ public class InviteService {
group.getId().toString(),
UUID.randomUUID().toString()));
log.trace("Link wurde erzeugt! (Gruppe: {})", group.getId());
log.debug("Link wurde erzeugt! (Gruppe: {})", group.getId());
}
void destroyLink(Group group) {
inviteRepository.deleteLinkOfGroup(group.getId().toString());
log.trace("Link wurde zerstört! (Gruppe: {})", group.getId());
log.debug("Link wurde zerstört! (Gruppe: {})", group.getId());
}
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.printStackTrace();
log.error("Gruppe zu Link ({}) konnte nicht gefunden werden!", link, e);
throw new InvalidInviteException(link);
}
}
@ -48,8 +47,7 @@ public class InviteService {
try {
return inviteRepository.findLinkByGroupId(group.getId().toString());
} catch (Exception e) {
log.error("Link zu Gruppe ({}) konnte nicht gefunden werden!", group.getId());
e.printStackTrace();
log.error("Link zu Gruppe ({}) konnte nicht gefunden werden!", group.getId(), e);
throw new NoInviteExistException(group.getId().toString());
}
}

View File

@ -3,6 +3,7 @@ package mops.gruppen2.service;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.exception.EventException;
@ -62,6 +63,10 @@ public class ProjectionService {
* @throws EventException Projektionsfehler, z.B. falls Events von verschiedenen Gruppen übergeben werden
*/
static Group projectSingleGroup(List<Event> events) throws EventException {
if (events.isEmpty()) {
throw new GroupNotFoundException(ProjectionService.class.toString());
}
Group group = new Group();
events.forEach(event -> event.apply(group));
@ -154,18 +159,17 @@ public class ProjectionService {
* Projiziert Gruppen, in welchen der User aktuell teilnimmt.
* Die Gruppen enthalten nur Metainformationen: Titel und Beschreibung.
*
* @param userId Die Id
* @param user Die Id
*
* @return Liste aus Gruppen
*/
@Cacheable("groups")
public List<Group> projectUserGroups(String userId) {
List<UUID> groupIds = eventStoreService.findExistingUserGroups(userId);
public List<Group> projectUserGroups(User user) {
List<UUID> groupIds = eventStoreService.findExistingUserGroups(user);
List<Event> groupEvents = eventStoreService.findEventsByGroupAndType(groupIds,
"CreateGroupEvent",
"UpdateGroupTitleEvent",
"UpdateGroupDescriptionEvent",
"DeleteGroupEvent");
"UpdateGroupDescriptionEvent");
return projectGroups(groupEvents);
}
@ -182,28 +186,38 @@ public class ProjectionService {
* @throws GroupNotFoundException Wenn die Gruppe nicht gefunden wird
*/
public Group projectSingleGroup(UUID groupId) throws GroupNotFoundException {
if (IdService.idIsEmpty(groupId)) {
return new Group();
if (IdService.isEmpty(groupId)) {
throw new GroupNotFoundException(groupId + ": " + ProjectionService.class);
}
try {
List<Event> events = eventStoreService.findGroupEvents(groupId);
return projectGroups(events).get(0);
} catch (IndexOutOfBoundsException e) {
log.error("Gruppe {} wurde nicht gefunden!", groupId.toString());
e.printStackTrace();
throw new GroupNotFoundException(ProjectionService.class.toString());
return projectSingleGroup(events);
} catch (Exception e) {
log.error("Gruppe {} wurde nicht gefunden!", groupId.toString(), e);
throw new GroupNotFoundException(groupId + ": " + ProjectionService.class);
}
}
/**
* Projiziert eine einzelne Gruppe, welche leer sein darf.
*/
public Group projectParent(UUID parentId) {
if (IdService.isEmpty(parentId)) {
return new Group();
}
return projectSingleGroup(parentId);
}
/**
* Entfernt alle Gruppen, in welchen ein User teilnimmt, aus einer Gruppenliste.
*
* @param groups Gruppenliste, aus der entfernt wird
* @param userId User, welcher teilnimmt
* @param user User, welcher teilnimmt
*/
void removeUserGroups(List<Group> groups, String userId) {
List<UUID> userGroups = eventStoreService.findExistingUserGroups(userId);
void removeUserGroups(List<Group> groups, User user) {
List<UUID> userGroups = eventStoreService.findExistingUserGroups(user);
groups.removeIf(group -> userGroups.contains(group.getId()));
}

View File

@ -3,6 +3,7 @@ package mops.gruppen2.service;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.EventException;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@ -32,16 +33,16 @@ public class SearchService {
* @throws EventException Projektionsfehler
*/
@Cacheable("groups")
public List<Group> searchPublicGroups(String search, String userId) throws EventException {
public List<Group> searchPublicGroups(String search, User user) throws EventException {
List<Group> groups = projectionService.projectPublicGroups();
projectionService.removeUserGroups(groups, userId);
projectionService.removeUserGroups(groups, user);
sortByGroupType(groups);
if (search.isEmpty()) {
return groups;
}
log.trace("Es wurde gesucht nach: {}", search);
log.debug("Es wurde gesucht nach: {}", search);
return groups.stream()
.filter(group -> group.toString().toLowerCase().contains(search.toLowerCase()))

View File

@ -1,172 +1,156 @@
package mops.gruppen2.service;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.GroupNotFoundException;
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 org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static mops.gruppen2.domain.Role.ADMIN;
@Service
@Log4j2
public class ValidationService {
public final class ValidationService {
private final SearchService searchService;
private final ProjectionService projectionService;
private ValidationService() {}
public ValidationService(SearchService searchService, ProjectionService projectionService) {
this.searchService = searchService;
this.projectionService = projectionService;
}
//TODO: make static or change return + assignment
public List<Group> checkSearch(String search, List<Group> groups, Account account) {
if (search != null) {
groups = searchService.searchPublicGroups(search, account.getName());
}
return groups;
}
// ######################################## CHECK ############################################
//TODO: what the fuck
public void throwIfGroupNotExisting(String title) {
if (title == null) {
throw new GroupNotFoundException("@details");
}
}
public void throwIfUserAlreadyInGroup(Group group, User user) {
if (checkIfUserInGroup(group, user)) {
throw new UserAlreadyExistsException("@details");
}
}
void throwIfNotInGroup(Group group, User user) {
if (!checkIfUserInGroup(group, user)) {
throw new UserNotFoundException(getClass().toString());
}
}
public boolean checkIfUserInGroup(Group group, User user) {
/**
* Überprüft, ob ein User in einer Gruppe teilnimmt.
*/
public static boolean checkIfMember(Group group, User user) {
return group.getMembers().contains(user);
}
public void throwIfGroupFull(Group group) {
if (group.getUserLimit() < group.getMembers().size() + 1) {
throw new GroupFullException("Du kannst der Gruppe daher leider nicht beitreten.");
}
public static boolean checkIfLastMember(User user, Group group) {
return checkIfMember(group, user) && group.getMembers().size() == 1;
}
//TODO: necessary?
boolean checkIfGroupEmpty(UUID groupId) {
return projectionService.projectSingleGroup(groupId).getMembers().isEmpty();
/**
* Überprüft, ob eine Gruppe voll ist.
*/
public static boolean checkIfGroupFull(Group group) {
return group.getMembers().size() >= group.getUserLimit();
}
boolean checkIfGroupEmpty(Group group) {
/**
* Überprüft, ob eine Gruppe leer ist.
*/
public static boolean checkIfGroupEmpty(Group group) {
return group.getMembers().isEmpty();
}
public void throwIfNoAdmin(Group group, User user) {
throwIfNoAccessToPrivate(group, user);
if (group.getRoles().get(user.getId()) != ADMIN) {
throw new NoAccessException("");
}
}
public void throwIfNoAccessToPrivate(Group group, User user) {
if (!checkIfUserInGroup(group, user) && group.getVisibility() == Visibility.PRIVATE) {
throw new NoAccessException("");
}
}
public boolean checkIfAdmin(Group group, User user) {
if (checkIfUserInGroup(group, user)) {
/**
* Ü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.getId()) == ADMIN;
}
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 checkIfGroupAccess(Group group, User user) {
return (group.getVisibility() == Visibility.PRIVATE && checkIfMember(group, user))
|| group.getVisibility() == Visibility.PUBLIC;
}
// ######################################## 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 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 throwIfNoAdmin(Group group, User user) {
if (!checkIfAdmin(group, user)) {
log.error("User {} ist kein Admin in Gruppe {}!", user, group);
throw new NoAccessException(group.toString());
}
}
/**
* Schmeißt keine Exception, wenn der User der letzte User ist.
*/
void throwIfLastAdmin(User user, Group group) {
public static void throwIfLastAdmin(User user, Group group) {
if (!checkIfLastMember(user, group) && checkIfLastAdmin(user, group)) {
throw new NoAdminAfterActionException("Du bist letzter Admin!");
}
}
boolean checkIfLastAdmin(User user, Group group) {
for (Map.Entry<String, Role> entry : group.getRoles().entrySet()) {
if (entry.getValue() == ADMIN && !(entry.getKey().equals(user.getId()))) {
return false;
}
public static void throwIfGroupFull(Group group) {
if (checkIfGroupFull(group)) {
log.error("Die Gruppe {} ist voll!", group);
throw new GroupFullException(group.toString());
}
return true;
}
boolean checkIfLastMember(User user, Group group) {
return group.getMembers().contains(user) && group.getMembers().size() == 1;
public static void throwIfNoGroupAccess(Group group, User user) {
if (!checkIfGroupAccess(group, user)) {
log.error("Der User {} hat keinen Zugriff auf Gruppe {}!", user, group);
throw new NoAccessException(group.toString());
}
}
/**
* Überprüft, ob alle Felder richtig gesetzt sind.
*
* @param description Die Beschreibung der Gruppe
* @param title Der Titel der Gruppe
* @param userLimit Das user Limit der Gruppe
*/
public void checkFields(String title, String description, Long userLimit, Boolean maxInfiniteUsers) {
if (description == null || description.trim().isEmpty()) {
throw new BadParameterException("Die Beschreibung wurde nicht korrekt angegeben");
}
// ##################################### VALIDATE FIELDS #####################################
//TODO: max title length?
public static void validateTitle(String title) {
if (title == null || title.trim().isEmpty()) {
throw new BadParameterException("Der Titel wurde nicht korrekt angegeben");
}
if (userLimit == null && maxInfiniteUsers == null) {
throw new BadParameterException("Teilnehmeranzahl wurde nicht korrekt angegeben");
}
if (userLimit != null && (userLimit < 1 || userLimit > 100_000L)) {
throw new BadParameterException("Teilnehmeranzahl wurde nicht korrekt angegeben");
log.error("Der Titel {} ist fehlerhaft!", title);
throw new BadParameterException("Der Titel darf nicht leer sein!");
}
}
public void checkFields(String title, String description) {
//TODO: max description length?
public static void validateDescription(String description) {
if (description == null || description.trim().isEmpty()) {
throw new BadParameterException("Die Beschreibung wurde nicht korrekt angegeben");
}
if (title == null || title.trim().isEmpty()) {
throw new BadParameterException("Der Titel wurde nicht korrekt angegeben");
log.error("Die Beschreibung {} ist fehlerhaft!", description);
throw new BadParameterException("Die Beschreibung darf nicht leer sein!");
}
}
public void throwIfNewUserLimitIsValid(Long newUserLimit, Group group) {
if (newUserLimit == null) {
throw new BadParameterException("Es wurde keine neue maximale Teilnehmeranzahl angegeben!");
public static void validateFlags(Visibility visibility, GroupType groupType) {
if (visibility == Visibility.PRIVATE && groupType == GroupType.LECTURE) {
throw new BadParameterException("Eine Veranstaltung kann nicht privat sein!");
}
}
public static void validateUserLimit(long userLimit, Group group) {
if (userLimit < 1) {
throw new BadParameterException("Das Userlimit muss größer als 1 sein!");
}
if (newUserLimit < 1 || newUserLimit > 100_000L) {
throw new BadParameterException("Die neue maximale Teilnehmeranzahl wurde nicht korrekt angegeben!");
}
if (group.getMembers().size() > newUserLimit) {
throw new BadParameterException("Die neue maximale Teilnehmeranzahl ist kleiner als die aktuelle Teilnehmeranzahl!");
if (userLimit < group.getMembers().size()) {
throw new BadParameterException("Das Userlimit kann nicht unter der momentanen Mitgliederanzahl sein!");
}
}
}

View File

@ -1,24 +1,33 @@
application.name=gruppen2
logging.pattern.console=[${application.name}],%magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS}, %highlight(%msg),%thread,%logger.%M%n
spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:blogdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=false
logging.level.org.springframework.jdbc.core=INFO
keycloak.principal-attribute=preferred_username
keycloak.auth-server-url=https://keycloak.cs.hhu.de/auth
keycloak.realm=MOPS
hhu_keycloak.token-uri=https://keycloak.cs.hhu.de/auth/realms/MOPS/protocol/openid-connect/token
keycloak.resource=gruppenfindung
keycloak.credentials.secret=fc6ebf10-8c63-4e71-a667-4eae4e8209a1
keycloak.verify-token-audience=true
keycloak.use-resource-role-mappings=true
keycloak.autodetect-bearer-only=true
keycloak.confidential-port=443
server.error.include-stacktrace=always
management.endpoints.web.exposure.include=info,health
spring.cache.type=NONE
logging.level.mops.gruppen2=trace
# Logging
logging.application.name = gruppen2
logging.pattern.console = [${logging.application.name}], %magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS},\t%blue(%msg)\n\t\t\t\t\t\t\t\t\t\t\t%thread,%logger.%M%n
spring.output.ansi.enabled = always
logging.level.mops.gruppen2 = trace
logging.level.org.springframework.jdbc.core = info
# Database
spring.datasource.platform = h2
spring.datasource.driver-class-name = org.h2.Driver
spring.datasource.initialization-mode = always
spring.datasource.url = jdbc:h2:mem:blogdb
spring.datasource.username = sa
spring.datasource.password =
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.h2.console.enabled = false
# Security
keycloak.principal-attribute = preferred_username
keycloak.auth-server-url = https://keycloak.cs.hhu.de/auth
keycloak.realm = MOPS
hhu_keycloak.token-uri = https://keycloak.cs.hhu.de/auth/realms/MOPS/protocol/openid-connect/token
keycloak.resource = gruppenfindung
keycloak.credentials.secret = fc6ebf10-8c63-4e71-a667-4eae4e8209a1
keycloak.verify-token-audience = true
keycloak.use-resource-role-mappings = true
keycloak.autodetect-bearer-only = true
keycloak.confidential-port = 443
# Misc
server.error.include-stacktrace = always
management.endpoints.web.exposure.include = info,health
spring.cache.type = none

View File

@ -1,19 +1,30 @@
application.name=gruppen2
logging.pattern.console=[${application.name}],%magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS}, %highlight(%msg),%thread,%logger.%M%n
spring.datasource.platform=mysql
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.initialization-mode=NEVER
spring.datasource.url=jdbc:mysql://dbmysql:3306/gruppen2
spring.datasource.username=root
spring.datasource.password=geheim
keycloak.principal-attribute=preferred_username
keycloak.auth-server-url=https://keycloak.cs.hhu.de/auth
keycloak.realm=MOPS
hhu_keycloak.token-uri=https://keycloak.cs.hhu.de/auth/realms/MOPS/protocol/openid-connect/token
keycloak.resource=gruppenfindung
keycloak.credentials.secret=fc6ebf10-8c63-4e71-a667-4eae4e8209a1
keycloak.verify-token-audience=true
keycloak.use-resource-role-mappings=true
keycloak.autodetect-bearer-only=true
keycloak.confidential-port=443
management.endpoints.web.exposure.include=info,health
# Logging
logging.application.name = gruppen2
logging.pattern.console = [${logging.application.name}],%magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS}, %highlight(%msg),%thread,%logger.%M%n
spring.output.ansi.enabled = always
logging.level.mops.gruppen2 = info
logging.level.org.springframework.jdbc.core = info
# Database
spring.datasource.platform = mysql
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.initialization-mode = never
spring.datasource.url = jdbc:mysql://dbmysql:3306/gruppen2
spring.datasource.username = root
spring.datasource.password = geheim
# Security
keycloak.principal-attribute = preferred_username
keycloak.auth-server-url = https://keycloak.cs.hhu.de/auth
keycloak.realm = MOPS
hhu_keycloak.token-uri = https://keycloak.cs.hhu.de/auth/realms/MOPS/protocol/openid-connect/token
keycloak.resource = gruppenfindung
keycloak.credentials.secret = fc6ebf10-8c63-4e71-a667-4eae4e8209a1
keycloak.verify-token-audience = true
keycloak.use-resource-role-mappings = true
keycloak.autodetect-bearer-only = true
keycloak.confidential-port = 443
# Misc
management.endpoints.web.exposure.include = info,health
server.error.include-stacktrace = always

View File

@ -1,2 +1,2 @@
spring.profiles.active=dev
spring.profiles.active = dev

View File

@ -30,7 +30,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li>
<a href="/findGroup" th:href="@{/gruppen2/findGroup}">Suche</a>
<a href="/searchPage" th:href="@{/gruppen2/searchPage}">Suche</a>
</li>
</ul>
</nav>

View File

@ -31,7 +31,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li>
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
<a th:href="@{/gruppen2/searchPage}" href="/searchPage">Suche</a>
</li>
</ul>
</nav>
@ -53,31 +53,33 @@
<textarea class="form-control" id="description" required rows="3" th:name="description"></textarea>
</div>
<div class="custom-control custom-checkbox">
<!--DO NOT WRAP-->
<input type="hidden" name="maxInfiniteUsers" value="0"/><input class="custom-control-input" type="checkbox" id="maxInfiniteUsers" onclick="this.previousSibling.value=1-this.previousSibling.value"/>
<!--DUMMY-->
<input type="hidden" id="maxInfiniteUsersDummy" name="maxInfiniteUsers" value="0"/>
<input class="custom-control-input" type="checkbox" id="maxInfiniteUsers" onchange="$('#maxInfiniteUsersDummy').val(this.checked ? 1 : 0)"/>
<label class="custom-control-label" for="maxInfiniteUsers">Anzahl
unbegrenzt</label>
</div>
<div class="form-group mt-3" id="userMaximum">
<label for="userMaximum">Teilnehmeranzahl</label>
<input class="form-control" th:name="userMaximum" type="number" min="1" max="100000" value="1">
<input class="form-control" id="userMax" th:name="userMaximum" type="number" min="1" max="100000" value="1">
</div>
<div class="custom-control custom-checkbox" id="privateCheckbox">
<!--DO NOT WRAP-->
<input type="hidden" name="visibility" value="0"/><input class="custom-control-input" type="checkbox" id="visibility" onclick="this.previousSibling.value=1-this.previousSibling.value"/>
<!--DUMMY-->
<input type="hidden" id="visibilityDummy" name="visibility" value="0"/>
<input class="custom-control-input" type="checkbox" id="visibility" onchange="$('#visibilityDummy').val(this.checked ? 1 : 0)"/>
<label class="custom-control-label" for="visibility">Privat</label>
</div>
<div class="custom-control custom-checkbox" id="lectureCheckbox">
<!--DO NOT WRAP-->
<input type="hidden" name="lecture" value="0"/><input class="custom-control-input" type="checkbox" id="lecture" onclick="this.previousSibling.value=1-this.previousSibling.value"/>
<!--DUMMY-->
<input type="hidden" id="lectureDummy" name="lecture" value="0"/>
<input class="custom-control-input" type="checkbox" id="lecture" onchange="$('#lectureDummy').val(this.checked ? 1 : 0)"/>
<label class="custom-control-label" for="lecture">Veranstaltung</label>
</div>
<div class="form-group" id="lectureParent">
<label for="parent"></label>
<div class="form-group mt-3" id="lectureParent">
<label for="parent">Veranstaltungszugehörigkeit</label>
<select class="form-control" id="parent" th:name="parent">
<option disabled selected>--Bitte Veranstaltung auswählen--
</option>
<option th:each="lecture : ${lectures}" th:name="parent" th:value="${lecture.getId()}" th:text="${lecture.getTitle()}"></option>
<option value="" selected>--Keine--</option>
<option th:each="lecture : ${lectures}" name="parent" th:value="${lecture.getId()}" th:text="${lecture.getTitle()}"></option>
</select>
</div>
<div class="form-group pt-4">
@ -105,7 +107,6 @@
</div>
<script>
//TODO: Hab ich kaputt gemacht
// Add the following code if you want the name of the file appear on select
$(".custom-file-input").on("change", function () {
const fileName = $(this).val().split("\\").pop();
@ -115,28 +116,28 @@
// Collapse lectureParent if lecture
$(document).ready(function () {
$('#lecture').change(function () {
$('#lectureParent').prop('disabled', function (i, v) { return !v; });
$('#parent').prop('disable', function (i, v) { return !v; });
});
});
// Collapse provateCheckbox if lecture
$(document).ready(function () {
$('#lecture').change(function () {
$('#privateCheckbox').prop('disabled', function (i, v) { return !v; });
$('#visibility').prop('disabled', function (i, v) { return !v; });
});
});
// Collapse lectureCheckbox if private
$(document).ready(function () {
$('#visibility').change(function () {
$('#lectureCheckbox').prop('disabled', function (i, v) { return !v; });
$('#lecture').prop('disabled', function (i, v) { return !v; });
});
});
// Collapse userMaximum if infinite
$(document).ready(function () {
$('#maxInfiniteUsers').change(function () {
$('#userMaximum').prop('disabled', function (i, v) { return !v; });
$('#userMax').prop('readonly', function (i, v) { return !v; });
});
});
</script>

View File

@ -27,7 +27,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li>
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
<a th:href="@{/gruppen2/searchPage}" href="/searchPage">Suche</a>
</li>
</ul>
</nav>
@ -50,8 +50,9 @@
<textarea class="form-control" id="description" required rows="3" th:name="description"></textarea>
</div>
<div class="custom-control custom-checkbox">
<!--DO NOT WRAP-->
<input type="hidden" name="maxInfiniteUsers" value="0"/><input class="custom-control-input" type="checkbox" id="maxInfiniteUsers" onclick="this.previousSibling.value=1-this.previousSibling.value"/>
<!--DUMMY-->
<input type="hidden" id="maxInfiniteUsersDummy" name="maxInfiniteUsers" value="0"/>
<input class="custom-control-input" type="checkbox" id="maxInfiniteUsers" onchange="$('#maxInfiniteUsersDummy').val(this.checked ? 1 : 0)"/>
<label class="custom-control-label" for="maxInfiniteUsers">Anzahl
unbegrenzt</label>
</div>
@ -61,15 +62,15 @@
type="number" min="1" max="10000">
</div>
<div class="custom-control custom-checkbox">
<!--DO NOT WRAP-->
<input type="hidden" name="visibility" value="0"/><input class="custom-control-input" type="checkbox" id="visibility" onclick="this.previousSibling.value=1-this.previousSibling.value"/>
<!--DUMMY-->
<input type="hidden" id="visibilityDummy" name="visibility" value="0"/>
<input class="custom-control-input" type="checkbox" id="visibility" onchange="$('#visibilityDummy').val(this.checked ? 1 : 0)"/>
<label class="custom-control-label" for="visibility">Privat</label>
</div>
<div class="form-group" id="lectureParent">
<label for="parent"></label>
<label for="parent">Veranstaltungszugehörigkeit</label>
<select class="form-control" id="parent" name="parent">
<option disabled selected>--Bitte Veranstaltung auswählen--
</option>
<option value="" selected>--Keine--</option>
<option th:each="lecture : ${lectures}" th:name="parent" th:value="${lecture.getId()}" th:text="${lecture.getTitle()}">
</option>
</select>

View File

@ -24,7 +24,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li>
<a href="/findGroup" th:href="@{/gruppen2/findGroup}">Suche</a>
<a href="/searchPage" th:href="@{/gruppen2/searchPage}">Suche</a>
</li>
</ul>
</nav>
@ -54,7 +54,7 @@
<span class="badge badge-pill badge-success" style="background: lightseagreen"
th:if='${group.getType() == group.getType().LECTURE}'>Veranstaltung</span>
<span class="badge badge-pill badge-info" style="background: mediumorchid"
th:text="${parent.getTitle()}">Parent</span>
th:text="${parent?.getTitle()}">Parent</span>
<div class="input-group mb-3" style="margin-top: 10px"
th:if="${roles.get(user.getId()) == admin}">

View File

@ -22,59 +22,29 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li class="active">
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
<a th:href="@{/gruppen2/searchPage}" href="/searchPage">Suche</a>
</li>
</ul>
</nav>
</header>
<main th:fragment="bodycontent">
<div class="container-fluid">
<div class="row">
<div class="col-9">
<div class="shadow-sm p-4" style="border: 1px solid aliceblue; border-radius: 5px; background: aliceblue">
<h1 style="color: black; font-weight: bold; font-optical-sizing: auto; overflow-wrap: break-word" th:text="${group.getTitle()}"></h1>
<h3>
<div class="shadow-sm p-4" style="border: 1px solid aliceblue; border-radius: 5px; background: aliceblue">
<h1 style="color: black; font-weight: bold; font-optical-sizing: auto; overflow-wrap: break-word" th:text="${group.getTitle()}"></h1>
<h3>
<span class="badge badge-pill badge-dark" style="background: darkslategray"
th:if='${group.getVisibility() == group.getVisibility().PRIVATE }'>Private Gruppe</span>
<span class="badge badge-pill badge-primary" style="background: #52a1eb"
th:if="${group.getVisibility() == group.getVisibility().PUBLIC}">Öffentliche Gruppe</span>
<span class="badge badge-pill badge-success"
style="background: lightseagreen"
th:if='${group.getType() == group.getType().LECTURE}'> Veranstaltung</span>
<span class="badge badge-pill badge-info" style="background: mediumorchid"
th:text="${parent.getTitle()}">Parent</span>
</h3>
<div class="shadow-sm p-4" style="background: white">
<p style="overflow-wrap: break-word; font-optical-sizing: auto"
th:text="${group.getDescription()}"></p>
</div>
<div class="form-group mt-2">
<div class="text-right">
<form method="post" th:action="@{/gruppen2/detailsBeitreten}">
<button class="btn btn-primary"
style="background: #52a1eb; border-style: none;"
th:href="@{/gruppen2/detailsBeitreten}"
th:name="id" th:value="${group.getId()}"
type="submit">Gruppe beitreten
</button>
</form>
</div>
</div>
</div>
</div>
<div class="col-3" style="white-space: nowrap">
<div style="display: inline-block; margin: 0">
<h2>Mitglieder</h2>
<div th:switch="${group.getUserLimit() != 100000}">
<h4 th:case="${true}">
<a th:text="${group.getMembers().size()}"></a>
<a>von maximal</a>
<a th:text="${group.getUserLimit()}"></a>
<a>Benutzern.</a>
</h4>
<h4 th:case="false">unbegrenzte Teilnehmeranzahl</h4>
</div>
</div>
<span class="badge badge-pill badge-primary" style="background: #52a1eb"
th:if="${group.getVisibility() == group.getVisibility().PUBLIC}">Öffentliche Gruppe</span>
<span class="badge badge-pill badge-success"
style="background: lightseagreen"
th:if='${group.getType() == lecture}'> Veranstaltung</span>
<span class="badge badge-pill badge-info" style="background: mediumorchid"
th:text="${parent?.getTitle()}">Parent</span>
</h3>
<div class="shadow-sm p-4" style="background: white">
<p style="overflow-wrap: break-word; font-optical-sizing: auto"
th:text="${group.getDescription()}"></p>
</div>
</div>
</div>

View File

@ -27,7 +27,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li>
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
<a th:href="@{/gruppen2/searchPage}" href="/searchPage">Suche</a>
</li>
</ul>
</nav>
@ -37,6 +37,7 @@
<div class="row">
<div class="col-10">
<h1>Mitglieder bearbeiten</h1>
<!--TODO: Anzeige bei unbegrenzt-->
<div th:switch="${group.getUserLimit() != 100000}">
<h5 th:case="${true}">
<a th:text="${group.getMembers().size()}"></a>

View File

@ -23,7 +23,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li>
<a href="/findGroup" th:href="@{/gruppen2/findGroup}">Suche</a>
<a href="/searchPage" th:href="@{/gruppen2/searchPage}">Suche</a>
</li>
</ul>
</nav>
@ -50,7 +50,7 @@
<h3 style="color: dodgerblue; font-weight: bold; font-optical-sizing: auto; overflow-wrap: break-word">
<span class="badge badge-pill badge-success"
style="background: lightseagreen; margin-right: 25px; float: right"
th:if='${gruppe.getType() == gruppe.getType().LECTURE}'>Veranstaltung</span>
th:if='${gruppe.getType() == lecture}'>Veranstaltung</span>
<a th:href="@{/gruppen2/details/{id}(id=${gruppe.getId()})}"
th:text="${gruppe.getTitle()}"></a>
</h3>

View File

@ -10,6 +10,7 @@
</th:block>
</head>
<body>
<header>
<nav class="navigation navigation-secondary" is="mops-navigation" th:fragment="navigation"
th:switch="${account.getRoles().contains('orga')}">
@ -24,56 +25,44 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li class="active">
<a href="/findGroup" th:href="@{/gruppen2/findGroup}">Suche</a>
<a href="/searchPage" th:href="@{/gruppen2/searchPage}">Suche</a>
</li>
</ul>
</nav>
</header>
<main th:fragment="bodycontent">
<div class="container-fluid">
<div class="row">
<div class="col-9">
<div class="shadow-sm p-4"
style="border: 1px solid aliceblue; border-radius: 5px; background: aliceblue">
<h1 style="color: black; font-weight: bold; font-optical-sizing: auto; overflow-wrap: break-word"
th:text="${group.getTitle()}"></h1>
<h3>Möchtest du dieser privaten Gruppe beitreten?</h3>
<div class="shadow-sm p-4" style="background: white">
<p style="overflow-wrap: break-word; font-optical-sizing: auto"
th:text="${group.getDescription()}"></p>
</div>
<div class="form-group mt-2">
<div class="text-right">
<form method="post" th:action="@{/gruppen2/acceptinvite}">
<input name="id" th:value="${group.getId()}" type="hidden"/>
<button class="btn btn-primary"
style="background: #52a1eb; border-style: none;"
type="submit">Ja, Gruppe beitreten
</button>
<a class="btn btn-primary"
href="/gruppen2"
style="background: #52a1eb; border-style: none;"
type="submit">Ich will das nicht.
</a>
</form>
</div>
</div>
</div>
</div>
<div class="col-3" style="white-space: nowrap">
<div style="display: inline-block; margin: 0">
<h2>Mitglieder</h2>
<div th:switch="${group.getUserLimit() != 100000}">
<h4 th:case="${true}">
<a th:text="${group.getMembers().size()}"></a>
<a>von maximal</a>
<a th:text="${group.getUserLimit()}"></a>
<a>Benutzern.</a>
</h4>
<h4 th:case="false">unbegrenzte Teilnehmeranzahl</h4>
</div>
<div class="shadow-sm p-4"
style="border: 1px solid aliceblue; border-radius: 5px; background: aliceblue">
<h1 style="color: black; font-weight: bold; font-optical-sizing: auto; overflow-wrap: break-word"
th:text="${group.getTitle()}"></h1>
<div class="shadow-sm p-4" style="background: white">
<p style="overflow-wrap: break-word; font-optical-sizing: auto"
th:text="${group.getDescription()}"></p>
</div>
<div class="form-group mt-2" th:if="${group.getMembers().size() < group.getUserLimit()}">
<h3>Möchtest du dieser privaten Gruppe beitreten?</h3>
<div class="text-right">
<form method="post" th:action="@{/gruppen2/acceptinvite}">
<input name="id" th:value="${group.getId()}" type="hidden"/>
<button class="btn btn-primary"
style="background: #52a1eb; border-style: none;"
type="submit">Ja, Gruppe beitreten
</button>
<a class="btn btn-primary"
href="/gruppen2"
style="background: #52a1eb; border-style: none;"
type="submit">Ich will das nicht.
</a>
</form>
</div>
</div>
<h3 class="mt-2" th:if="${group.getMembers().size() >= group.getUserLimit()}">Gruppe ist
voll und
kann nicht
beigetreten
werden.</h3>
</div>
</div>
</main>

View File

@ -23,7 +23,7 @@
<a href="/createStudent" th:href="@{/gruppen2/createStudent}">Erstellen</a>
</li>
<li class="active">
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
<a th:href="@{/gruppen2/searchPage}" href="/searchPage">Suche</a>
</li>
</ul>
</nav>
@ -35,7 +35,7 @@
<h1>Gruppensuche</h1>
<div class="shadow-sm p-2"
style="border: 10px solid aliceblue; border-radius: 5px; background: aliceblue">
<form method="get" th:action="@{/gruppen2/findGroup}">
<form method="get" th:action="@{/gruppen2/search}">
<div class="form-group">
<label for="suchleiste">Suchbegriff:</label>
<input class="form-control" id="suchleiste"
@ -46,11 +46,9 @@
style="background: #52a1eb; border-style: none"
type="submit">Suchen
</button>
<button class="btn btn-primary"
style="background: deepskyblue; border-style: none"
type="submit">
<a href="/gruppen2/findGroup?suchbegriff=" style="color: white">Alle
anzeigen</a>
<button class="btn btn-primary" style="background: deepskyblue; border-style: none" type="submit">
<a href="/gruppen2/search?suchbegriff=" style="color: white">Alle
anzeigen</a>
</button>
</form>
</div>

View File

@ -14,6 +14,7 @@ import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.event.UpdateGroupDescriptionEvent;
import mops.gruppen2.domain.event.UpdateGroupTitleEvent;
import mops.gruppen2.domain.event.UpdateRoleEvent;
import mops.gruppen2.domain.event.UpdateUserLimitEvent;
import java.util.ArrayList;
import java.util.Collection;
@ -93,6 +94,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.addAll(addUserEvents(membercount, groupId));
return eventList;
@ -105,6 +107,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.addAll(addUserEvents(membercount, groupId));
return eventList;
@ -170,8 +173,7 @@ public class TestBuilder {
faker.random().hex(),
null,
GroupType.SIMPLE,
visibility,
10000000L
visibility
);
}
@ -185,8 +187,7 @@ public class TestBuilder {
faker.random().hex(),
null,
GroupType.LECTURE,
Visibility.PUBLIC,
10000000L
Visibility.PUBLIC
);
}
@ -286,6 +287,10 @@ public class TestBuilder {
);
}
public static Event updateUserLimitMaxEvent(UUID groupId) {
return new UpdateUserLimitEvent(groupId, firstname(), Long.MAX_VALUE);
}
public static Event updateRoleEvent(UUID groupId, String userId, Role role) {
return new UpdateRoleEvent(
groupId,

View File

@ -22,6 +22,7 @@ import static mops.gruppen2.TestBuilder.createPublicGroupEvent;
import static mops.gruppen2.TestBuilder.deleteGroupEvent;
import static mops.gruppen2.TestBuilder.deleteUserEvent;
import static mops.gruppen2.TestBuilder.updateGroupTitleEvent;
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;
@ -36,13 +37,13 @@ class APIControllerTest {
private EventRepository eventRepository;
@Autowired
private APIController apiController;
@Autowired
private EventStoreService eventStoreService;
@Autowired
private JdbcTemplate template;
@BeforeEach
void setUp() {
eventStoreService = new EventStoreService(eventRepository);
eventRepository.deleteAll();
//noinspection SqlResolve
template.execute("ALTER TABLE event ALTER COLUMN event_id RESTART WITH 1");
@ -61,6 +62,7 @@ class APIControllerTest {
@WithMockUser(username = "api_user", roles = "api_user")
void updateGroup_singleGroup() {
eventStoreService.saveAll(createPublicGroupEvent(uuidMock(0)),
updateUserLimitMaxEvent(uuidMock(0)),
addUserEvent(uuidMock(0)),
addUserEvent(uuidMock(0)),
addUserEvent(uuidMock(0)),
@ -69,7 +71,7 @@ class APIControllerTest {
assertThat(apiController.updateGroups(0L).getGroupList()).hasSize(1);
assertThat(apiController.updateGroups(4L).getGroupList()).hasSize(1);
assertThat(apiController.updateGroups(10L).getGroupList()).hasSize(0);
assertThat(apiController.updateGroups(0L).getStatus()).isEqualTo(5);
assertThat(apiController.updateGroups(0L).getStatus()).isEqualTo(6);
}
@ -77,9 +79,11 @@ class APIControllerTest {
@WithMockUser(username = "api_user", roles = "api_user")
void updateGroup_multipleGroups() {
eventStoreService.saveAll(createPublicGroupEvent(uuidMock(0)),
updateUserLimitMaxEvent(uuidMock(0)),
addUserEvent(uuidMock(0)),
addUserEvent(uuidMock(0)),
createPrivateGroupEvent(uuidMock(1)),
updateUserLimitMaxEvent(uuidMock(1)),
addUserEvent(uuidMock(1)),
addUserEvent(uuidMock(1)),
addUserEvent(uuidMock(1)));

View File

@ -8,6 +8,7 @@ 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.updateUserLimitMaxEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -17,9 +18,10 @@ class AddUserEventTest {
@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");
Group group = apply(createEvent, addEvent);
Group group = apply(createEvent, updateLimitEvent, addEvent);
assertThat(group.getMembers()).hasSize(1);
assertThat(group.getMembers().get(0).getGivenname()).isEqualTo("Thomas");
@ -30,11 +32,12 @@ class AddUserEventTest {
@Test
void applyEvent_userAlreadyExists() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEventA = addUserEvent(uuidMock(0), "A");
Event addEventB = addUserEvent(uuidMock(0), "B");
Event addEventC = addUserEvent(uuidMock(0), "A");
Group group = apply(createEvent, addEventA, addEventB);
Group group = apply(createEvent, updateLimitEvent, addEventA, addEventB);
assertThrows(UserAlreadyExistsException.class, () -> addEventA.apply(group));
assertThrows(UserAlreadyExistsException.class, () -> addEventC.apply(group));

View File

@ -17,15 +17,13 @@ class CreateGroupEventTest {
"A",
uuidMock(1),
GroupType.SIMPLE,
Visibility.PUBLIC,
100L);
Visibility.PUBLIC);
Group group = TestBuilder.apply(createEvent);
assertThat(group.getMembers()).hasSize(0);
assertThat(group.getType()).isEqualTo(GroupType.SIMPLE);
assertThat(group.getVisibility()).isEqualTo(Visibility.PUBLIC);
assertThat(group.getUserLimit()).isEqualTo(100);
assertThat(group.getId()).isEqualTo(uuidMock(0));
assertThat(group.getParent()).isEqualTo(uuidMock(1));
}

View File

@ -17,8 +17,7 @@ class DeleteGroupEventTest {
"A",
uuidMock(1),
GroupType.SIMPLE,
Visibility.PUBLIC,
100L);
Visibility.PUBLIC);
Event deleteEvent = new DeleteGroupEvent(uuidMock(0), "A");
Group group = TestBuilder.apply(createEvent, deleteEvent);

View File

@ -7,6 +7,7 @@ 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;
@ -16,10 +17,11 @@ class DeleteUserEventTest {
@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");
Group group = TestBuilder.apply(createEvent, addEvent, deleteEvent);
Group group = TestBuilder.apply(createEvent, updateLimitEvent, addEvent, deleteEvent);
assertThat(group.getMembers()).hasSize(0);
}
@ -27,10 +29,11 @@ class DeleteUserEventTest {
@Test
void applyEvent_userNotFound() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEvent = addUserEvent(uuidMock(0), "A");
Event deleteEvent = new DeleteUserEvent(uuidMock(0), "B");
Group group = TestBuilder.apply(createEvent, addEvent);
Group group = TestBuilder.apply(createEvent, updateLimitEvent, addEvent);
assertThrows(UserNotFoundException.class, () -> deleteEvent.apply(group));
assertThat(group.getMembers()).hasSize(1);

View File

@ -8,6 +8,7 @@ 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.updateUserLimitMaxEvent;
import static mops.gruppen2.TestBuilder.uuidMock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -17,10 +18,11 @@ class UpdateRoleEventTest {
@Test
void applyEvent() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEvent = addUserEvent(uuidMock(0), "A");
Event updateEvent = new UpdateRoleEvent(uuidMock(0), "A", Role.ADMIN);
Group group = apply(createEvent, addEvent, updateEvent);
Group group = apply(createEvent, updateLimitEvent, addEvent, updateEvent);
assertThat(group.getRoles().get("A")).isEqualTo(Role.ADMIN);
}
@ -28,10 +30,11 @@ class UpdateRoleEventTest {
@Test
void applyEvent_userNotFound() {
Event createEvent = createPublicGroupEvent(uuidMock(0));
Event updateLimitEvent = updateUserLimitMaxEvent(uuidMock(0));
Event addEvent = addUserEvent(uuidMock(0), "A");
Event updateEvent = new UpdateRoleEvent(uuidMock(0), "B", Role.ADMIN);
Group group = apply(createEvent, addEvent);
Group group = apply(createEvent, updateLimitEvent, addEvent);
assertThrows(UserNotFoundException.class, () -> updateEvent.apply(group));
assertThat(group.getRoles().get("A")).isEqualTo(Role.MEMBER);

View File

@ -1,6 +1,7 @@
package mops.gruppen2.service;
import mops.gruppen2.Gruppen2Application;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.dto.EventDTO;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.repository.EventRepository;
@ -93,7 +94,7 @@ class EventStoreServiceTest {
addUserEvent(uuidMock(3), "A"),
addUserEvent(uuidMock(3), "B"));
assertThat(eventStoreService.findExistingUserGroups("A")).hasSize(4);
assertThat(eventStoreService.findExistingUserGroups("B")).hasSize(1);
assertThat(eventStoreService.findExistingUserGroups(new User("A"))).hasSize(4);
assertThat(eventStoreService.findExistingUserGroups(new User("B"))).hasSize(1);
}
}

View File

@ -3,6 +3,7 @@ package mops.gruppen2.service;
import mops.gruppen2.Gruppen2Application;
import mops.gruppen2.TestBuilder;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.repository.EventRepository;
@ -58,7 +59,7 @@ class GroupServiceTest {
@BeforeEach
void setUp() {
groupService = new GroupService(eventStoreService, validationService, inviteService);
groupService = new GroupService(eventStoreService, inviteService);
eventRepository.deleteAll();
//noinspection SqlResolve
template.execute("ALTER TABLE event ALTER COLUMN event_id RESTART WITH 1");
@ -184,7 +185,7 @@ class GroupServiceTest {
updateGroupTitleEvent(uuidMock(0)),
updateGroupDescriptionEvent(uuidMock(0)));
assertThat(searchService.searchPublicGroups("", "jens")).isEmpty();
assertThat(searchService.searchPublicGroups("", new User("jens"))).isEmpty();
}
//TODO: SearchServiceTest
@ -193,7 +194,7 @@ class GroupServiceTest {
eventStoreService.saveAll(completePublicGroups(10, 0),
completePrivateGroups(10, 0));
assertThat(searchService.searchPublicGroups("", "jens")).hasSize(10);
assertThat(searchService.searchPublicGroups("", new User("jens"))).hasSize(10);
}
//TODO: SearchServiceTest
@ -207,9 +208,9 @@ class GroupServiceTest {
updateGroupDescriptionEvent(uuidMock(1), "KK"),
createPrivateGroupEvent());
assertThat(searchService.searchPublicGroups("A", "jesus")).hasSize(2);
assertThat(searchService.searchPublicGroups("F", "jesus")).hasSize(1);
assertThat(searchService.searchPublicGroups("Z", "jesus")).hasSize(0);
assertThat(searchService.searchPublicGroups("A", new User("jesus"))).hasSize(2);
assertThat(searchService.searchPublicGroups("F", new User("jesus"))).hasSize(1);
assertThat(searchService.searchPublicGroups("Z", new User("jesus"))).hasSize(0);
}
}