diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 8cbd8b6..cd60d60 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -223,7 +223,7 @@ - + diff --git a/docker-compose.yaml b/docker-compose.yaml index f1dea7a..0769adf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ version: "3.7" services: dbmysql: - image: mysql:5.7 + image: mysql:8.0 container_name: 'dbmysql' environment: MYSQL_DATABASE: 'gruppen2' diff --git a/src/main/java/mops/gruppen2/controller/APIController.java b/src/main/java/mops/gruppen2/controller/APIController.java index a70b2db..34649c7 100644 --- a/src/main/java/mops/gruppen2/controller/APIController.java +++ b/src/main/java/mops/gruppen2/controller/APIController.java @@ -3,14 +3,15 @@ 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.event.Event; import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.service.APIFormatterService; -import mops.gruppen2.service.EventService; -import mops.gruppen2.service.GroupService; -import mops.gruppen2.service.UserService; +import mops.gruppen2.service.APIService; +import mops.gruppen2.service.EventStoreService; +import mops.gruppen2.service.IdService; +import mops.gruppen2.service.ProjectionService; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -19,7 +20,6 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * Api zum Datenabgleich mit Gruppenfindung. @@ -27,48 +27,43 @@ import java.util.stream.Collectors; //TODO: API-Service? @RestController @RequestMapping("/gruppen2/api") +@Log4j2 public class APIController { - private final EventService eventService; - private final GroupService groupService; - private final UserService userService; + private final EventStoreService eventStoreService; + private final ProjectionService projectionService; - public APIController(EventService eventService, GroupService groupService, UserService userService) { - this.eventService = eventService; - this.groupService = groupService; - this.userService = userService; + public APIController(EventStoreService eventStoreService, ProjectionService projectionService) { + this.eventStoreService = eventStoreService; + this.projectionService = projectionService; } @GetMapping("/updateGroups/{lastEventId}") @Secured("ROLE_api_user") @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 { - List events = eventService.getNewEvents(lastEventId); - - return APIFormatterService.wrap(eventService.getMaxEventId(), groupService.projectEventList(events)); + 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)); } @GetMapping("/getGroupIdsOfUser/{userId}") @Secured("ROLE_api_user") @ApiOperation("Gibt alle Gruppen zurück, in denen sich ein Teilnehmer befindet") - public List getGroupIdsOfUser(@ApiParam("Teilnehmer dessen groupIds zurückgegeben werden sollen") @PathVariable String userId) { - return userService.getUserGroups(userId).stream() - .map(group -> group.getId().toString()) - .collect(Collectors.toList()); + public List getGroupIdsOfUser(@ApiParam("Teilnehmer dessen groupIds zurückgegeben werden sollen") + @PathVariable String userId) { + log.info("ApiRequest to /getGroupIdsOfUser\n"); + return IdService.uuidsToString(eventStoreService.findExistingUserGroups(new User(userId))); } @GetMapping("/getGroup/{groupId}") @Secured("ROLE_api_user") @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 { - List eventList = eventService.getEventsOfGroup(UUID.fromString(groupId)); - List groups = groupService.projectEventList(eventList); - - if (groups.isEmpty()) { - return null; - } - - return groups.get(0); + 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)); } } diff --git a/src/main/java/mops/gruppen2/controller/GroupCreationController.java b/src/main/java/mops/gruppen2/controller/GroupCreationController.java index ed7fcbf..4920d51 100644 --- a/src/main/java/mops/gruppen2/controller/GroupCreationController.java +++ b/src/main/java/mops/gruppen2/controller/GroupCreationController.java @@ -1,9 +1,14 @@ package mops.gruppen2.controller; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.Account; -import mops.gruppen2.service.ControllerService; +import mops.gruppen2.domain.Group; +import mops.gruppen2.domain.GroupType; +import mops.gruppen2.domain.User; +import mops.gruppen2.service.CsvService; import mops.gruppen2.service.GroupService; -import mops.gruppen2.service.KeyCloakService; +import mops.gruppen2.service.IdService; +import mops.gruppen2.service.ProjectionService; import mops.gruppen2.service.ValidationService; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.cache.annotation.CacheEvict; @@ -17,21 +22,25 @@ 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; + +@SuppressWarnings("SameReturnValue") @Controller @SessionScope @RequestMapping("/gruppen2") +@Log4j2 public class GroupCreationController { private final GroupService groupService; - private final ControllerService controllerService; - private final ValidationService validationService; + private final ProjectionService projectionService; - public GroupCreationController(GroupService groupService, ControllerService controllerService, ValidationService validationService) { + public GroupCreationController(GroupService groupService, ProjectionService projectionService) { this.groupService = groupService; - this.controllerService = controllerService; - this.validationService = validationService; + this.projectionService = projectionService; } @RolesAllowed({"ROLE_orga", "ROLE_actuator"}) @@ -39,10 +48,10 @@ public class GroupCreationController { public String createGroupAsOrga(KeycloakAuthenticationToken token, Model model) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("GET to /createOrga\n"); - model.addAttribute("account", account); - model.addAttribute("lectures", groupService.getAllLecturesWithVisibilityPublic()); + model.addAttribute("account", new Account(token)); + model.addAttribute("lectures", projectionService.projectLectures()); return "createOrga"; } @@ -53,28 +62,29 @@ public class GroupCreationController { public String postCrateGroupAsOrga(KeycloakAuthenticationToken token, @RequestParam("title") String title, @RequestParam("description") String description, - @RequestParam(value = "visibility", required = false) Boolean visibility, - @RequestParam(value = "lecture", required = false) Boolean lecture, - @RequestParam("userMaximum") Long userMaximum, - @RequestParam(value = "maxInfiniteUsers", required = false) Boolean maxInfiniteUsers, - @RequestParam(value = "parent", required = false) String parent, + @RequestParam("visibility") boolean isPrivate, + @RequestParam("lecture") boolean isLecture, + @RequestParam("maxInfiniteUsers") boolean isInfinite, + @RequestParam("userMaximum") long userLimit, + @RequestParam("parent") String parent, @RequestParam(value = "file", required = false) MultipartFile file) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - UUID parentUUID = controllerService.getUUID(parent); + log.info("POST to /createOrga\n"); - validationService.checkFields(description, title, userMaximum, maxInfiniteUsers); + Account account = new Account(token); + User user = new User(account); - controllerService.createGroupAsOrga(account, - title, - description, - visibility, - lecture, - maxInfiniteUsers, - userMaximum, - parentUUID, - file); - return "redirect:/gruppen2"; + Group group = groupService.createGroup(user, + title, + description, + getVisibility(isPrivate), + getGroupType(isLecture), + getUserLimit(isInfinite, userLimit), + getParent(parent, isLecture)); + + groupService.addUsersToGroup(CsvService.readCsvFile(file), group, user); + + return "redirect:/gruppen2/details/" + IdService.uuidToString(group.getId()); } @RolesAllowed("ROLE_studentin") @@ -82,10 +92,10 @@ public class GroupCreationController { public String createGroupAsStudent(KeycloakAuthenticationToken token, Model model) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("GET to /createStudent\n"); - model.addAttribute("account", account); - model.addAttribute("lectures", groupService.getAllLecturesWithVisibilityPublic()); + model.addAttribute("account", new Account(token)); + model.addAttribute("lectures", projectionService.projectLectures()); return "createStudent"; } @@ -96,25 +106,26 @@ public class GroupCreationController { public String postCreateGroupAsStudent(KeycloakAuthenticationToken token, @RequestParam("title") String title, @RequestParam("description") String description, - @RequestParam("userMaximum") Long userMaximum, - @RequestParam(value = "visibility", required = false) Boolean visibility, - @RequestParam(value = "maxInfiniteUsers", required = false) Boolean maxInfiniteUsers, - @RequestParam(value = "parent", required = false) String parent) { + @RequestParam("visibility") boolean isPrivate, + @RequestParam("maxInfiniteUsers") boolean isInfinite, + @RequestParam("userMaximum") long userLimit, + @RequestParam("parent") String parent) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - UUID parentUUID = controllerService.getUUID(parent); + log.info("POST to /createStudent\n"); - validationService.checkFields(description, title, userMaximum, maxInfiniteUsers); + ValidationService.validateTitle(title); + ValidationService.validateDescription(description); - controllerService.createGroup(account, - title, - description, - visibility, - null, - maxInfiniteUsers, - userMaximum, - parentUUID); + Account account = new Account(token); + User user = new User(account); + Group group = groupService.createGroup(user, + title, + description, + getVisibility(isPrivate), + GroupType.SIMPLE, + getUserLimit(isInfinite, userLimit), + getParent(parent, false)); - return "redirect:/gruppen2"; + return "redirect:/gruppen2/details/" + IdService.uuidToString(group.getId()); } } diff --git a/src/main/java/mops/gruppen2/controller/GroupDetailsController.java b/src/main/java/mops/gruppen2/controller/GroupDetailsController.java index 008851e..9f5bacb 100644 --- a/src/main/java/mops/gruppen2/controller/GroupDetailsController.java +++ b/src/main/java/mops/gruppen2/controller/GroupDetailsController.java @@ -1,14 +1,16 @@ package mops.gruppen2.controller; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.Account; import mops.gruppen2.domain.Group; import mops.gruppen2.domain.Role; import mops.gruppen2.domain.User; import mops.gruppen2.domain.Visibility; -import mops.gruppen2.service.ControllerService; +import mops.gruppen2.service.CsvService; +import mops.gruppen2.service.GroupService; +import mops.gruppen2.service.IdService; import mops.gruppen2.service.InviteService; -import mops.gruppen2.service.KeyCloakService; -import mops.gruppen2.service.UserService; +import mops.gruppen2.service.ProjectionService; import mops.gruppen2.service.ValidationService; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.cache.annotation.CacheEvict; @@ -26,21 +28,21 @@ import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; import java.util.UUID; +@SuppressWarnings("SameReturnValue") @Controller @SessionScope @RequestMapping("/gruppen2") +@Log4j2 public class GroupDetailsController { - private final ControllerService controllerService; - private final UserService userService; - private final ValidationService validationService; private final InviteService inviteService; + private final GroupService groupService; + private final ProjectionService projectionService; - public GroupDetailsController(ControllerService controllerService, UserService userService, ValidationService validationService, InviteService inviteService) { - this.controllerService = controllerService; - this.userService = userService; - this.validationService = validationService; + public GroupDetailsController(InviteService inviteService, GroupService groupService, ProjectionService projectionService) { this.inviteService = inviteService; + this.groupService = groupService; + this.projectionService = projectionService; } @RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"}) @@ -50,36 +52,38 @@ public class GroupDetailsController { HttpServletRequest request, @PathVariable("id") String groupId) { - Group group = userService.getGroupById(UUID.fromString(groupId)); - Account account = KeyCloakService.createAccountFromPrincipal(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 = controllerService.getParent(parentId); + log.info("GET to /details\n"); - validationService.throwIfGroupNotExisting(group.getTitle()); + Account account = new Account(token); + User user = new User(account); 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); - if (validationService.checkIfAdmin(group, user)) { - model.addAttribute("link", serverURL + "gruppen2/acceptinvite/" + inviteService.getLinkByGroupId(group.getId())); + // Invitelink Anzeige für Admins + if (ValidationService.checkIfAdmin(group, user)) { + String actualURL = request.getRequestURL().toString(); + String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/")); + + model.addAttribute("link", serverURL + "gruppen2/acceptinvite/" + inviteService.getLinkByGroup(group)); } return "detailsMember"; @@ -91,11 +95,13 @@ public class GroupDetailsController { Model model, @PathVariable("id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - User user = new User(account); - Group group = userService.getGroupById(UUID.fromString(groupId)); + log.info("GET to /details/changeMetadata\n"); - validationService.throwIfNoAdmin(group, user); + Account account = new Account(token); + User user = new User(account); + + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + ValidationService.throwIfNoAdmin(group, user); model.addAttribute("account", account); model.addAttribute("title", group.getTitle()); @@ -116,14 +122,15 @@ public class GroupDetailsController { @RequestParam("description") String description, @RequestParam("groupId") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("POST to /details/changeMetadata\n"); + + Account account = new Account(token); User user = new User(account); - Group group = userService.getGroupById(UUID.fromString(groupId)); - validationService.throwIfNoAdmin(group, user); - validationService.checkFields(title, description); - - controllerService.changeMetaData(account, group, title, description); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + ValidationService.throwIfNoAdmin(group, user); + groupService.updateTitle(user, group, title); + groupService.updateDescription(user, group, description); return "redirect:/gruppen2/details/" + groupId; } @@ -134,11 +141,13 @@ public class GroupDetailsController { Model model, @PathVariable("id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - Group group = userService.getGroupById(UUID.fromString(groupId)); + log.info("GET to /details/members\n"); + + Account account = new Account(token); 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()); @@ -155,18 +164,17 @@ public class GroupDetailsController { @RequestParam("group_id") String groupId, @RequestParam("user_id") String userId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - Group group = userService.getGroupById(UUID.fromString(groupId)); - User principle = new User(account); - User user = new User(userId, "", "", ""); + log.info("POST to /details/members/changeRole\n"); - validationService.throwIfNoAdmin(group, principle); + Account account = new Account(token); + User user = new User(account); - //TODO: checkIfAdmin checkt nicht, dass die rolle geändert wurde. oder die rolle wird nicht geändert + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + ValidationService.throwIfNoAdmin(group, user); + groupService.toggleMemberRole(new User(userId), group); - controllerService.changeRole(account, 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; } @@ -177,15 +185,16 @@ public class GroupDetailsController { @PostMapping("/details/members/changeMaximum") @CacheEvict(value = "groups", allEntries = true) public String changeMaxSize(KeycloakAuthenticationToken token, - @RequestParam("maximum") Long maximum, + @RequestParam("maximum") long userLimit, @RequestParam("group_id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - Group group = userService.getGroupById(UUID.fromString(groupId)); + log.info("POST to /details/members/changeMaximum\n"); - validationService.throwIfNewMaximumIsValid(maximum, group); + Account account = new Account(token); + User user = new User(account); - controllerService.updateMaxUser(account, UUID.fromString(groupId), maximum); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + groupService.updateUserLimit(user, group, userLimit); return "redirect:/gruppen2/details/members/" + groupId; } @@ -197,17 +206,15 @@ public class GroupDetailsController { @RequestParam("group_id") String groupId, @RequestParam("user_id") String userId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - User principle = new User(account); - User user = new User(userId, "", "", ""); - Group group = userService.getGroupById(UUID.fromString(groupId)); + log.info("POST to /details/members/deleteUser\n"); - validationService.throwIfNoAdmin(group, principle); + Account account = new Account(token); + User user = new User(account); - controllerService.deleteUser(account, 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 +227,16 @@ public class GroupDetailsController { Model model, @RequestParam("id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("POST to /detailsBeitreten\n"); + + Account account = new Account(token); User user = new User(account); - Group group = userService.getGroupById(UUID.fromString(groupId)); - - validationService.throwIfUserAlreadyInGroup(group, user); - validationService.throwIfGroupFull(group); - - controllerService.addUser(account, UUID.fromString(groupId)); model.addAttribute("account", account); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + groupService.addUser(user, group); + return "redirect:/gruppen2"; } @@ -240,11 +246,13 @@ public class GroupDetailsController { public String leaveGroup(KeycloakAuthenticationToken token, @RequestParam("group_id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - User user = new User(account); - Group group = userService.getGroupById(UUID.fromString(groupId)); + log.info("POST to /leaveGroup\n"); - controllerService.deleteUser(account, user, group); + Account account = new Account(token); + User user = new User(account); + + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + groupService.deleteUser(user, group); return "redirect:/gruppen2"; } @@ -255,13 +263,13 @@ public class GroupDetailsController { public String deleteGroup(KeycloakAuthenticationToken token, @RequestParam("group_id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("POST to /deleteGroup\n"); + + Account account = new Account(token); User user = new User(account); - Group group = userService.getGroupById(UUID.fromString(groupId)); - validationService.throwIfNoAdmin(group, user); - - controllerService.deleteGroupEvent(user.getId(), UUID.fromString(groupId)); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); + groupService.deleteGroup(user, group); return "redirect:/gruppen2"; } @@ -273,8 +281,13 @@ public class GroupDetailsController { @RequestParam("group_id") String groupId, @RequestParam(value = "file", required = false) MultipartFile file) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - controllerService.addUsersFromCsv(account, file, groupId); + 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)); + groupService.addUsersToGroup(CsvService.readCsvFile(file), group, user); return "redirect:/gruppen2/details/members/" + groupId; } diff --git a/src/main/java/mops/gruppen2/controller/GruppenfindungController.java b/src/main/java/mops/gruppen2/controller/GruppenfindungController.java index c1e156a..b94ba1e 100644 --- a/src/main/java/mops/gruppen2/controller/GruppenfindungController.java +++ b/src/main/java/mops/gruppen2/controller/GruppenfindungController.java @@ -1,10 +1,11 @@ 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.KeyCloakService; -import mops.gruppen2.service.UserService; +import mops.gruppen2.service.ProjectionService; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -14,13 +15,15 @@ import javax.annotation.security.RolesAllowed; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +@SuppressWarnings("SameReturnValue") @Controller +@Log4j2 public class GruppenfindungController { - private final UserService userService; + private final ProjectionService projectionService; - public GruppenfindungController(UserService userService) { - this.userService = userService; + public GruppenfindungController(ProjectionService projectionService) { + this.projectionService = projectionService; } @GetMapping("") @@ -33,12 +36,15 @@ public class GruppenfindungController { public String index(KeycloakAuthenticationToken token, Model model) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("GET to /gruppen2\n"); + + Account account = new Account(token); User user = new User(account); model.addAttribute("account", account); - model.addAttribute("gruppen", userService.getUserGroups(user)); + model.addAttribute("gruppen", projectionService.projectUserGroups(user)); model.addAttribute("user", user); + model.addAttribute("lecture", GroupType.LECTURE); return "index"; } diff --git a/src/main/java/mops/gruppen2/controller/SearchAndInviteController.java b/src/main/java/mops/gruppen2/controller/SearchAndInviteController.java index ef351cc..c764f12 100644 --- a/src/main/java/mops/gruppen2/controller/SearchAndInviteController.java +++ b/src/main/java/mops/gruppen2/controller/SearchAndInviteController.java @@ -1,13 +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.ControllerService; +import mops.gruppen2.service.GroupService; import mops.gruppen2.service.InviteService; -import mops.gruppen2.service.KeyCloakService; -import mops.gruppen2.service.UserService; +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; @@ -21,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; +@SuppressWarnings("SameReturnValue") @Controller @SessionScope @RequestMapping("/gruppen2") +@Log4j2 public class SearchAndInviteController { - private final ValidationService validationService; private final InviteService inviteService; - private final UserService userService; - private final ControllerService controllerService; + private final GroupService groupService; + private final ProjectionService projectionService; + private final SearchService searchService; - public SearchAndInviteController(ValidationService validationService, InviteService inviteService, UserService userService, ControllerService controllerService) { - this.validationService = validationService; + public SearchAndInviteController(InviteService inviteService, GroupService groupService, ProjectionService projectionService, SearchService searchService) { this.inviteService = inviteService; - this.userService = userService; - this.controllerService = controllerService; + 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) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - List groups = new ArrayList<>(); - groups = validationService.checkSearch(search, groups, account); + log.info("GET to /searchPage\n"); + + Account account = new Account(token); + + 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 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"; } @@ -65,20 +88,26 @@ public class SearchAndInviteController { Model model, @RequestParam("id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); - Group group = userService.getGroupById(UUID.fromString(groupId)); - UUID parentId = group.getParent(); - Group parent = controllerService.getParent(parentId); + log.info("GET to /detailsSearch\n"); + + Account account = new Account(token); 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"; } @@ -89,17 +118,26 @@ public class SearchAndInviteController { Model model, @PathVariable("link") String link) { - Group group = userService.getGroupById(inviteService.getGroupIdFromLink(link)); + log.info("GET to /acceptInvite\n"); - validationService.throwIfGroupNotExisting(group.getTitle()); + Account account = new Account(token); + User user = new User(account); - model.addAttribute("account", KeyCloakService.createAccountFromPrincipal(token)); + Group group = projectionService.projectSingleGroup(inviteService.getGroupIdFromLink(link)); + + 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"; } @@ -109,14 +147,16 @@ public class SearchAndInviteController { public String postAcceptInvite(KeycloakAuthenticationToken token, @RequestParam("id") String groupId) { - Account account = KeyCloakService.createAccountFromPrincipal(token); + log.info("POST to /acceptInvite\n"); + + Account account = new Account(token); User user = new User(account); - Group group = userService.getGroupById(UUID.fromString(groupId)); + Group group = projectionService.projectSingleGroup(UUID.fromString(groupId)); - validationService.throwIfUserAlreadyInGroup(group, user); - validationService.throwIfGroupFull(group); + ValidationService.throwIfMember(group, user); + ValidationService.throwIfGroupFull(group); - controllerService.addUser(account, UUID.fromString(groupId)); + groupService.addUser(user, group); return "redirect:/gruppen2/details/" + groupId; } diff --git a/src/main/java/mops/gruppen2/domain/Account.java b/src/main/java/mops/gruppen2/domain/Account.java index ad24a7a..706427a 100644 --- a/src/main/java/mops/gruppen2/domain/Account.java +++ b/src/main/java/mops/gruppen2/domain/Account.java @@ -1,10 +1,14 @@ package mops.gruppen2.domain; +import lombok.AllArgsConstructor; import lombok.Value; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import java.util.Set; @Value +@AllArgsConstructor public class Account { String name; //user_id @@ -13,4 +17,14 @@ public class Account { String givenname; String familyname; Set roles; + + public Account(KeycloakAuthenticationToken token) { + KeycloakPrincipal principal = (KeycloakPrincipal) token.getPrincipal(); + name = principal.getName(); + email = principal.getKeycloakSecurityContext().getIdToken().getEmail(); + image = null; + givenname = principal.getKeycloakSecurityContext().getIdToken().getGivenName(); + familyname = principal.getKeycloakSecurityContext().getIdToken().getFamilyName(); + roles = token.getAccount().getRoles(); + } } diff --git a/src/main/java/mops/gruppen2/domain/Group.java b/src/main/java/mops/gruppen2/domain/Group.java index 9bfdd9c..9839947 100644 --- a/src/main/java/mops/gruppen2/domain/Group.java +++ b/src/main/java/mops/gruppen2/domain/Group.java @@ -1,7 +1,9 @@ package mops.gruppen2.domain; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import java.util.ArrayList; import java.util.HashMap; @@ -14,22 +16,30 @@ import java.util.UUID; */ @Getter @Setter +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString public class Group { - //TODO: List to Hashmap - private final List members; - private final Map roles; + @EqualsAndHashCode.Include private UUID id; - private String title; - private String description; - private Long userMaximum; - private GroupType type; - private Visibility visibility; + + @ToString.Exclude private UUID parent; - public Group() { - members = new ArrayList<>(); - roles = new HashMap<>(); - } + //TODO: Single Type for Public/Private/Lecture? + private GroupType type; + private Visibility visibility; + private String title; + private String description; + + // Default + Minimum: 1 + @ToString.Exclude + private long userLimit = 1; + + //TODO: List to Hashmap + @ToString.Exclude + private final List members = new ArrayList<>(); + @ToString.Exclude + private final Map roles = new HashMap<>(); } diff --git a/src/main/java/mops/gruppen2/domain/Role.java b/src/main/java/mops/gruppen2/domain/Role.java index 2b58e75..10d7856 100644 --- a/src/main/java/mops/gruppen2/domain/Role.java +++ b/src/main/java/mops/gruppen2/domain/Role.java @@ -2,5 +2,9 @@ package mops.gruppen2.domain; public enum Role { ADMIN, - MEMBER + MEMBER; + + public Role toggle() { + return this == ADMIN ? MEMBER : ADMIN; + } } diff --git a/src/main/java/mops/gruppen2/domain/User.java b/src/main/java/mops/gruppen2/domain/User.java index 16806b2..7cb9e77 100644 --- a/src/main/java/mops/gruppen2/domain/User.java +++ b/src/main/java/mops/gruppen2/domain/User.java @@ -4,16 +4,22 @@ import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; @Getter @AllArgsConstructor -@NoArgsConstructor -@EqualsAndHashCode(exclude = {"givenname", "familyname", "email"}) +@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) { @@ -22,4 +28,16 @@ public class User { familyname = account.getFamilyname(); email = account.getEmail(); } + + /** + * User identifizieren sich über die Id, mehr wird also manchmal nicht benötigt. + * + * @param userId Die User Id + */ + public User(String userId) { + id = userId; + givenname = ""; + familyname = ""; + email = ""; + } } diff --git a/src/main/java/mops/gruppen2/domain/api/GroupRequestWrapper.java b/src/main/java/mops/gruppen2/domain/api/GroupRequestWrapper.java index b56589c..b1993ef 100644 --- a/src/main/java/mops/gruppen2/domain/api/GroupRequestWrapper.java +++ b/src/main/java/mops/gruppen2/domain/api/GroupRequestWrapper.java @@ -13,6 +13,6 @@ import java.util.List; @Getter public class GroupRequestWrapper { - private final Long status; + private final long status; private final List groupList; } diff --git a/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java b/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java index b2f65be..71145e0 100644 --- a/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/AddUserEvent.java @@ -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; @@ -29,6 +33,13 @@ public class AddUserEvent extends Event { this.email = email; } + public AddUserEvent(Group group, User user) { + super(group.getId(), user.getId()); + givenname = user.getGivenname(); + familyname = user.getFamilyname(); + email = user.getEmail(); + } + @Override protected void applyEvent(Group group) throws EventException { User user = new User(userId, givenname, familyname, email); @@ -37,11 +48,14 @@ public class AddUserEvent extends Event { throw new UserAlreadyExistsException(getClass().toString()); } - if (group.getMembers().size() >= group.getUserMaximum()) { + if (group.getMembers().size() >= group.getUserLimit()) { throw new GroupFullException(getClass().toString()); } 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()); } } diff --git a/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java index e75c6e6..6b10dce 100644 --- a/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/CreateGroupEvent.java @@ -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 groupUserMaximum; - public CreateGroupEvent(UUID groupId, String userId, UUID parent, GroupType type, Visibility visibility, Long userMaximum) { + public CreateGroupEvent(UUID groupId, String userId, UUID parent, GroupType type, Visibility visibility) { super(groupId, userId); groupParent = parent; groupType = type; groupVisibility = visibility; - groupUserMaximum = userMaximum; } @Override @@ -31,6 +33,7 @@ public class CreateGroupEvent extends Event { group.setParent(groupParent); group.setType(groupType); group.setVisibility(groupVisibility); - group.setUserMaximum(groupUserMaximum); + + log.trace("\t\t\t\t\tNeue Gruppe: {}", group.toString()); } } diff --git a/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java b/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java index adc54f5..e356b36 100644 --- a/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/DeleteGroupEvent.java @@ -2,18 +2,27 @@ 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 java.util.UUID; @Getter @NoArgsConstructor // For Jackson +@ToString +@Log4j2 public class DeleteGroupEvent extends Event { public DeleteGroupEvent(UUID groupId, String userId) { super(groupId, userId); } + public DeleteGroupEvent(Group group, User user) { + super(group.getId(), user.getId()); + } + @Override protected void applyEvent(Group group) { group.getRoles().clear(); @@ -23,6 +32,8 @@ public class DeleteGroupEvent extends Event { group.setVisibility(null); group.setType(null); group.setParent(null); - group.setUserMaximum(0L); + group.setUserLimit(0L); + + log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group); } } diff --git a/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java b/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java index c12e1b2..b595f65 100644 --- a/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/DeleteUserEvent.java @@ -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,21 +16,32 @@ import java.util.UUID; */ @Getter @NoArgsConstructor // For Jackson +@ToString +@Log4j2 public class DeleteUserEvent extends Event { public DeleteUserEvent(UUID groupId, String userId) { super(groupId, userId); } + public DeleteUserEvent(Group group, User user) { + 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(this.userId)) { + 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; } } - throw new UserNotFoundException(this.getClass().toString()); + throw new UserNotFoundException(getClass().toString()); } } diff --git a/src/main/java/mops/gruppen2/domain/event/Event.java b/src/main/java/mops/gruppen2/domain/event/Event.java index bf1d8f5..b14f0aa 100644 --- a/src/main/java/mops/gruppen2/domain/event/Event.java +++ b/src/main/java/mops/gruppen2/domain/event/Event.java @@ -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" @@ -24,7 +26,7 @@ import java.util.UUID; @JsonSubTypes.Type(value = UpdateGroupTitleEvent.class, name = "UpdateGroupTitleEvent"), @JsonSubTypes.Type(value = UpdateRoleEvent.class, name = "UpdateRoleEvent"), @JsonSubTypes.Type(value = DeleteGroupEvent.class, name = "DeleteGroupEvent"), - @JsonSubTypes.Type(value = UpdateUserMaxEvent.class, name = "UpdateUserMaxEvent") + @JsonSubTypes.Type(value = UpdateUserLimitEvent.class, name = "UpdateUserLimitEvent") }) @Getter @NoArgsConstructor @@ -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; diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java index bbd9f6d..92f5b88 100644 --- a/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/UpdateGroupDescriptionEvent.java @@ -2,7 +2,10 @@ 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; import java.util.UUID; @@ -12,6 +15,8 @@ import java.util.UUID; */ @Getter @NoArgsConstructor // For Jackson +@ToString +@Log4j2 public class UpdateGroupDescriptionEvent extends Event { private String newGroupDescription; @@ -21,6 +26,11 @@ public class UpdateGroupDescriptionEvent extends Event { this.newGroupDescription = newGroupDescription.trim(); } + public UpdateGroupDescriptionEvent(Group group, User user, String newGroupDescription) { + super(group.getId(), user.getId()); + this.newGroupDescription = newGroupDescription.trim(); + } + @Override protected void applyEvent(Group group) { if (newGroupDescription.isEmpty()) { @@ -28,5 +38,7 @@ public class UpdateGroupDescriptionEvent extends Event { } group.setDescription(newGroupDescription); + + log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription()); } } diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java index 689e55f..385e2ac 100644 --- a/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/UpdateGroupTitleEvent.java @@ -2,7 +2,10 @@ 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; import java.util.UUID; @@ -12,6 +15,8 @@ import java.util.UUID; */ @Getter @NoArgsConstructor // For Jackson +@ToString +@Log4j2 public class UpdateGroupTitleEvent extends Event { private String newGroupTitle; @@ -21,6 +26,11 @@ public class UpdateGroupTitleEvent extends Event { this.newGroupTitle = newGroupTitle.trim(); } + public UpdateGroupTitleEvent(Group group, User user, String newGroupTitle) { + super(group.getId(), user.getId()); + this.newGroupTitle = newGroupTitle.trim(); + } + @Override protected void applyEvent(Group group) { if (newGroupTitle.isEmpty()) { @@ -28,6 +38,8 @@ public class UpdateGroupTitleEvent extends Event { } group.setTitle(newGroupTitle); + + log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle()); } } diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java index e7703e3..8c4af48 100644 --- a/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java +++ b/src/main/java/mops/gruppen2/domain/event/UpdateRoleEvent.java @@ -2,8 +2,11 @@ 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; import mops.gruppen2.domain.exception.UserNotFoundException; import java.util.UUID; @@ -13,6 +16,8 @@ import java.util.UUID; */ @Getter @NoArgsConstructor // For Jackson +@ToString +@Log4j2 public class UpdateRoleEvent extends Event { private Role newRole; @@ -22,10 +27,18 @@ public class UpdateRoleEvent extends Event { this.newRole = newRole; } + public UpdateRoleEvent(Group group, User user, Role newRole) { + super(group.getId(), user.getId()); + this.newRole = newRole; + } + @Override 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; } diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateUserLimitEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateUserLimitEvent.java new file mode 100644 index 0000000..f87de1d --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/event/UpdateUserLimitEvent.java @@ -0,0 +1,42 @@ +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; +import mops.gruppen2.domain.exception.EventException; + +import java.util.UUID; + +@Getter +@NoArgsConstructor +@ToString +@Log4j2 +public class UpdateUserLimitEvent extends Event { + + private long userLimit; + + public UpdateUserLimitEvent(UUID groupId, String userId, long userLimit) { + super(groupId, userId); + this.userLimit = userLimit; + } + + public UpdateUserLimitEvent(Group group, User user, long userLimit) { + super(group.getId(), user.getId()); + this.userLimit = userLimit; + } + + @Override + protected void applyEvent(Group group) throws EventException { + if (userLimit <= 0 || userLimit < group.getMembers().size()) { + throw new BadParameterException("Usermaximum zu klein."); + } + + group.setUserLimit(userLimit); + + log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getUserLimit()); + } +} diff --git a/src/main/java/mops/gruppen2/domain/event/UpdateUserMaxEvent.java b/src/main/java/mops/gruppen2/domain/event/UpdateUserMaxEvent.java deleted file mode 100644 index f9df452..0000000 --- a/src/main/java/mops/gruppen2/domain/event/UpdateUserMaxEvent.java +++ /dev/null @@ -1,30 +0,0 @@ -package mops.gruppen2.domain.event; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import mops.gruppen2.domain.Group; -import mops.gruppen2.domain.exception.BadParameterException; -import mops.gruppen2.domain.exception.EventException; - -import java.util.UUID; - -@Getter -@NoArgsConstructor -public class UpdateUserMaxEvent extends Event { - - private Long userMaximum; - - public UpdateUserMaxEvent(UUID groupId, String userId, Long userMaximum) { - super(groupId, userId); - this.userMaximum = userMaximum; - } - - @Override - protected void applyEvent(Group group) throws EventException { - if (userMaximum <= 0 || userMaximum < group.getMembers().size()) { - throw new BadParameterException("Usermaximum zu klein."); - } - - group.setUserMaximum(userMaximum); - } -} diff --git a/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java b/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java new file mode 100644 index 0000000..15448b4 --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/exception/BadPayloadException.java @@ -0,0 +1,10 @@ +package mops.gruppen2.domain.exception; + +import org.springframework.http.HttpStatus; + +public class BadPayloadException extends EventException { + + public BadPayloadException(String info) { + super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Payload konnte nicht übersetzt werden!", info); + } +} diff --git a/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java b/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java index 29209bd..bb8b028 100644 --- a/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java +++ b/src/main/java/mops/gruppen2/domain/exception/UserNotFoundException.java @@ -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); } } diff --git a/src/main/java/mops/gruppen2/repository/EventRepository.java b/src/main/java/mops/gruppen2/repository/EventRepository.java index 775991f..13bd990 100644 --- a/src/main/java/mops/gruppen2/repository/EventRepository.java +++ b/src/main/java/mops/gruppen2/repository/EventRepository.java @@ -11,30 +11,61 @@ import java.util.List; @Repository public interface EventRepository extends CrudRepository { - @Query("SELECT distinct group_id FROM event WHERE user_id =:id AND event_type = :type") - List findGroupIdsWhereUserId(@Param("id") String userId, @Param("type") String type); + // ####################################### GROUP IDs ######################################### - @Query("SELECT * from event WHERE group_id =:id") - List findEventDTOByGroupId(@Param("id") String groupId); + @Query("SELECT DISTINCT group_id FROM event" + + " WHERE user_id = :userId AND event_type = :type") + List findGroupIdsByUserAndType(@Param("userId") String userId, + @Param("type") String type); - @Query("SELECT DISTINCT group_id FROM event WHERE event_id > :status") - List findNewEventSinceStatus(@Param("status") Long status); + @Query("SELECT DISTINCT group_id FROM event" + + " WHERE event_id > :status") + List findGroupIdsWhereEventIdGreaterThanStatus(@Param("status") long status); - @Query("SELECT * FROM event WHERE group_id IN (:groupIds) ") - List findAllEventsOfGroups(@Param("groupIds") List groupIds); + // ####################################### EVENT DTOs ######################################## + + @Query("SELECT * FROM event" + + " WHERE group_id IN (:groupIds) ") + List findEventDTOsByGroup(@Param("groupIds") List groupIds); + + @Query("SELECT * FROM event" + + " WHERE group_id IN (:userIds) ") + List findEventDTOsByUser(@Param("groupIds") List userIds); + + @Query("SELECT * FROM event" + + " WHERE event_type IN (:types)") + List findEventDTOsByType(@Param("types") List types); + + @Query("SELECT * FROM event" + + " WHERE event_type IN (:types) AND group_id IN (:groupIds)") + List findEventDTOsByGroupAndType(@Param("types") List types, + @Param("groupIds") List groupIds); + + @Query("SELECT * FROM event" + + " WHERE event_type IN (:types) AND user_id = :userId") + List findEventDTOsByUserAndType(@Param("types") List types, + @Param("userId") String userId); + + // ################################ 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')" + + ")" + + "SELECT * FROM ranked_events WHERE rn = 1;") + List findLatestEventDTOsPartitionedByGroupByUser(@Param("userId") String userId); + + @Query("WITH ranked_events AS (" + + "SELECT *, ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY event_id DESC) AS rn" + + " FROM event" + + " WHERE event_type IN (:types)" + + ")" + + "SELECT * FROM ranked_events WHERE rn = 1;") + List findLatestEventDTOsPartitionedByGroupByType(@Param("types") List types); + + // ######################################### COUNT ########################################### @Query("SELECT MAX(event_id) FROM event") - Long getHighesEventID(); - - @Query("SELECT * FROM event WHERE event_type = :type") - List findAllEventsByType(@Param("type") String type); - - @Query("SELECT * FROM event WHERE event_type = :type AND user_id = :userId") - List findEventsByTypeAndUserId(@Param("type") String type, @Param("userId") String userId); - - @Query("SELECT COUNT(*) FROM event WHERE event_type = :type AND group_id = :groupId") - Long countEventsByTypeAndGroupId(@Param("type") String type, @Param("groupId") String groupId); - - @Query("SELECT COUNT(*) FROM event WHERE group_id = :groupId AND user_id = :userId AND event_type = :type") - Long countEventsByGroupIdAndUserIdAndEventType(@Param("groupId") String groupId, @Param("userId") String userId, @Param("type") String type); + Long findMaxEventId(); } diff --git a/src/main/java/mops/gruppen2/service/APIFormatterService.java b/src/main/java/mops/gruppen2/service/APIService.java similarity index 78% rename from src/main/java/mops/gruppen2/service/APIFormatterService.java rename to src/main/java/mops/gruppen2/service/APIService.java index 2985a4f..c56e694 100644 --- a/src/main/java/mops/gruppen2/service/APIFormatterService.java +++ b/src/main/java/mops/gruppen2/service/APIService.java @@ -1,5 +1,6 @@ package mops.gruppen2.service; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.Group; import mops.gruppen2.domain.api.GroupRequestWrapper; import org.springframework.stereotype.Service; @@ -7,9 +8,10 @@ import org.springframework.stereotype.Service; import java.util.List; @Service -public final class APIFormatterService { +@Log4j2 +public class APIService { - private APIFormatterService() {} + private APIService() {} public static GroupRequestWrapper wrap(long status, List groupList) { return new GroupRequestWrapper(status, groupList); diff --git a/src/main/java/mops/gruppen2/service/ControllerService.java b/src/main/java/mops/gruppen2/service/ControllerService.java index 0d932d3..424dd0e 100644 --- a/src/main/java/mops/gruppen2/service/ControllerService.java +++ b/src/main/java/mops/gruppen2/service/ControllerService.java @@ -1,359 +1,44 @@ package mops.gruppen2.service; -import mops.gruppen2.domain.Account; -import mops.gruppen2.domain.Group; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.GroupType; -import mops.gruppen2.domain.Role; -import mops.gruppen2.domain.User; import mops.gruppen2.domain.Visibility; -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.UpdateGroupDescriptionEvent; -import mops.gruppen2.domain.event.UpdateGroupTitleEvent; -import mops.gruppen2.domain.event.UpdateRoleEvent; -import mops.gruppen2.domain.event.UpdateUserMaxEvent; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.exception.WrongFileException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; - -import static mops.gruppen2.domain.Role.ADMIN; @Service -public class ControllerService { +@Log4j2 +public final class ControllerService { - private static final Logger LOG = LoggerFactory.getLogger("controllerServiceLogger"); - private final EventService eventService; - private final UserService userService; - private final ValidationService validationService; - private final InviteService inviteService; + private ControllerService() {} - public ControllerService(EventService eventService, UserService userService, ValidationService validationService, InviteService inviteService) { - this.eventService = eventService; - this.userService = userService; - this.validationService = validationService; - this.inviteService = inviteService; + public static Visibility getVisibility(boolean isPrivate) { + return isPrivate ? Visibility.PRIVATE : Visibility.PUBLIC; } - private static User getVeteranMember(Account account, Group group) { - List members = group.getMembers(); - String newAdminId; - if (members.get(0).getId().equals(account.getName())) { - newAdminId = members.get(1).getId(); - } else { - newAdminId = members.get(0).getId(); - } - return new User(newAdminId, "", "", ""); + public static GroupType getGroupType(boolean isLecture) { + return isLecture ? GroupType.LECTURE : GroupType.SIMPLE; } /** - * Wie createGroup, nur das hier die Gruppe auch als Veranstaltung gesetzt werden kann und CSV Dateien mit Nutzern - * eingelesen werden können. + * Wenn die maximale Useranzahl unendlich ist, wird das Maximum auf 100000 gesetzt. + * Praktisch gibt es also maximal 100000 Nutzer pro Gruppe. * - * @param account Der Nutzer der die Gruppe erstellt - * @param title Parameter für die neue Gruppe - * @param description Parameter für die neue Gruppe - * @param isVisibilityPrivate Parameter für die neue Gruppe - * @param isLecture Parameter für die neue Gruppe - * @param isMaximumInfinite Parameter für die neue Gruppe - * @param userMaximum Parameter für die neue Gruppe - * @param parent Parameter für die neue Gruppe - * @param file Parameter für die neue Gruppe - */ - public void createGroupAsOrga(Account account, - String title, - String description, - Boolean isVisibilityPrivate, - Boolean isLecture, - Boolean isMaximumInfinite, - Long userMaximum, - UUID parent, - MultipartFile file) { - - userMaximum = checkInfiniteUsers(isMaximumInfinite, userMaximum); - - List newUsers = readCsvFile(file); - - List oldUsers = new ArrayList<>(); - User user = new User(account); - oldUsers.add(user); - - removeOldUsersFromNewUsers(oldUsers, newUsers); - - userMaximum = adjustUserMaximum((long) newUsers.size(), 1L, userMaximum); - - UUID groupId = createGroup(account, - title, - description, - isVisibilityPrivate, - isLecture, - isMaximumInfinite, - userMaximum, parent); - - addUserList(newUsers, groupId); - } - - /** - * Wenn die maximale Useranzahl unendlich ist, wird das Maximum auf 100000 gesetzt. Praktisch gibt es also Maximla 100000 - * Nutzer pro Gruppe. - * - * @param isMaximumInfinite Gibt an ob es unendlich viele User geben soll - * @param userMaximum Das Maximum an Usern, falls es eins gibt + * @param isInfinite Gibt an, ob es unendlich viele User geben soll + * @param userLimit Das Maximum an Usern, falls es eins gibt * * @return Maximum an Usern */ - private static Long checkInfiniteUsers(Boolean isMaximumInfinite, Long userMaximum) { - isMaximumInfinite = isMaximumInfinite != null; - - if (isMaximumInfinite) { - userMaximum = 100_000L; - } - - return userMaximum; + public static long getUserLimit(boolean isInfinite, long userLimit) { + return isInfinite ? Long.MAX_VALUE : userLimit; } /** - * 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. - * - * @param account Keycloak-Account - * @param title Gruppentitel - * @param description Gruppenbeschreibung + * Ermittelt die UUID des Parents, falls vorhanden. */ - //TODO: remove booleans - public UUID createGroup(Account account, - String title, - String description, - Boolean isVisibilityPrivate, - Boolean isLecture, - Boolean isMaximumInfinite, - Long userMaximum, - UUID parent) { - - userMaximum = checkInfiniteUsers(isMaximumInfinite, userMaximum); - - Visibility groupVisibility = setGroupVisibility(isVisibilityPrivate); - UUID groupId = UUID.randomUUID(); - - GroupType groupType = setGroupType(isLecture); - - CreateGroupEvent createGroupEvent = new CreateGroupEvent(groupId, - account.getName(), - parent, - groupType, - groupVisibility, - userMaximum); - eventService.saveEvent(createGroupEvent); - - inviteService.createLink(groupId); - - User user = new User(account.getName(), "", "", ""); - - addUser(account, groupId); - updateTitle(account, groupId, title); - updateDescription(account, groupId, description); - updateRole(user, groupId); - - return groupId; + public static UUID getParent(String parent, boolean isLecture) { + return isLecture ? IdService.emptyUUID() : IdService.stringToUUID(parent); } - - private static List readCsvFile(MultipartFile file) throws EventException { - if (file == null) { - return new ArrayList<>(); - } - if (!file.isEmpty()) { - try { - List userList = CsvService.read(file.getInputStream()); - return userList.stream().distinct().collect(Collectors.toList()); //filters duplicates from list - } catch (IOException ex) { - LOG.warn("File konnte nicht gelesen werden"); - throw new WrongFileException(file.getOriginalFilename()); - } - } - return new ArrayList<>(); - } - - private static void removeOldUsersFromNewUsers(List oldUsers, List newUsers) { - for (User oldUser : oldUsers) { - newUsers.remove(oldUser); - } - } - - private static Long adjustUserMaximum(Long newUsers, Long oldUsers, Long maxUsers) { - if (oldUsers + newUsers > maxUsers) { - maxUsers = oldUsers + newUsers; - } - return maxUsers; - } - - private void addUserList(List newUsers, UUID groupId) { - for (User user : newUsers) { - Group group = userService.getGroupById(groupId); - if (group.getMembers().contains(user)) { - LOG.info("Benutzer {} ist bereits in Gruppe", user.getId()); - } else { - AddUserEvent addUserEvent = new AddUserEvent(groupId, user.getId(), user.getGivenname(), user.getFamilyname(), user.getEmail()); - eventService.saveEvent(addUserEvent); - } - } - } - - private static Visibility setGroupVisibility(Boolean isVisibilityPrivate) { - isVisibilityPrivate = isVisibilityPrivate != null; - - if (isVisibilityPrivate) { - return Visibility.PRIVATE; - } else { - return Visibility.PUBLIC; - } - } - - private static GroupType setGroupType(Boolean isLecture) { - isLecture = isLecture != null; - if (isLecture) { - return GroupType.LECTURE; - } else { - return GroupType.SIMPLE; - } - } - - public void addUser(Account account, UUID groupId) { - AddUserEvent addUserEvent = new AddUserEvent(groupId, account.getName(), account.getGivenname(), account.getFamilyname(), account.getEmail()); - eventService.saveEvent(addUserEvent); - } - - private void updateTitle(Account account, UUID groupId, String title) { - UpdateGroupTitleEvent updateGroupTitleEvent = new UpdateGroupTitleEvent(groupId, account.getName(), title); - eventService.saveEvent(updateGroupTitleEvent); - } - - public void updateRole(User user, UUID groupId) throws EventException { - UpdateRoleEvent updateRoleEvent; - Group group = userService.getGroupById(groupId); - validationService.throwIfNotInGroup(group, user); - - if (group.getRoles().get(user.getId()) == ADMIN) { - updateRoleEvent = new UpdateRoleEvent(group.getId(), user.getId(), Role.MEMBER); - } else { - updateRoleEvent = new UpdateRoleEvent(group.getId(), user.getId(), ADMIN); - } - eventService.saveEvent(updateRoleEvent); - } - - private void updateDescription(Account account, UUID groupId, String description) { - UpdateGroupDescriptionEvent updateGroupDescriptionEvent = new UpdateGroupDescriptionEvent(groupId, account.getName(), description); - eventService.saveEvent(updateGroupDescriptionEvent); - } - - public void addUsersFromCsv(Account account, MultipartFile file, String groupId) { - Group group = userService.getGroupById(UUID.fromString(groupId)); - - List newUserList = readCsvFile(file); - removeOldUsersFromNewUsers(group.getMembers(), newUserList); - - UUID groupUUID = getUUID(groupId); - - Long newUserMaximum = adjustUserMaximum((long) newUserList.size(), (long) group.getMembers().size(), group.getUserMaximum()); - if (newUserMaximum > group.getUserMaximum()) { - updateMaxUser(account, groupUUID, newUserMaximum); - } - - addUserList(newUserList, groupUUID); - } - - public UUID getUUID(String id) { - return UUID.fromString(Objects.requireNonNullElse(id, "00000000-0000-0000-0000-000000000000")); - } - - public void updateMaxUser(Account account, UUID groupId, Long userMaximum) { - UpdateUserMaxEvent updateUserMaxEvent = new UpdateUserMaxEvent(groupId, account.getName(), userMaximum); - eventService.saveEvent(updateUserMaxEvent); - } - - public void changeMetaData(Account account, Group group, String title, String description) { - if (!title.equals(group.getTitle())) { - updateTitle(account, group.getId(), title); - } - - if (!description.equals(group.getDescription())) { - updateDescription(account, group.getId(), description); - } - } - - public Group getParent(UUID parentId) { - Group parent = new Group(); - if (!idIsEmpty(parentId)) { - parent = userService.getGroupById(parentId); - } - return parent; - } - - public void deleteUser(Account account, User user, Group group) throws EventException { - changeRoleIfLastAdmin(account, group); - - validationService.throwIfNotInGroup(group, user); - - deleteUserEvent(user, group.getId()); - - if (validationService.checkIfGroupEmpty(group.getId())) { - deleteGroupEvent(user.getId(), group.getId()); - } - } - - private static boolean idIsEmpty(UUID id) { - if (id == null) { - return true; - } - - return "00000000-0000-0000-0000-000000000000".equals(id.toString()); - } - - private void deleteUserEvent(User user, UUID groupId) { - DeleteUserEvent deleteUserEvent = new DeleteUserEvent(groupId, user.getId()); - eventService.saveEvent(deleteUserEvent); - } - - public void deleteGroupEvent(String userId, UUID groupId) { - DeleteGroupEvent deleteGroupEvent = new DeleteGroupEvent(groupId, userId); - inviteService.destroyLink(groupId); - eventService.saveEvent(deleteGroupEvent); - } - - private void promoteVeteranMember(Account account, Group group) { - if (validationService.checkIfLastAdmin(account, group)) { - User newAdmin = getVeteranMember(account, group); - updateRole(newAdmin, group.getId()); - } - } - - public void changeRoleIfLastAdmin(Account account, Group group) { - if (group.getMembers().size() <= 1) { - return; - } - promoteVeteranMember(account, group); - } - - public void changeRole(Account account, User user, Group group) { - if (user.getId().equals(account.getName())) { - if (group.getMembers().size() <= 1) { - validationService.throwIfLastAdmin(account, group); - } - promoteVeteranMember(account, group); - } - updateRole(user, group.getId()); - } - } diff --git a/src/main/java/mops/gruppen2/service/CsvService.java b/src/main/java/mops/gruppen2/service/CsvService.java index a476481..eb78aa1 100644 --- a/src/main/java/mops/gruppen2/service/CsvService.java +++ b/src/main/java/mops/gruppen2/service/CsvService.java @@ -3,19 +3,42 @@ package mops.gruppen2.service; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.User; +import mops.gruppen2.domain.exception.EventException; +import mops.gruppen2.domain.exception.WrongFileException; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; @Service +@Log4j2 public final class CsvService { private CsvService() {} - static List read(InputStream stream) throws IOException { + public static List readCsvFile(MultipartFile file) throws EventException { + if (file == null || file.isEmpty()) { + return Collections.emptyList(); + } + + try { + List userList = read(file.getInputStream()); + return userList.stream() + .distinct() + .collect(Collectors.toList()); //filter duplicates from list + } catch (IOException e) { + log.error("File konnte nicht gelesen werden!", e); + throw new WrongFileException(file.getOriginalFilename()); + } + } + + private static List read(InputStream stream) throws IOException { CsvMapper mapper = new CsvMapper(); CsvSchema schema = mapper.schemaFor(User.class).withHeader().withColumnReordering(true); diff --git a/src/main/java/mops/gruppen2/service/EventService.java b/src/main/java/mops/gruppen2/service/EventService.java deleted file mode 100644 index 5226519..0000000 --- a/src/main/java/mops/gruppen2/service/EventService.java +++ /dev/null @@ -1,168 +0,0 @@ -package mops.gruppen2.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import mops.gruppen2.domain.dto.EventDTO; -import mops.gruppen2.domain.event.Event; -import mops.gruppen2.repository.EventRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@Service -//TODO: Evtl aufsplitten in EventRepoService und EventService? -public class EventService { - - private static final Logger LOG = LoggerFactory.getLogger(EventService.class); - private final EventRepository eventStore; - - public EventService(EventRepository eventStore) { - this.eventStore = eventStore; - } - - /** - * Erzeugt ein DTO aus einem Event und speicher es. - * - * @param event Event, welches gespeichert wird - */ - public void saveEvent(Event event) { - eventStore.save(getDTOFromEvent(event)); - } - - public void saveAll(Event... events) { - for (Event event : events) { - eventStore.save(getDTOFromEvent(event)); - } - } - - /** - * Erzeugt aus einem Event Objekt ein EventDTO Objekt. - * - * @param event Event, welches in DTO übersetzt wird - * - * @return EventDTO (Neues DTO) - */ - public EventDTO getDTOFromEvent(Event event) { - String payload = ""; - try { - payload = JsonService.serializeEvent(event); - } catch (JsonProcessingException e) { - LOG.error("Event ({}) konnte nicht serialisiert werden!", event.getClass()); - } - - return new EventDTO(null, event.getGroupId().toString(), event.getUserId(), getEventType(event), payload); - } - - /** - * 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); - } - - /** - * Speichert alle Events aus der übergebenen Liste in der DB. - * - * @param events Liste an Events die gespeichert werden soll - */ - @SafeVarargs - public final void saveAll(List... events) { - for (List eventlist : events) { - for (Event event : eventlist) { - eventStore.save(getDTOFromEvent(event)); - } - } - } - - /** - * Findet alle Events welche ab dem neuen Status hinzugekommen sind. - * Sucht alle Events mit event_id > status - * - * @param status Die Id des zuletzt gespeicherten Events - * - * @return Liste von neueren Events - */ - public List getNewEvents(Long status) { - List groupIdsThatChanged = eventStore.findNewEventSinceStatus(status); - - List groupEventDTOS = eventStore.findAllEventsOfGroups(groupIdsThatChanged); - return getEventsFromDTOs(groupEventDTOS); - } - - /** - * Erzeugt aus einer Liste von eventDTOs eine Liste von Events. - * - * @param eventDTOS Liste von DTOs - * - * @return Liste von Events - */ - List getEventsFromDTOs(Iterable eventDTOS) { - List events = new ArrayList<>(); - - for (EventDTO eventDTO : eventDTOS) { - try { - events.add(JsonService.deserializeEvent(eventDTO.getEvent_payload())); - } catch (JsonProcessingException e) { - LOG.error("Payload\n {}\n konnte nicht deserialisiert werden!", eventDTO.getEvent_payload()); - } - } - return events; - } - - public long getMaxEventId() { - long highestEvent = 0; - - try { - highestEvent = eventStore.getHighesEventID(); - } catch (NullPointerException e) { - LOG.debug("Eine maxId von 0 wurde zurückgegeben, da keine Events vorhanden sind."); - } - - return highestEvent; - } - - /** - * Gibt eine Liste mit allen Events zurück, die zu der Gruppe gehören. - * - * @param groupId Gruppe die betrachtet werden soll - * - * @return Liste aus Events - */ - public List getEventsOfGroup(UUID groupId) { - List eventDTOList = eventStore.findEventDTOByGroupId(groupId.toString()); - return getEventsFromDTOs(eventDTOList); - } - - /** - * Gibt eine Liste aus GruppenIds zurück, in denen sich der User befindet. - * - * @param userId Die Id des Users - * - * @return Liste aus GruppenIds - */ - public List findGroupIdsByUser(String userId) { - return eventStore.findGroupIdsWhereUserId(userId, "AddUserEvent").stream().map(UUID::fromString).collect(Collectors.toList()); - } - - /** - * Gibt true zurück, falls der User aktuell in der Gruppe ist, sonst false. - * - * @param groupId Id der Gruppe - * @param userId Id des zu überprüfenden Users - * - * @return true or false - */ - boolean userInGroup(UUID groupId, String userId) { - return eventStore.countEventsByGroupIdAndUserIdAndEventType(groupId.toString(), userId, "AddUserEvent") > eventStore.countEventsByGroupIdAndUserIdAndEventType(groupId.toString(), userId, "DeleteUserEvent"); - } -} diff --git a/src/main/java/mops/gruppen2/service/EventStoreService.java b/src/main/java/mops/gruppen2/service/EventStoreService.java new file mode 100644 index 0000000..adfce38 --- /dev/null +++ b/src/main/java/mops/gruppen2/service/EventStoreService.java @@ -0,0 +1,259 @@ +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; +import mops.gruppen2.domain.event.Event; +import mops.gruppen2.domain.exception.BadPayloadException; +import mops.gruppen2.repository.EventRepository; +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 +@Log4j2 +public class EventStoreService { + + private final EventRepository eventStore; + + public EventStoreService(EventRepository eventStore) { + this.eventStore = eventStore; + } + + + //########################################### SAVE ########################################### + + + /** + * Erzeugt ein DTO aus einem Event und speicher es. + * + * @param event Event, welches gespeichert wird + */ + public void saveEvent(Event event) { + eventStore.save(getDTOFromEvent(event)); + } + + public void saveAll(Event... events) { + for (Event event : events) { + eventStore.save(getDTOFromEvent(event)); + } + } + + /** + * Speichert alle Events aus der übergebenen Liste in der DB. + * + * @param events Liste an Events die gespeichert werden soll + */ + @SafeVarargs + public final void saveAll(List... events) { + for (List eventlist : events) { + for (Event event : eventlist) { + eventStore.save(getDTOFromEvent(event)); + } + } + } + + + //########################################### DTOs ########################################### + + + static List getDTOsFromEvents(List events) { + return events.stream() + .map(EventStoreService::getDTOFromEvent) + .collect(Collectors.toList()); + } + + /** + * Erzeugt aus einem Event Objekt ein EventDTO Objekt. + * + * @param event Event, welches in DTO übersetzt wird + * + * @return EventDTO (Neues DTO) + */ + static EventDTO getDTOFromEvent(Event event) { + try { + String payload = JsonService.serializeEvent(event); + return new EventDTO(null, + event.getGroupId().toString(), + event.getUserId(), + getEventType(event), + payload); + } catch (JsonProcessingException e) { + log.error("Event ({}) konnte nicht serialisiert werden!", event, e); + throw new BadPayloadException(EventStoreService.class.toString()); + } + } + + /** + * Erzeugt aus einer Liste von eventDTOs eine Liste von Events. + * + * @param eventDTOS Liste von DTOs + * + * @return Liste von Events + */ + private static List getEventsFromDTOs(List eventDTOS) { + return eventDTOS.stream() + .map(EventStoreService::getEventFromDTO) + .collect(Collectors.toList()); + } + + private static Event getEventFromDTO(EventDTO dto) { + try { + return JsonService.deserializeEvent(dto.getEvent_payload()); + } catch (JsonProcessingException e) { + log.error("Payload {} konnte nicht deserialisiert werden!", dto.getEvent_payload(), e); + throw new BadPayloadException(EventStoreService.class.toString()); + } + } + + /** + * 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 ########################################## + + + List findGroupEvents(UUID groupId) { + return getEventsFromDTOs(eventStore.findEventDTOsByGroup(Collections.singletonList(groupId.toString()))); + } + + /** + * Sucht alle Events, welche zu einer der übergebenen Gruppen gehören. + * + * @param groupIds Liste an IDs + * + * @return Liste an Events + */ + List findGroupEvents(List groupIds) { + List eventDTOS = new ArrayList<>(); + + for (UUID groupId : groupIds) { + eventDTOS.addAll(eventStore.findEventDTOsByGroup(Collections.singletonList(groupId.toString()))); + } + + return getEventsFromDTOs(eventDTOS); + } + + /** + * Findet alle Events zu Gruppen, welche seit dem neuen Status verändert wurden. + * + * @param status Die Id des zuletzt gespeicherten Events + * + * @return Liste von neuen und alten Events + */ + List findChangedGroupEvents(long status) { + List changedGroupIds = eventStore.findGroupIdsWhereEventIdGreaterThanStatus(status); + List groupEventDTOS = eventStore.findEventDTOsByGroup(changedGroupIds); + + log.debug("Seit Event {} haben sich {} Gruppen geändert!", status, changedGroupIds.size()); + + return getEventsFromDTOs(groupEventDTOS); + } + + /** + * Liefert Gruppen-Ids von existierenden (ungelöschten) Gruppen. + * + * @return GruppenIds (UUID) als Liste + */ + List findExistingGroupIds() { + List createEvents = findLatestEventsFromGroupsByType("CreateGroupEvent", + "DeleteGroupEvent"); + + return createEvents.stream() + .filter(event -> event instanceof CreateGroupEvent) + .map(Event::getGroupId) + .collect(Collectors.toList()); + } + + /** + * Liefert Gruppen-Ids von existierenden (ungelöschten) Gruppen, in welchen der User teilnimmt. + * + * @return GruppenIds (UUID) als Liste + */ + public List findExistingUserGroups(User user) { + List userEvents = findLatestEventsFromGroupsByUser(user); + List 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) + .map(Event::getGroupId) + .collect(Collectors.toList()); + } + + + // #################################### SIMPLE QUERIES ####################################### + + + /** + * Ermittelt die Id zuletzt gespeicherten Events. + * + * @return Letzte EventId + */ + public long findMaxEventId() { + try { + return eventStore.findMaxEventId(); + } catch (NullPointerException e) { + log.debug("Keine Events vorhanden!"); + return 0; + } + } + + List findEventsByType(String... types) { + return getEventsFromDTOs(eventStore.findEventDTOsByType(Arrays.asList(types))); + } + + List findEventsByType(String type) { + return getEventsFromDTOs(eventStore.findEventDTOsByType(Collections.singletonList(type))); + } + + List findEventsByGroupAndType(List groupIds, String... types) { + return getEventsFromDTOs(eventStore.findEventDTOsByGroupAndType(Arrays.asList(types), + IdService.uuidsToString(groupIds))); + } + + /** + * Sucht zu jeder Gruppe das letzte Add- oder DeleteUserEvent heraus, welches den übergebenen User betrifft. + * + * @param user User, zu welchem die Events gesucht werden + * + * @return Eine Liste von einem Add- oder DeleteUserEvent pro Gruppe + */ + private List findLatestEventsFromGroupsByUser(User user) { + return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByUser(user.getId())); + } + + + /** + * Sucht zu jeder Gruppe das letzte Event des/der übergebenen Typen heraus. + * + * @param types Eventtyp, nach welchem gesucht wird + * + * @return Eine Liste von einem Event pro Gruppe + */ + private List findLatestEventsFromGroupsByType(String... types) { + return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByType(Arrays.asList(types))); + } +} diff --git a/src/main/java/mops/gruppen2/service/GroupService.java b/src/main/java/mops/gruppen2/service/GroupService.java index 03549ea..c4f8b71 100644 --- a/src/main/java/mops/gruppen2/service/GroupService.java +++ b/src/main/java/mops/gruppen2/service/GroupService.java @@ -1,170 +1,235 @@ package mops.gruppen2.service; -import mops.gruppen2.domain.Account; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.Group; import mops.gruppen2.domain.GroupType; +import mops.gruppen2.domain.Role; +import mops.gruppen2.domain.User; import mops.gruppen2.domain.Visibility; -import mops.gruppen2.domain.dto.EventDTO; +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.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 mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.repository.EventRepository; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -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; +/** + * Behandelt Aufgaben, welche sich auf eine Gruppe beziehen. + * Es werden übergebene Gruppen bearbeitet und dementsprechend Events erzeugt und gespeichert. + */ @Service +@Log4j2 public class GroupService { - private final EventService eventService; - private final EventRepository eventRepository; + private final EventStoreService eventStoreService; + private final InviteService inviteService; - public GroupService(EventService eventService, EventRepository eventRepository) { - this.eventService = eventService; - this.eventRepository = eventRepository; + 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, + String title, + String description, + Visibility visibility, + GroupType groupType, + long userLimit, + UUID parent) { + + // 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); + updateUserLimit(user, group, userLimit); + + inviteService.createLink(group); + + return group; + } + + + // ################################### GRUPPEN ÄNDERN ######################################## + + + /** + * Fügt eine Liste von Usern zu einer Gruppe hinzu. + * Duplikate werden übersprungen, die erzeugten Events werden gespeichert. + * Dabei wird das Teilnehmermaximum eventuell angehoben. + * + * @param newUsers Userliste + * @param group Gruppe + * @param user Ausführender User + */ + public void addUsersToGroup(List newUsers, Group group, User user) { + updateUserLimit(user, group, getAdjustedUserLimit(newUsers, group)); + + newUsers.forEach(newUser -> addUserSilent(newUser, group)); } /** - * Sucht in der DB alle Zeilen raus welche eine der Gruppen_ids hat. - * Wandelt die Zeilen in Events um und gibt davon eine Liste zurück. + * Ermittelt ein passendes Teilnehmermaximum. + * Reicht das alte Maximum, wird dieses zurückgegeben. + * Ansonsten wird ein erhöhtes Maximum zurückgegeben. * - * @param groupIds Liste an IDs + * @param newUsers Neue Teilnehmer + * @param group Bestehende Gruppe, welche verändert wird * - * @return Liste an Events + * @return Das neue Teilnehmermaximum */ - //TODO: Das vielleicht in den EventRepoService? - public List getGroupEvents(List groupIds) { - List eventDTOS = new ArrayList<>(); - for (UUID groupId : groupIds) { - eventDTOS.addAll(eventRepository.findEventDTOByGroupId(groupId.toString())); + private static long getAdjustedUserLimit(List newUsers, Group group) { + return Math.max((long) group.getMembers().size() + newUsers.size(), group.getUserLimit()); + } + + /** + * Wechselt die Rolle eines Teilnehmers von Admin zu Member oder andersherum. + * + * @param user 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 { + ValidationService.throwIfNoMember(group, user); + ValidationService.throwIfLastAdmin(user, group); + + Role role = group.getRoles().get(user.getId()); + updateRole(user, group, role.toggle()); + } + + + // ################################# SINGLE EVENTS ########################################### + // Spezifische Events werden erzeugt, validiert, auf die Gruppe angewandt und gespeichert + + + /** + * 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); + Group group = new Group(); + event.apply(group); + + eventStoreService.saveEvent(event); + + return group; + } + + public void addUser(User user, Group group) { + ValidationService.throwIfMember(group, user); + ValidationService.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) { + try { + addUser(user, group); + } catch (Exception e) { + log.debug("Doppelter User {} wurde nicht zu Gruppe {} hinzugefügt!", user, group); } - return eventService.getEventsFromDTOs(eventDTOS); } - /** - * Wird verwendet beim Gruppe erstellen bei der Parent-Auswahl: nur Titel benötigt. - * - * @return List of groups - */ - @Cacheable("groups") - public List getAllLecturesWithVisibilityPublic() { - List createEvents = eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("CreateGroupEvent")); - createEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("DeleteGroupEvent"))); - createEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("UpdateGroupTitleEvent"))); - createEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("DeleteGroupEvent"))); + public void deleteUser(User user, Group group) throws EventException { + ValidationService.throwIfNoMember(group, user); + ValidationService.throwIfLastAdmin(user, group); - List visibleGroups = projectEventList(createEvents); + if (ValidationService.checkIfGroupEmpty(group)) { + deleteGroup(user, group); + } else { + Event event = new DeleteUserEvent(group, user); + event.apply(group); - return visibleGroups.stream() - .filter(group -> group.getType() == GroupType.LECTURE) - .filter(group -> group.getVisibility() == Visibility.PUBLIC) - .collect(Collectors.toList()); + eventStoreService.saveEvent(event); + } } - /** - * Erzeugt eine neue Map wo Gruppen aus den Events erzeugt und den Gruppen_ids zugeordnet werden. - * Die Gruppen werden als Liste zurückgegeben. - * - * @param events Liste an Events - * - * @return Liste an Projizierten Gruppen - * - * @throws EventException Projektionsfehler - */ - public List projectEventList(List events) throws EventException { - Map groupMap = new HashMap<>(); + public void deleteGroup(User user, Group group) { + ValidationService.throwIfNoAdmin(group, user); - events.parallelStream() - .forEachOrdered(event -> event.apply(getOrCreateGroup(groupMap, event.getGroupId()))); + Event event = new DeleteGroupEvent(group, user); + event.apply(group); + inviteService.destroyLink(group); - return new ArrayList<>(groupMap.values()); + eventStoreService.saveEvent(event); } - /** - * Gibt die Gruppe mit der richtigen Id aus der übergebenen Map wieder, existiert diese nicht - * wird die Gruppe erstellt und der Map hizugefügt. - * - * @param groups Map aus GruppenIds und Gruppen - * @param groupId Die Id der Gruppe, die zurückgegeben werden soll - * - * @return Die gesuchte Gruppe - */ - private static Group getOrCreateGroup(Map groups, UUID groupId) { - if (!groups.containsKey(groupId)) { - groups.put(groupId, new Group()); + 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); + } + + 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); + + eventStoreService.saveEvent(event); + } + + private 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); + } + + public void updateUserLimit(User user, Group group, long userLimit) { + ValidationService.throwIfNoAdmin(group, user); + ValidationService.validateUserLimit(userLimit, group); + + if (userLimit == group.getUserLimit()) { + return; } - return groups.get(groupId); - } + Event event = new UpdateUserLimitEvent(group, user, userLimit); + event.apply(group); - /** - * Filtert alle öffentliche Gruppen nach dem Suchbegriff und gibt diese als Liste von Gruppen zurück. - * Groß und Kleinschreibung wird nicht beachtet. - * - * @param search Der Suchstring - * - * @return Liste von projizierten Gruppen - * - * @throws EventException Projektionsfehler - */ - //Todo Rename - @Cacheable("groups") - public List findGroupWith(String search, Account account) throws EventException { - if (search.isEmpty()) { - return getAllGroupWithVisibilityPublic(account.getName()); - } - - return getAllGroupWithVisibilityPublic(account.getName()).parallelStream().filter(group -> group.getTitle().toLowerCase().contains(search.toLowerCase()) || group.getDescription().toLowerCase().contains(search.toLowerCase())).collect(Collectors.toList()); - } - - /** - * Wird verwendet bei der Suche nach Gruppen: Titel, Beschreibung werden benötigt. - * Außerdem wird beachtet, ob der eingeloggte User bereits in entsprechenden Gruppen mitglied ist. - * - * @return Liste von projizierten Gruppen - * - * @throws EventException Projektionsfehler - */ - //TODO Rename - @Cacheable("groups") - public List getAllGroupWithVisibilityPublic(String userId) throws EventException { - List groupEvents = eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("CreateGroupEvent")); - groupEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("UpdateGroupDescriptionEvent"))); - groupEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("UpdateGroupTitleEvent"))); - groupEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("DeleteGroupEvent"))); - groupEvents.addAll(eventService.getEventsFromDTOs(eventRepository.findAllEventsByType("UpdateUserMaxEvent"))); - - List visibleGroups = projectEventList(groupEvents); - - sortByGroupType(visibleGroups); - - return visibleGroups.stream() - .filter(group -> group.getType() != null) - .filter(group -> !eventService.userInGroup(group.getId(), userId)) - .filter(group -> group.getVisibility() == Visibility.PUBLIC) - .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 - */ - void sortByGroupType(List groups) { - groups.sort((Group g1, Group g2) -> { - if (g1.getType() == GroupType.LECTURE) { - return -1; - } - if (g2.getType() == GroupType.LECTURE) { - return 0; - } - - return 1; - }); + eventStoreService.saveEvent(event); } } diff --git a/src/main/java/mops/gruppen2/service/IdService.java b/src/main/java/mops/gruppen2/service/IdService.java new file mode 100644 index 0000000..0611343 --- /dev/null +++ b/src/main/java/mops/gruppen2/service/IdService.java @@ -0,0 +1,51 @@ +package mops.gruppen2.service; + +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@Log4j2 +public final class IdService { + + private IdService() {} + + public static List stringsToUUID(List groupIds) { + return groupIds.stream() + .map(IdService::stringToUUID) + .collect(Collectors.toList()); + } + + /** + * Wandelt einen String in eine UUID um. + * Dabei wird eine "leere" UUID generiert, falls der String leer ist. + * + * @param groupId Id als String + * + * @return Id als UUID + */ + public static UUID stringToUUID(String groupId) { + return groupId.isEmpty() ? emptyUUID() : UUID.fromString(groupId); + } + + public static List uuidsToString(List groupIds) { + return groupIds.stream() + .map(UUID::toString) + .collect(Collectors.toList()); + } + + public static String uuidToString(UUID groupId) { + return groupId.toString(); + } + + public static boolean isEmpty(UUID id) { + return id == null || emptyUUID().equals(id); + } + + public static UUID emptyUUID() { + return UUID.fromString("00000000-0000-0000-0000-000000000000"); + } +} diff --git a/src/main/java/mops/gruppen2/service/InviteService.java b/src/main/java/mops/gruppen2/service/InviteService.java index 962dd81..d13ef17 100644 --- a/src/main/java/mops/gruppen2/service/InviteService.java +++ b/src/main/java/mops/gruppen2/service/InviteService.java @@ -1,50 +1,54 @@ package mops.gruppen2.service; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.Group; import mops.gruppen2.domain.dto.InviteLinkDTO; import mops.gruppen2.domain.exception.InvalidInviteException; import mops.gruppen2.domain.exception.NoInviteExistException; import mops.gruppen2.repository.InviteRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.UUID; @Service +@Log4j2 public class InviteService { - private static final Logger LOG = LoggerFactory.getLogger(InviteService.class); private final InviteRepository inviteRepository; public InviteService(InviteRepository inviteRepository) { this.inviteRepository = inviteRepository; } - void createLink(UUID groupId) { - inviteRepository.save(new InviteLinkDTO(null, groupId.toString(), UUID.randomUUID().toString())); + void createLink(Group group) { + inviteRepository.save(new InviteLinkDTO(null, + group.getId().toString(), + UUID.randomUUID().toString())); + + log.debug("Link wurde erzeugt! (Gruppe: {})", group.getId()); } - void destroyLink(UUID groupId) { - inviteRepository.deleteLinkOfGroup(groupId.toString()); + void destroyLink(Group group) { + inviteRepository.deleteLinkOfGroup(group.getId().toString()); + + 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); + log.error("Gruppe zu Link ({}) konnte nicht gefunden werden!", link, e); + throw new InvalidInviteException(link); } - - throw new InvalidInviteException(link); } - public String getLinkByGroupId(UUID groupId) { + public String getLinkByGroup(Group group) { try { - return inviteRepository.findLinkByGroupId(groupId.toString()); + return inviteRepository.findLinkByGroupId(group.getId().toString()); } catch (Exception e) { - LOG.error("Link zu Gruppe ({}) konnte nicht gefunden werden!", groupId); + log.error("Link zu Gruppe ({}) konnte nicht gefunden werden!", group.getId(), e); + throw new NoInviteExistException(group.getId().toString()); } - - throw new NoInviteExistException(groupId.toString()); } } diff --git a/src/main/java/mops/gruppen2/service/JsonService.java b/src/main/java/mops/gruppen2/service/JsonService.java index 9f3150b..81389d8 100644 --- a/src/main/java/mops/gruppen2/service/JsonService.java +++ b/src/main/java/mops/gruppen2/service/JsonService.java @@ -2,6 +2,7 @@ package mops.gruppen2.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.event.Event; import org.springframework.stereotype.Service; @@ -9,12 +10,13 @@ import org.springframework.stereotype.Service; * Übersetzt JSON-Event-Payloads zu Java-Event-Repräsentationen und zurück. */ @Service +@Log4j2 public final class JsonService { private JsonService() {} /** - * Übersetzt mithilfe der Jackson-Library eine Java-Event-Repräsentation zu einem JSON-Event-Payload. + * Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload. * * @param event Java-Event-Repräsentation * @@ -29,7 +31,7 @@ public final class JsonService { } /** - * Übersetzt mithilfe der Jackson-Library einen JSON-Event-Payload zu einer Java-Event-Repräsentation. + * Übersetzt eine JSON-Event-Payload zu einer Java-Event-Repräsentation. * * @param json JSON-Event-Payload als String * diff --git a/src/main/java/mops/gruppen2/service/KeyCloakService.java b/src/main/java/mops/gruppen2/service/KeyCloakService.java deleted file mode 100644 index 3067da1..0000000 --- a/src/main/java/mops/gruppen2/service/KeyCloakService.java +++ /dev/null @@ -1,30 +0,0 @@ -package mops.gruppen2.service; - -import mops.gruppen2.domain.Account; -import org.keycloak.KeycloakPrincipal; -import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; -import org.springframework.stereotype.Service; - -@Service -public final class KeyCloakService { - - private KeyCloakService() {} - - /** - * Creates an Account. - * - * @param token Ein toller token - * - * @return Account with current userdata - */ - public static Account createAccountFromPrincipal(KeycloakAuthenticationToken token) { - KeycloakPrincipal principal = (KeycloakPrincipal) token.getPrincipal(); - return new Account( - principal.getName(), - principal.getKeycloakSecurityContext().getIdToken().getEmail(), - null, - principal.getKeycloakSecurityContext().getIdToken().getGivenName(), - principal.getKeycloakSecurityContext().getIdToken().getFamilyName(), - token.getAccount().getRoles()); - } -} diff --git a/src/main/java/mops/gruppen2/service/ProjectionService.java b/src/main/java/mops/gruppen2/service/ProjectionService.java new file mode 100644 index 0000000..3fcb5bf --- /dev/null +++ b/src/main/java/mops/gruppen2/service/ProjectionService.java @@ -0,0 +1,225 @@ +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; +import mops.gruppen2.domain.exception.GroupNotFoundException; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +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; + +/** + * Liefert verschiedene Projektionen auf Gruppen. + * Benötigt ausschließlich den EventStoreService. + */ +@Service +@Log4j2 +public class ProjectionService { + + private final EventStoreService eventStoreService; + + public ProjectionService(EventStoreService eventStoreService) { + this.eventStoreService = eventStoreService; + } + + + // ################################## STATISCHE PROJEKTIONEN ################################# + + + /** + * Konstruiert Gruppen aus einer Liste von Events. + * + * @param events Liste an Events + * + * @return Liste an Projizierten Gruppen + * + * @throws EventException Projektionsfehler + */ + static List projectGroups(List events) throws EventException { + Map groupMap = new HashMap<>(); + + events.forEach(event -> event.apply(getOrCreateGroup(groupMap, event.getGroupId()))); + + return new ArrayList<>(groupMap.values()); + } + + /** + * Projiziert Events, geht aber davon aus, dass alle zu derselben Gruppe gehören. + * + * @param events Eventliste + * + * @return Eine projizierte Gruppe + * + * @throws EventException Projektionsfehler, z.B. falls Events von verschiedenen Gruppen übergeben werden + */ + static Group projectSingleGroup(List events) throws EventException { + if (events.isEmpty()) { + throw new GroupNotFoundException(ProjectionService.class.toString()); + } + + Group group = new Group(); + + events.forEach(event -> event.apply(group)); + + return group; + } + + /** + * Gibt die Gruppe mit der richtigen Id aus der übergebenen Map wieder, existiert diese nicht + * wird die Gruppe erstellt und der Map hizugefügt. + * + * @param groups Map aus GruppenIds und Gruppen + * @param groupId Die Id der Gruppe, die zurückgegeben werden soll + * + * @return Die gesuchte Gruppe + */ + private static Group getOrCreateGroup(Map groups, UUID groupId) { + if (!groups.containsKey(groupId)) { + groups.put(groupId, new Group()); + } + + return groups.get(groupId); + } + + + // ############################### PROJEKTIONEN MIT DATENBANK ################################ + + + /** + * Projiziert Gruppen, welche sich seit einer übergebenen eventId geändert haben. + * Die Gruppen werden dabei vollständig konstruiert. + * + * @param status Letzte bekannte eventId + * + * @return Liste an Gruppen + */ + public List projectNewGroups(long status) { + List events = eventStoreService.findChangedGroupEvents(status); + + return projectGroups(events); + } + + /** + * Projiziert öffentliche Gruppen. + * Die Gruppen enthalten Metainformationen: Titel, Beschreibung und MaxUserAnzahl. + * Außerdem wird noch beachtet, ob der eingeloggte User bereits in entsprechenden Gruppen mitglied ist. + * + * @return Liste von projizierten Gruppen + * + * @throws EventException Projektionsfehler + */ + @Cacheable("groups") + //TODO: remove userID param + public List projectPublicGroups() throws EventException { + List groupIds = eventStoreService.findExistingGroupIds(); + List events = eventStoreService.findEventsByGroupAndType(groupIds, + "CreateGroupEvent", + "UpdateGroupDescriptionEvent", + "UpdateGroupTitleEvent", + "UpdateUserMaxEvent"); + + List groups = projectGroups(events); + + return groups.stream() + .filter(group -> group.getVisibility() == Visibility.PUBLIC) + .collect(Collectors.toList()); + } + + /** + * Projiziert Vorlesungen. + * Projektionen enthalten nur Metainformationen: Titel. + * + * @return Liste von Veranstaltungen + */ + @Cacheable("groups") + public List projectLectures() { + List groupIds = eventStoreService.findExistingGroupIds(); + List events = eventStoreService.findEventsByGroupAndType(groupIds, + "CreateGroupEvent", + "UpdateGroupTitleEvent"); + + List lectures = projectGroups(events); + + return lectures.stream() + .filter(group -> group.getType() == GroupType.LECTURE) + .collect(Collectors.toList()); + } + + /** + * Projiziert Gruppen, in welchen der User aktuell teilnimmt. + * Die Gruppen enthalten nur Metainformationen: Titel und Beschreibung. + * + * @param user Die Id + * + * @return Liste aus Gruppen + */ + @Cacheable("groups") + public List projectUserGroups(User user) { + List groupIds = eventStoreService.findExistingUserGroups(user); + List groupEvents = eventStoreService.findEventsByGroupAndType(groupIds, + "CreateGroupEvent", + "UpdateGroupTitleEvent", + "UpdateGroupDescriptionEvent"); + + return projectGroups(groupEvents); + } + + /** + * Gibt die Gruppe zurück, die zu der übergebenen Id passt. + * Enthält alle verfügbaren Informationen, also auch User (langsam). + * Gibt eine leere Gruppe zurück, falls die Id leer ist. + * + * @param groupId Die Id der gesuchten Gruppe + * + * @return Die gesuchte Gruppe + * + * @throws GroupNotFoundException Wenn die Gruppe nicht gefunden wird + */ + public Group projectSingleGroup(UUID groupId) throws GroupNotFoundException { + if (IdService.isEmpty(groupId)) { + throw new GroupNotFoundException(groupId + ": " + ProjectionService.class); + } + + try { + List events = eventStoreService.findGroupEvents(groupId); + 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 user User, welcher teilnimmt + */ + void removeUserGroups(List groups, User user) { + List userGroups = eventStoreService.findExistingUserGroups(user); + + groups.removeIf(group -> userGroups.contains(group.getId())); + } +} + diff --git a/src/main/java/mops/gruppen2/service/SearchService.java b/src/main/java/mops/gruppen2/service/SearchService.java index 6e94bdf..b399120 100644 --- a/src/main/java/mops/gruppen2/service/SearchService.java +++ b/src/main/java/mops/gruppen2/service/SearchService.java @@ -1,6 +1,69 @@ 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; +import java.util.List; +import java.util.stream.Collectors; + @Service -public class SearchService {} +@Log4j2 +public class SearchService { + + private final ProjectionService projectionService; + + public SearchService(ProjectionService projectionService) { + this.projectionService = projectionService; + } + + /** + * Filtert alle öffentliche Gruppen nach dem Suchbegriff und gibt diese als sortierte Liste zurück. + * Groß- und Kleinschreibung wird nicht beachtet. + * Der Suchbegriff wird im Gruppentitel und in der Beschreibung gesucht. + * + * @param search Der Suchstring + * + * @return Liste von projizierten Gruppen + * + * @throws EventException Projektionsfehler + */ + @Cacheable("groups") + public List searchPublicGroups(String search, User user) throws EventException { + List groups = projectionService.projectPublicGroups(); + projectionService.removeUserGroups(groups, user); + sortByGroupType(groups); + + if (search.isEmpty()) { + return groups; + } + + log.debug("Es wurde gesucht nach: {}", search); + + return groups.stream() + .filter(group -> group.toString().toLowerCase().contains(search.toLowerCase())) + .collect(Collectors.toList()); + } + + /** + * Sortiert die übergebene Liste an Gruppen, sodass Veranstaltungen am Anfang der Liste sind. + * + * @param groups Die Liste von Gruppen die sortiert werden soll + */ + private static void sortByGroupType(List groups) { + groups.sort((Group g1, Group g2) -> { + if (g1.getType() == GroupType.LECTURE) { + return -1; + } + if (g2.getType() == GroupType.LECTURE) { + return 0; + } + + return 1; + }); + } +} diff --git a/src/main/java/mops/gruppen2/service/UserService.java b/src/main/java/mops/gruppen2/service/UserService.java deleted file mode 100644 index dd6d65b..0000000 --- a/src/main/java/mops/gruppen2/service/UserService.java +++ /dev/null @@ -1,76 +0,0 @@ -package mops.gruppen2.service; - -import mops.gruppen2.domain.Group; -import mops.gruppen2.domain.User; -import mops.gruppen2.domain.event.Event; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.exception.GroupNotFoundException; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Service -public class UserService { - - private final GroupService groupService; - private final EventService eventService; - - public UserService(GroupService groupService, EventService eventService) { - this.groupService = groupService; - this.eventService = eventService; - } - - @Cacheable("groups") - public List getUserGroups(String userId) throws EventException { - return getUserGroups(new User(userId, "", "", "")); - } - - /** - * Gibt eine Liste aus Gruppen zurück, in denen sich der übergebene User befindet. - * - * @param user Der User - * - * @return Liste aus Gruppen - */ - //TODO: Nur AddUserEvents + DeleteUserEvents betrachten - @Cacheable("groups") - public List getUserGroups(User user) { - List groupIds = eventService.findGroupIdsByUser(user.getId()); - List events = groupService.getGroupEvents(groupIds); - List groups = groupService.projectEventList(events); - List newGroups = new ArrayList<>(); - - for (Group group : groups) { - if (group.getMembers().contains(user)) { - newGroups.add(group); - } - } - groupService.sortByGroupType(newGroups); - - return newGroups; - } - - /** - * Gibt die Gruppe zurück, die zu der übergebenen Id passt. - * - * @param groupId Die Id der gesuchten Gruppe - * - * @return Die gesuchte Gruppe - * - * @throws EventException Wenn die Gruppe nicht gefunden wird - */ - public Group getGroupById(UUID groupId) throws EventException { - List groupIds = new ArrayList<>(); - groupIds.add(groupId); - - try { - List events = groupService.getGroupEvents(groupIds); - return groupService.projectEventList(events).get(0); - } catch (IndexOutOfBoundsException e) { - throw new GroupNotFoundException("@UserService"); - } - } -} diff --git a/src/main/java/mops/gruppen2/service/ValidationService.java b/src/main/java/mops/gruppen2/service/ValidationService.java index b1defef..bbbf052 100644 --- a/src/main/java/mops/gruppen2/service/ValidationService.java +++ b/src/main/java/mops/gruppen2/service/ValidationService.java @@ -1,157 +1,156 @@ package mops.gruppen2.service; -import mops.gruppen2.domain.Account; +import lombok.extern.log4j.Log4j2; 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 -public class ValidationService { +@Log4j2 +public final class ValidationService { - private final UserService userService; - private final GroupService groupService; + private ValidationService() {} - public ValidationService(UserService userService, GroupService groupService) { - this.userService = userService; - this.groupService = groupService; - } - //TODO: make static or change return + assignment - public List checkSearch(String search, List groups, Account account) { - if (search != null) { - groups = groupService.findGroupWith(search, account); - } - return groups; - } + // ######################################## CHECK ############################################ - 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.getUserMaximum() < 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; } - boolean checkIfGroupEmpty(UUID groupId) { - return userService.getGroupById(groupId).getMembers().isEmpty(); + /** + * Überprüft, ob eine Gruppe voll ist. + */ + public static boolean checkIfGroupFull(Group group) { + return group.getMembers().size() >= group.getUserLimit(); } - public void throwIfNoAdmin(Group group, User user) { - throwIfNoAccessToPrivate(group, user); - if (group.getRoles().get(user.getId()) != ADMIN) { - throw new NoAccessException(""); - } + /** + * Überprüft, ob eine Gruppe leer ist. + */ + public static boolean checkIfGroupEmpty(Group group) { + return group.getMembers().isEmpty(); } - 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; } - void throwIfLastAdmin(Account account, Group group) { - if (checkIfLastAdmin(account, group)) { + 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. + */ + public static void throwIfLastAdmin(User user, Group group) { + if (!checkIfLastMember(user, group) && checkIfLastAdmin(user, group)) { throw new NoAdminAfterActionException("Du bist letzter Admin!"); } } - boolean checkIfLastAdmin(Account account, Group group) { - for (Map.Entry entry : group.getRoles().entrySet()) { - if (entry.getValue() == ADMIN && !(entry.getKey().equals(account.getName()))) { - 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; } - /** - * Überprüft ob alle Felder richtig gesetzt sind. - * - * @param description Die Beschreibung der Gruppe - * @param title Der Titel der Gruppe - * @param userMaximum Das user Limit der Gruppe - */ - public void checkFields(String title, String description, Long userMaximum, Boolean maxInfiniteUsers) { - if (description == null || description.trim().isEmpty()) { - throw new BadParameterException("Die Beschreibung wurde nicht korrekt angegeben"); + 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()); } + } + + // ##################################### 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 (userMaximum == null && maxInfiniteUsers == null) { - throw new BadParameterException("Teilnehmeranzahl wurde nicht korrekt angegeben"); - } - - if (userMaximum != null && (userMaximum < 1 || userMaximum > 10000L)) { - 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 throwIfNewMaximumIsValid(Long newUserMaximum, Group group) { - if (newUserMaximum == 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 (newUserMaximum < 1 || newUserMaximum > 10000L) { - throw new BadParameterException("Die neue maximale Teilnehmeranzahl wurde nicht korrekt angegeben!"); - } - - if (group.getMembers().size() > newUserMaximum) { - 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!"); } } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 0a56ff4..b7df6fb 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,23 +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 +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 diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 3474f7a..bee26b7 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -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 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7f0e7ec..8deb882 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,2 @@ -spring.profiles.active=dev +spring.profiles.active = dev diff --git a/src/main/resources/templates/changeMetadata.html b/src/main/resources/templates/changeMetadata.html index 7488365..d09020e 100644 --- a/src/main/resources/templates/changeMetadata.html +++ b/src/main/resources/templates/changeMetadata.html @@ -30,7 +30,7 @@ Erstellen - Suche + Suche @@ -42,7 +42,7 @@ Metadaten ändern + style=" border: 10px solid aliceblue; background: aliceblue;"> Titel + Gruppenerstellung @@ -14,7 +15,9 @@ + + @@ -28,77 +31,72 @@ Erstellen - Suche + Suche + Gruppenerstellung - - + + Titel - + Beschreibung - + - + + + Anzahl unbegrenzt - Teilnehmeranzahl - + Teilnehmeranzahl + - - Private - Gruppe + + + + Privat - + + + Veranstaltung - - - - --Bitte Veranstaltung auswählen-- - - - + + Veranstaltungszugehörigkeit + + --Keine-- + - CSV Datei von Mitgliedern hochladen + Erstellen @@ -107,38 +105,45 @@ + + + +