diff --git a/src/main/java/mops/gruppen2/config/SwaggerConfig.java b/src/main/java/mops/gruppen2/config/SwaggerConfig.java index 4c13329..fb3c81d 100644 --- a/src/main/java/mops/gruppen2/config/SwaggerConfig.java +++ b/src/main/java/mops/gruppen2/config/SwaggerConfig.java @@ -1,6 +1,5 @@ package mops.gruppen2.config; -import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -16,7 +15,6 @@ import java.util.Collections; @Profile("dev") @Configuration -@EnableCaching @EnableSwagger2 public class SwaggerConfig { diff --git a/src/main/java/mops/gruppen2/domain/event/Event.java b/src/main/java/mops/gruppen2/domain/event/Event.java index 84e563c..abb2ceb 100644 --- a/src/main/java/mops/gruppen2/domain/event/Event.java +++ b/src/main/java/mops/gruppen2/domain/event/Event.java @@ -11,6 +11,7 @@ import mops.gruppen2.domain.exception.BadArgumentException; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.exception.IdMismatchException; import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.infrastructure.GroupCache; import java.util.UUID; @@ -31,7 +32,6 @@ import java.util.UUID; @NoArgsConstructor // Lombok needs a default constructor in the base class public abstract class Event { - @JsonProperty("groupid") protected UUID groupid; @@ -60,7 +60,7 @@ public abstract class Event { this.version = version; } - public Group apply(Group group) throws EventException { + public void apply(Group group, GroupCache cache) throws EventException { log.trace("Event wird angewendet:\t{}", this); if (version == 0) { @@ -68,23 +68,32 @@ public abstract class Event { } checkGroupIdMatch(group.getId()); - group.update(version); + group.updateVersion(version); applyEvent(group); - - return group; + updateCache(cache, group); } - private void checkGroupIdMatch(UUID groupId) throws IdMismatchException { + private void checkGroupIdMatch(UUID groupid) throws IdMismatchException { // CreateGroupEvents müssen die Id erst initialisieren if (this instanceof CreateGroupEvent) { return; } - if (!groupid.equals(groupId)) { + if (!this.groupid.equals(groupid)) { throw new IdMismatchException("Das Event gehört zu einer anderen Gruppe"); } } + private void updateCache(GroupCache cache, Group group) { + if (this instanceof CreateGroupEvent) { + cache.put(group); + } + + if (this instanceof DestroyGroupEvent) { + cache.remove(group); + } + } + protected abstract void applyEvent(Group group) throws EventException; @JsonIgnore diff --git a/src/main/java/mops/gruppen2/domain/model/group/Group.java b/src/main/java/mops/gruppen2/domain/model/group/Group.java index 5c407f1..b26fae3 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/Group.java +++ b/src/main/java/mops/gruppen2/domain/model/group/Group.java @@ -10,14 +10,14 @@ import mops.gruppen2.domain.exception.LastAdminException; import mops.gruppen2.domain.exception.NoAccessException; import mops.gruppen2.domain.exception.UserAlreadyExistsException; import mops.gruppen2.domain.exception.UserNotFoundException; -import mops.gruppen2.domain.helper.CommonHelper; -import mops.gruppen2.domain.helper.ValidationHelper; import mops.gruppen2.domain.model.group.wrapper.Body; import mops.gruppen2.domain.model.group.wrapper.Description; import mops.gruppen2.domain.model.group.wrapper.Limit; import mops.gruppen2.domain.model.group.wrapper.Link; import mops.gruppen2.domain.model.group.wrapper.Parent; import mops.gruppen2.domain.model.group.wrapper.Title; +import mops.gruppen2.domain.service.helper.CommonHelper; +import mops.gruppen2.domain.service.helper.ValidationHelper; import javax.validation.Valid; import java.time.LocalDateTime; @@ -73,6 +73,7 @@ public class Group { // ####################################### Members ########################################### + public List getMembers() { return SortHelper.sortByMemberRole(new ArrayList<>(memberships.values())).stream() .map(Membership::getUser) @@ -210,6 +211,10 @@ public class Group { return type == Type.LECTURE; } + public boolean hasParent() { + return !parent.isEmpty(); + } + // ######################################## Setters ########################################## @@ -262,7 +267,7 @@ public class Group { this.link = link; } - public void update(long version) throws IdMismatchException { + public void updateVersion(long version) throws IdMismatchException { meta = meta.setVersion(version); } @@ -310,4 +315,8 @@ public class Group { + (meta == null ? "meta: null" : meta.toString()) + ")"; } + + public static Group EMPTY() { + return new Group(); + } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/User.java b/src/main/java/mops/gruppen2/domain/model/group/User.java index 63464ee..5fa6802 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/User.java +++ b/src/main/java/mops/gruppen2/domain/model/group/User.java @@ -56,4 +56,8 @@ public class User { public String format() { return givenname + " " + familyname; } + + public boolean isMember(Group group) { + return group.getMembers().contains(this); + } } diff --git a/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java index bd3d05d..e19cc6e 100644 --- a/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java +++ b/src/main/java/mops/gruppen2/domain/model/group/wrapper/Parent.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.ToString; import lombok.Value; -import mops.gruppen2.domain.helper.CommonHelper; +import mops.gruppen2.domain.service.helper.CommonHelper; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; diff --git a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java index 00f4d2d..8acb61d 100644 --- a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java +++ b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java @@ -3,39 +3,26 @@ package mops.gruppen2.domain.service; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.aspect.annotation.TraceMethodCalls; import mops.gruppen2.domain.event.AddMemberEvent; -import mops.gruppen2.domain.event.CreateGroupEvent; import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.event.EventType; -import mops.gruppen2.domain.event.SetTypeEvent; import mops.gruppen2.domain.exception.BadPayloadException; -import mops.gruppen2.domain.exception.InvalidInviteException; -import mops.gruppen2.domain.helper.CommonHelper; -import mops.gruppen2.domain.helper.JsonHelper; -import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.service.helper.JsonHelper; import mops.gruppen2.persistance.EventRepository; import mops.gruppen2.persistance.dto.EventDTO; import org.springframework.stereotype.Service; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; -import static mops.gruppen2.domain.event.EventType.CREATEGROUP; import static mops.gruppen2.domain.event.EventType.DESTROYGROUP; -import static mops.gruppen2.domain.event.EventType.SETLINK; -import static mops.gruppen2.domain.event.EventType.SETTYPE; -import static mops.gruppen2.domain.helper.CommonHelper.eventTypesToString; -import static mops.gruppen2.domain.helper.CommonHelper.uuidsToString; +import static mops.gruppen2.domain.service.helper.CommonHelper.eventTypesToString; @Log4j2 @RequiredArgsConstructor @Service -@TraceMethodCalls public class EventStoreService { private final EventRepository eventStore; @@ -77,12 +64,6 @@ public class EventStoreService { //########################################### DTOs ########################################### - private static List getDTOsFromEvents(List events) { - return events.stream() - .map(EventStoreService::getDTOFromEvent) - .collect(Collectors.toList()); - } - /** * Erzeugt aus einem Event Objekt ein EventDTO Objekt. * @@ -132,81 +113,6 @@ public class EventStoreService { // ######################################## 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 findChangedGroups(long status) { - List changedGroupIds = eventStore.findGroupIdsWhereEventIdGreaterThanStatus(status); - - log.debug("Seit Event {} haben sich {} Gruppen geändert!", status, changedGroupIds.size()); - - return CommonHelper.stringsToUUID(changedGroupIds); - } - - /** - * Liefert Gruppen-Ids von existierenden (ungelöschten) Gruppen. - * - * @return GruppenIds (UUID) als Liste - */ - List findExistingGroupIds() { - List createEvents = findLatestEventsFromGroupsByType(CREATEGROUP, - DESTROYGROUP); - - return createEvents.stream() - .filter(event -> event instanceof CreateGroupEvent) - .map(Event::getGroupid) - .collect(Collectors.toList()); - } - - public List findPublicGroupIds() { - List groups = findExistingGroupIds(); - List typeEvents = findLatestEventsFromGroupsByType(SETTYPE); - - typeEvents.removeIf(event -> ((SetTypeEvent) event).getType() == Type.PRIVATE); - typeEvents.removeIf(event -> !groups.contains(event.getGroupid())); - - return typeEvents.stream() - .map(Event::getGroupid) - .collect(Collectors.toList()); - } - - public List findLectureGroupIds() { - List groups = findExistingGroupIds(); - List typeEvents = findLatestEventsFromGroupsByType(SETTYPE); - - typeEvents.removeIf(event -> ((SetTypeEvent) event).getType() != Type.LECTURE); - typeEvents.removeIf(event -> !groups.contains(event.getGroupid())); - - return typeEvents.stream() - .map(Event::getGroupid) - .collect(Collectors.toList()); - } - /** * Liefert Gruppen-Ids von existierenden (ungelöschten) Gruppen, in welchen der User teilnimmt. * @@ -234,20 +140,6 @@ public class EventStoreService { .collect(Collectors.toList()); } - public UUID findGroupByLink(String link) { - List groupEvents = findEventsByType(eventTypesToString(SETLINK)); - - if (groupEvents.size() > 1) { - throw new InvalidInviteException("Es existieren mehrere Gruppen mit demselben Link."); - } - - if (groupEvents.isEmpty()) { - throw new InvalidInviteException("Link nicht gefunden."); - } - - return groupEvents.get(0).getGroupid(); - } - // #################################### SIMPLE QUERIES ####################################### @@ -262,23 +154,10 @@ public class EventStoreService { return eventStore.findMaxEventId(); } catch (NullPointerException e) { log.debug("Keine Events vorhanden!"); - return 1; + 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(uuidsToString(groupIds), - Arrays.asList(types))); - } - /** * Sucht zu jeder Gruppe das letzte Add- oder DeleteUserEvent heraus, welches den übergebenen User betrifft. * @@ -301,4 +180,12 @@ public class EventStoreService { private List findLatestEventsFromGroupsByType(EventType... types) { return getEventsFromDTOs(eventStore.findLatestEventDTOsPartitionedByGroupByType(Arrays.asList(eventTypesToString(types)))); } + + public List findAllEvents() { + return getEventsFromDTOs(eventStore.findAllEvents()); + } + + public List findNewEvents(long version, long maxid) { + return getEventsFromDTOs(eventStore.findNewEvents(version, maxid)); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/GroupService.java b/src/main/java/mops/gruppen2/domain/service/GroupService.java index 477da7c..612e627 100644 --- a/src/main/java/mops/gruppen2/domain/service/GroupService.java +++ b/src/main/java/mops/gruppen2/domain/service/GroupService.java @@ -2,7 +2,6 @@ package mops.gruppen2.domain.service; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import mops.gruppen2.aspect.annotation.TraceMethodCalls; import mops.gruppen2.domain.event.AddMemberEvent; import mops.gruppen2.domain.event.CreateGroupEvent; import mops.gruppen2.domain.event.DestroyGroupEvent; @@ -16,7 +15,6 @@ import mops.gruppen2.domain.event.SetTitleEvent; import mops.gruppen2.domain.event.SetTypeEvent; import mops.gruppen2.domain.event.UpdateRoleEvent; import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.helper.ValidationHelper; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.Role; import mops.gruppen2.domain.model.group.Type; @@ -26,6 +24,8 @@ import mops.gruppen2.domain.model.group.wrapper.Limit; import mops.gruppen2.domain.model.group.wrapper.Link; import mops.gruppen2.domain.model.group.wrapper.Parent; import mops.gruppen2.domain.model.group.wrapper.Title; +import mops.gruppen2.domain.service.helper.ValidationHelper; +import mops.gruppen2.infrastructure.GroupCache; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -37,11 +37,11 @@ import java.util.UUID; * Es werden übergebene Gruppen bearbeitet und dementsprechend Events erzeugt und gespeichert. */ @Log4j2 -@TraceMethodCalls @RequiredArgsConstructor @Service public class GroupService { + private final GroupCache groupCache; private final EventStoreService eventStoreService; // ################################# GRUPPE ERSTELLEN ######################################## @@ -266,7 +266,7 @@ public class GroupService { private void applyAndSave(Group group, Event event) throws EventException { event.init(group.version() + 1); - event.apply(group); + event.apply(group, groupCache); eventStoreService.saveEvent(event); } diff --git a/src/main/java/mops/gruppen2/domain/service/ProjectionService.java b/src/main/java/mops/gruppen2/domain/service/ProjectionService.java deleted file mode 100644 index 1338eef..0000000 --- a/src/main/java/mops/gruppen2/domain/service/ProjectionService.java +++ /dev/null @@ -1,215 +0,0 @@ -package mops.gruppen2.domain.service; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import mops.gruppen2.domain.event.Event; -import mops.gruppen2.domain.exception.EventException; -import mops.gruppen2.domain.exception.GroupNotFoundException; -import mops.gruppen2.domain.helper.CommonHelper; -import mops.gruppen2.domain.model.group.Group; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * Liefert verschiedene Projektionen auf Gruppen. - * Benötigt ausschließlich den EventStoreService. - */ -@Log4j2 -@RequiredArgsConstructor -@Service -public class ProjectionService { - - private final EventStoreService eventStoreService; - - - // ################################## STATISCHE PROJEKTIONEN ################################# - - - /** - * 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 - */ - private static Group projectGroupByEvents(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; - } - - /** - * Konstruiert Gruppen aus einer Liste von Events. - * - * @param events Liste an Events - * - * @return Liste an Projizierten Gruppen - * - * @throws EventException Projektionsfehler - */ - public static List projectGroupsByEvents(List events) throws EventException { - Map groupMap = new HashMap<>(); - - events.forEach(event -> event.apply(getOrCreateGroup(groupMap, event.getGroupid()))); - - return new ArrayList<>(groupMap.values()); - } - - /** - * 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 ################################ - - - /** - * 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 projectGroupById(UUID groupId) throws GroupNotFoundException { - try { - List events = eventStoreService.findGroupEvents(groupId); - return projectGroupByEvents(events); - } catch (Exception e) { - log.error("Gruppe {} wurde nicht gefunden!", groupId.toString(), e); - throw new GroupNotFoundException(groupId + ": " + ProjectionService.class); - } - } - - public Group projectParent(UUID parent) { - if (CommonHelper.uuidIsEmpty(parent)) { - return new Group(); - } - - return projectGroupById(parent); - } - - public List projectGroupsByIds(List groupids) { - List events = eventStoreService.findGroupEvents(groupids); - - return projectGroupsByEvents(events); - } - - /** - * 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 projectChangedGroups(long status) { - List changedids = eventStoreService.findChangedGroups(status); - - return projectGroupsByIds(changedids); - } - - /** - * 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") - public List projectPublicGroups() throws EventException { - List groupIds = eventStoreService.findPublicGroupIds(); - - if (groupIds.isEmpty()) { - return Collections.emptyList(); - } - - return projectGroupsByIds(groupIds); - } - - /** - * Projiziert Vorlesungen. - * Projektionen enthalten nur Metainformationen: Titel. - * - * @return Liste von Veranstaltungen - */ - @Cacheable("groups") - public List projectLectures() { - List groupIds = eventStoreService.findLectureGroupIds(); - - if (groupIds.isEmpty()) { - return Collections.emptyList(); - } - - return projectGroupsByIds(groupIds); - } - - /** - * Projiziert Gruppen, in welchen der User aktuell teilnimmt. - * Die Gruppen enthalten nur Metainformationen: Titel und Beschreibung. - * - * @param userid Die Id - * - * @return Liste aus Gruppen - */ - @Cacheable("groups") - public List projectUserGroups(String userid) { - List groupIds = eventStoreService.findExistingUserGroups(userid); - - if (groupIds.isEmpty()) { - return Collections.emptyList(); - } - - return projectGroupsByIds(groupIds); - } - - /** - * Entfernt alle Gruppen, in welchen ein User teilnimmt, aus einer Gruppenliste. - * - * @param groups Gruppenliste, aus der entfernt wird - * @param userid User, welcher teilnimmt - */ - void removeUserGroups(List groups, String userid) { - List userGroups = eventStoreService.findExistingUserGroups(userid); - - groups.removeIf(group -> userGroups.contains(group.getId())); - } - - public Group projectGroupByLink(String link) { - return projectGroupById(eventStoreService.findGroupByLink(link)); - } -} - diff --git a/src/main/java/mops/gruppen2/domain/service/SearchService.java b/src/main/java/mops/gruppen2/domain/service/SearchService.java index dcb5228..f87bf1b 100644 --- a/src/main/java/mops/gruppen2/domain/service/SearchService.java +++ b/src/main/java/mops/gruppen2/domain/service/SearchService.java @@ -1,24 +1,21 @@ package mops.gruppen2.domain.service; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.model.group.Group; -import mops.gruppen2.domain.model.group.SortHelper; -import org.springframework.cache.annotation.Cacheable; +import mops.gruppen2.infrastructure.GroupCache; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service +@RequiredArgsConstructor @Log4j2 public class SearchService { - private final ProjectionService projectionService; - - public SearchService(ProjectionService projectionService) { - this.projectionService = projectionService; - } + private final GroupCache groupCache; /** * Filtert alle öffentliche Gruppen nach dem Suchbegriff und gibt diese als sortierte Liste zurück. @@ -31,13 +28,11 @@ public class SearchService { * * @throws EventException Projektionsfehler */ - @Cacheable("groups") + //TODO: search in lectures public List searchPublicGroups(String search, String principal) { - List groups = projectionService.projectPublicGroups(); - System.out.println(groups); - projectionService.removeUserGroups(groups, principal); - System.out.println(groups); - SortHelper.sortByGroupType(groups); + List groups = groupCache.publics(); + + groups = removeUserGroups(groups, principal); if (search.isEmpty()) { return groups; @@ -50,4 +45,10 @@ public class SearchService { .collect(Collectors.toList()); } + private static List removeUserGroups(List groups, String principal) { + return groups.stream() + .filter(group -> !group.isMember(principal)) + .collect(Collectors.toList()); + } + } diff --git a/src/main/java/mops/gruppen2/domain/helper/APIHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java similarity index 80% rename from src/main/java/mops/gruppen2/domain/helper/APIHelper.java rename to src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java index 5da7990..39e7569 100644 --- a/src/main/java/mops/gruppen2/domain/helper/APIHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java @@ -1,10 +1,10 @@ -package mops.gruppen2.domain.helper; +package mops.gruppen2.domain.service.helper; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.model.group.Group; -import mops.gruppen2.web.api.GroupRequestWrapper; +import mops.gruppen2.infrastructure.api.GroupRequestWrapper; import java.util.List; diff --git a/src/main/java/mops/gruppen2/domain/helper/CommonHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java similarity index 96% rename from src/main/java/mops/gruppen2/domain/helper/CommonHelper.java rename to src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java index 6e833d8..5ac0ed1 100644 --- a/src/main/java/mops/gruppen2/domain/helper/CommonHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java @@ -1,4 +1,4 @@ -package mops.gruppen2.domain.helper; +package mops.gruppen2.domain.service.helper; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/mops/gruppen2/domain/helper/CsvHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java similarity index 97% rename from src/main/java/mops/gruppen2/domain/helper/CsvHelper.java rename to src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java index 5a43b85..c087540 100644 --- a/src/main/java/mops/gruppen2/domain/helper/CsvHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/CsvHelper.java @@ -1,4 +1,4 @@ -package mops.gruppen2.domain.helper; +package mops.gruppen2.domain.service.helper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.dataformat.csv.CsvMapper; diff --git a/src/main/java/mops/gruppen2/domain/helper/JsonHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java similarity index 97% rename from src/main/java/mops/gruppen2/domain/helper/JsonHelper.java rename to src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java index 614e929..b66c57f 100644 --- a/src/main/java/mops/gruppen2/domain/helper/JsonHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/JsonHelper.java @@ -1,4 +1,4 @@ -package mops.gruppen2.domain.helper; +package mops.gruppen2.domain.service.helper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java new file mode 100644 index 0000000..b01b1aa --- /dev/null +++ b/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java @@ -0,0 +1,49 @@ +package mops.gruppen2.domain.service.helper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.event.Event; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.infrastructure.GroupCache; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Liefert verschiedene Projektionen auf Gruppen. + * Benötigt ausschließlich den EventStoreService. + */ +@Log4j2 +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ProjectionHelper { + + public static void project(Map groups, List events, GroupCache cache) { + if (events.isEmpty()) { + return; + } + + log.trace(groups); + log.trace(events); + + events.forEach(event -> event.apply(getOrCreateGroup(groups, event.getGroupid()), cache)); + } + + /** + * 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); + } +} diff --git a/src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/ValidationHelper.java similarity index 98% rename from src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java rename to src/main/java/mops/gruppen2/domain/service/helper/ValidationHelper.java index 02cdc2e..22942bd 100644 --- a/src/main/java/mops/gruppen2/domain/helper/ValidationHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/ValidationHelper.java @@ -1,4 +1,4 @@ -package mops.gruppen2.domain.helper; +package mops.gruppen2.domain.service.helper; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/mops/gruppen2/infrastructure/ApplicationInit.java b/src/main/java/mops/gruppen2/infrastructure/ApplicationInit.java new file mode 100644 index 0000000..4c91b4e --- /dev/null +++ b/src/main/java/mops/gruppen2/infrastructure/ApplicationInit.java @@ -0,0 +1,18 @@ +package mops.gruppen2.infrastructure; + +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class ApplicationInit { + + private final GroupCache groupCache; + + @EventListener(ApplicationReadyEvent.class) + public void init() { + groupCache.init(); + } +} diff --git a/src/main/java/mops/gruppen2/infrastructure/GroupCache.java b/src/main/java/mops/gruppen2/infrastructure/GroupCache.java new file mode 100644 index 0000000..16044be --- /dev/null +++ b/src/main/java/mops/gruppen2/infrastructure/GroupCache.java @@ -0,0 +1,79 @@ +package mops.gruppen2.infrastructure; + +import lombok.RequiredArgsConstructor; +import mops.gruppen2.domain.exception.GroupNotFoundException; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.domain.service.helper.ProjectionHelper; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Component +@Scope("singleton") +public class GroupCache { + + private final EventStoreService eventStoreService; + + private final Map groups = new HashMap<>(); + + public void init() { + long maxid = eventStoreService.findMaxEventId(); + ProjectionHelper.project(groups, eventStoreService.findNewEvents(0, maxid), this); + } + + public void put(Group group) { + groups.put(group.getId(), group); + } + + public void remove(Group group) { + groups.remove(group.getId()); + } + + // Getters + + public Group group(UUID groupid) { + if (!groups.containsKey(groupid)) { + throw new GroupNotFoundException("Gruppe ist nicht im Cache."); + } + + return groups.get(groupid); + } + + public Group group(String link) { + return groups.values().stream() + .filter(group -> group.getLink().equals(link)) + .findFirst() + .orElseThrow(() -> new GroupNotFoundException("Link nicht im Cache.")); + } + + public List userGroups(String userid) { + return groups.values().stream() + .filter(group -> group.isMember(userid)) + .collect(Collectors.toUnmodifiableList()); + } + + public List publics() { + return groups.values().stream() + .filter(Group::isPublic) + .collect(Collectors.toUnmodifiableList()); + } + + public List privates() { + return groups.values().stream() + .filter(Group::isPrivate) + .collect(Collectors.toUnmodifiableList()); + } + + public List lectures() { + return groups.values().stream() + .filter(Group::isLecture) + .collect(Collectors.toUnmodifiableList()); + } +} diff --git a/src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java b/src/main/java/mops/gruppen2/infrastructure/ModelAttributeControllerAdvice.java similarity index 89% rename from src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java rename to src/main/java/mops/gruppen2/infrastructure/ModelAttributeControllerAdvice.java index 71c432f..17b81ed 100644 --- a/src/main/java/mops/gruppen2/web/ModelAttributeControllerAdvice.java +++ b/src/main/java/mops/gruppen2/infrastructure/ModelAttributeControllerAdvice.java @@ -1,5 +1,6 @@ -package mops.gruppen2.web; +package mops.gruppen2.infrastructure; +import lombok.RequiredArgsConstructor; import mops.gruppen2.domain.Account; import mops.gruppen2.domain.model.group.Role; import mops.gruppen2.domain.model.group.Type; @@ -9,9 +10,12 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; +@RequiredArgsConstructor @ControllerAdvice public class ModelAttributeControllerAdvice { + private final GroupCache groupCache; + // Add modelAttributes before each @RequestMapping @ModelAttribute public void modelAttributes(KeycloakAuthenticationToken token, diff --git a/src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java b/src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java similarity index 89% rename from src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java rename to src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java index d8e0680..e4d32a2 100644 --- a/src/main/java/mops/gruppen2/web/api/GroupRequestWrapper.java +++ b/src/main/java/mops/gruppen2/infrastructure/api/GroupRequestWrapper.java @@ -1,4 +1,4 @@ -package mops.gruppen2.web.api; +package mops.gruppen2.infrastructure.api; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/mops/gruppen2/web/APIController.java b/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java similarity index 79% rename from src/main/java/mops/gruppen2/web/APIController.java rename to src/main/java/mops/gruppen2/infrastructure/controller/APIController.java index 0a8dc5c..662bf99 100644 --- a/src/main/java/mops/gruppen2/web/APIController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java @@ -1,4 +1,4 @@ -package mops.gruppen2.web; +package mops.gruppen2.infrastructure.controller; import io.swagger.annotations.ApiOperation; @@ -6,12 +6,8 @@ import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; -import mops.gruppen2.domain.helper.APIHelper; -import mops.gruppen2.domain.helper.CommonHelper; -import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.service.EventStoreService; -import mops.gruppen2.domain.service.ProjectionService; -import mops.gruppen2.web.api.GroupRequestWrapper; +import mops.gruppen2.domain.service.helper.CommonHelper; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -19,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; -import java.util.UUID; /** * Api zum Datenabgleich. @@ -31,8 +26,9 @@ import java.util.UUID; @RequestMapping("/gruppen2/api") public class APIController { + //TODO: redo api + private final EventStoreService eventStoreService; - private final ProjectionService projectionService; /** * Erzeugt eine Liste aus Gruppen, welche sich seit einer übergebenen Event-Id geändert haben. @@ -40,15 +36,15 @@ public class APIController { * * @param eventId Die Event-ID, welche der Anfragesteller beim letzten Aufruf erhalten hat */ - @GetMapping("/update/{id}") + /*@GetMapping("/update/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt veränderte Gruppen zurück") public GroupRequestWrapper getApiUpdate(@ApiParam("Letzte gespeicherte EventId des Anfragestellers") @PathVariable("id") long eventId) { return APIHelper.wrap(eventStoreService.findMaxEventId(), - projectionService.projectChangedGroups(eventId)); - } + projectionHelper.projectChangedGroups(eventId)); + }*/ /** * Gibt die Gruppen-IDs von Gruppen, in welchen der übergebene Nutzer teilnimmt, zurück. @@ -65,13 +61,13 @@ public class APIController { /** * Konstruiert eine einzelne, vollständige Gruppe. */ - @GetMapping("/group/{id}") + /*@GetMapping("/group/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück") public Group getApiGroup(@ApiParam("Gruppen-Id der gefordeten Gruppe") @PathVariable("id") String groupId) { - return projectionService.projectGroupById(UUID.fromString(groupId)); - } + return projectionHelper.projectGroupById(UUID.fromString(groupId)); + }*/ } diff --git a/src/main/java/mops/gruppen2/web/GroupCreationController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java similarity index 87% rename from src/main/java/mops/gruppen2/web/GroupCreationController.java rename to src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java index af56d77..84d4e1e 100644 --- a/src/main/java/mops/gruppen2/web/GroupCreationController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GroupCreationController.java @@ -1,10 +1,8 @@ -package mops.gruppen2.web; +package mops.gruppen2.infrastructure.controller; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; -import mops.gruppen2.domain.helper.CsvHelper; -import mops.gruppen2.domain.helper.ValidationHelper; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.Type; import mops.gruppen2.domain.model.group.User; @@ -13,9 +11,10 @@ import mops.gruppen2.domain.model.group.wrapper.Limit; import mops.gruppen2.domain.model.group.wrapper.Parent; import mops.gruppen2.domain.model.group.wrapper.Title; import mops.gruppen2.domain.service.GroupService; -import mops.gruppen2.domain.service.ProjectionService; +import mops.gruppen2.domain.service.helper.CsvHelper; +import mops.gruppen2.domain.service.helper.ValidationHelper; +import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; -import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -35,21 +34,20 @@ import javax.validation.Valid; @RequestMapping("/gruppen2") public class GroupCreationController { + private final GroupCache groupCache; private final GroupService groupService; - private final ProjectionService projectionService; @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @GetMapping("/create") public String getCreate(Model model) { - model.addAttribute("lectures", projectionService.projectLectures()); + model.addAttribute("lectures", groupCache.lectures()); return "create"; } @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/create") - @CacheEvict(value = "groups", allEntries = true) public String postCreateOrga(KeycloakAuthenticationToken token, @RequestParam("type") Type type, @RequestParam("parent") @Valid Parent parent, diff --git a/src/main/java/mops/gruppen2/web/GroupDetailsController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java similarity index 81% rename from src/main/java/mops/gruppen2/web/GroupDetailsController.java rename to src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java index 58c3131..7aa5879 100644 --- a/src/main/java/mops/gruppen2/web/GroupDetailsController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GroupDetailsController.java @@ -1,19 +1,18 @@ -package mops.gruppen2.web; +package mops.gruppen2.infrastructure.controller; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; -import mops.gruppen2.domain.helper.CsvHelper; -import mops.gruppen2.domain.helper.ValidationHelper; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.User; import mops.gruppen2.domain.model.group.wrapper.Description; import mops.gruppen2.domain.model.group.wrapper.Limit; import mops.gruppen2.domain.model.group.wrapper.Title; import mops.gruppen2.domain.service.GroupService; -import mops.gruppen2.domain.service.ProjectionService; +import mops.gruppen2.domain.service.helper.CsvHelper; +import mops.gruppen2.domain.service.helper.ValidationHelper; +import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; -import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -36,8 +35,8 @@ import java.util.UUID; @RequestMapping("/gruppen2") public class GroupDetailsController { + private final GroupCache groupCache; private final GroupService groupService; - private final ProjectionService projectionService; @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @GetMapping("/details/{id}") @@ -46,10 +45,13 @@ public class GroupDetailsController { @PathVariable("id") String groupId) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); // Parent Badge - Group parent = projectionService.projectParent(group.getParent()); + Group parent = Group.EMPTY(); + if (group.hasParent()) { + parent = groupCache.group(group.getParent()); + } model.addAttribute("group", group); model.addAttribute("parent", parent); @@ -64,12 +66,11 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/join") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsJoin(KeycloakAuthenticationToken token, @PathVariable("id") String groupId) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); if (ValidationHelper.checkIfMember(group, principal)) { return "redirect:/gruppen2/details/" + groupId; @@ -82,12 +83,11 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/leave") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsLeave(KeycloakAuthenticationToken token, @PathVariable("id") String groupId) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); groupService.kickMember(group, principal, principal); @@ -102,7 +102,7 @@ public class GroupDetailsController { @PathVariable("id") String groupId) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); // Invite Link String actualURL = request.getRequestURL().toString(); @@ -119,14 +119,15 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/edit/meta") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditMeta(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, @Valid Title title, @Valid Description description) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); + + System.out.println(group); groupService.setTitle(group, principal, title); groupService.setDescription(group, principal, description); @@ -136,12 +137,11 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/edit/userlimit") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditUserLimit(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, @Valid Limit limit) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); groupService.setLimit(group, principal, limit); @@ -150,13 +150,12 @@ public class GroupDetailsController { @RolesAllowed("ROLE_orga") @PostMapping("/details/{id}/edit/csv") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditCsv(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, @RequestParam(value = "file", required = false) MultipartFile file) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); groupService.addUsersToGroup(group, principal, CsvHelper.readCsvFile(file)); @@ -165,13 +164,12 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/edit/role/{userid}") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditRole(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, @PathVariable("userid") String target) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); ValidationHelper.throwIfNoAdmin(group, principal); @@ -187,13 +185,12 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/edit/delete/{userid}") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditDelete(KeycloakAuthenticationToken token, @PathVariable("id") String groupId, @PathVariable("userid") String target) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupId)); + Group group = groupCache.group(UUID.fromString(groupId)); ValidationHelper.throwIfNoAdmin(group, principal); @@ -207,12 +204,11 @@ public class GroupDetailsController { @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @PostMapping("/details/{id}/edit/destroy") - @CacheEvict(value = "groups", allEntries = true) public String postDetailsEditDestroy(KeycloakAuthenticationToken token, @PathVariable("id") String groupid) { String principal = token.getName(); - Group group = projectionService.projectGroupById(UUID.fromString(groupid)); + Group group = groupCache.group(UUID.fromString(groupid)); groupService.deleteGroup(group, principal); diff --git a/src/main/java/mops/gruppen2/web/GruppenfindungController.java b/src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java similarity index 75% rename from src/main/java/mops/gruppen2/web/GruppenfindungController.java rename to src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java index 465be52..15e1953 100644 --- a/src/main/java/mops/gruppen2/web/GruppenfindungController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/GruppenfindungController.java @@ -1,10 +1,10 @@ -package mops.gruppen2.web; +package mops.gruppen2.infrastructure.controller; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCall; import mops.gruppen2.domain.exception.PageNotFoundException; -import mops.gruppen2.domain.service.ProjectionService; +import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -20,22 +20,25 @@ import javax.servlet.http.HttpServletRequest; @Controller public class GruppenfindungController { - private final ProjectionService projectionService; + private final GroupCache groupCache; // For convenience - //@GetMapping("") - //public String redirect() { - // return "redirect:/gruppen2"; - //} + @GetMapping("") + public String redirect() { + return "redirect:/gruppen2"; + } + + @GetMapping("/login") + public String login() { + return "redirect:/gruppen2"; + } @TraceMethodCall @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @GetMapping("/gruppen2") public String getIndexPage(KeycloakAuthenticationToken token, Model model) { - - String principal = token.getName(); - model.addAttribute("groups", projectionService.projectUserGroups(principal)); + model.addAttribute("groups", groupCache.userGroups(token.getName())); return "index"; } diff --git a/src/main/java/mops/gruppen2/web/SearchAndInviteController.java b/src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java similarity index 90% rename from src/main/java/mops/gruppen2/web/SearchAndInviteController.java rename to src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java index 10bc6f6..e9e9e16 100644 --- a/src/main/java/mops/gruppen2/web/SearchAndInviteController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/SearchAndInviteController.java @@ -1,13 +1,13 @@ -package mops.gruppen2.web; +package mops.gruppen2.infrastructure.controller; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; -import mops.gruppen2.domain.helper.ValidationHelper; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.model.group.Type; -import mops.gruppen2.domain.service.ProjectionService; import mops.gruppen2.domain.service.SearchService; +import mops.gruppen2.domain.service.helper.ValidationHelper; +import mops.gruppen2.infrastructure.GroupCache; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -29,7 +29,7 @@ import java.util.List; @RequestMapping("/gruppen2") public class SearchAndInviteController { - private final ProjectionService projectionService; + private final GroupCache groupCache; private final SearchService searchService; @RolesAllowed({"ROLE_orga", "ROLE_studentin"}) @@ -62,7 +62,7 @@ public class SearchAndInviteController { @PathVariable("link") String link) { String principal = token.getName(); - Group group = projectionService.projectGroupByLink(link); + Group group = groupCache.group(link); model.addAttribute("group", group); diff --git a/src/main/java/mops/gruppen2/persistance/EventRepository.java b/src/main/java/mops/gruppen2/persistance/EventRepository.java index d5b5164..0b187d7 100644 --- a/src/main/java/mops/gruppen2/persistance/EventRepository.java +++ b/src/main/java/mops/gruppen2/persistance/EventRepository.java @@ -11,43 +11,21 @@ import java.util.List; @Repository public interface EventRepository extends CrudRepository { - // ####################################### GROUP IDs ######################################### - - /*@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 findGroupIdsWhereEventIdGreaterThanStatus(@Param("status") long status); // ####################################### 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") String... userIds);*/ + @Query("SELECT * FROM event WHERE event_id > :version AND event_id <= :max") + List findNewEvents(@Param("version") long version, + @Param("max") long maxid); - @Query("SELECT * FROM event" - + " WHERE event_type IN (:types)") - List findEventDTOsByType(@Param("types") List types); + @Query("SELECT * FROM event") + List findAllEvents(); - @Query("SELECT * FROM event" - + " WHERE event_type IN (:types) AND group_id IN (:groupIds)") - List findEventDTOsByGroupAndType(@Param("groupIds") List groupIds, - @Param("types") List types); - - /*@Query("SELECT * FROM event" - + " WHERE event_type IN (:types) AND user_id = :userId") - List findEventDTOsByUserAndType(@Param("userId") String userId, - @Param("types") String... types);*/ // ################################ LATEST EVENT DTOs ######################################## + @Query("WITH ranked_events AS (" + "SELECT *, ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY event_id DESC) AS rn" + " FROM event" diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 13f17ef..824d245 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -12,11 +12,6 @@ spring.datasource.password = spring.jpa.database-platform = org.hibernate.dialect.H2Dialect spring.h2.console.enabled = false -# Security -keycloak.auth-server-url = http://localhost:8082/auth -hhu_keycloak.token-uri = http://localhost:8082/auth/realms/Gruppen/protocol/openid-connect/token - - # Misc server.error.include-stacktrace = always management.endpoints.web.exposure.include = info,health diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 864d2dc..a9faa16 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -10,11 +10,6 @@ spring.datasource.url = jdbc:mysql://dbmysql:3306/gruppen spring.datasource.username = gruppen spring.datasource.password = password -# Security -keycloak.auth-server-url = http://localhost:8082/auth -hhu_keycloak.token-uri = http://localhost:8082/auth/realms/Gruppen/protocol/openid-connect/token - - # Misc management.endpoints.web.exposure.include = info,health server.error.include-stacktrace = always diff --git a/src/main/resources/application-heroku.properties b/src/main/resources/application-heroku.properties index 3839b1b..2ddb9d8 100644 --- a/src/main/resources/application-heroku.properties +++ b/src/main/resources/application-heroku.properties @@ -10,10 +10,6 @@ spring.datasource.url = mysql://b4b665d39d0670:cc933ff7@eu spring.datasource.username = b4b665d39d0670 spring.datasource.password = cc933ff7 -# Security -keycloak.auth-server-url = https://gruppenkeycloak.herokuapp.com/auth -hhu_keycloak.token-uri = https://gruppenkeycloak.herokuapp.com/auth/realms/master/protocol/openid-connect/token - # Misc management.endpoints.web.exposure.include = info,health diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6866cba..45828c0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -11,6 +11,8 @@ spring.profiles.active = dev #keycloak.use-resource-role-mappings = true #keycloak.autodetect-bearer-only = true #keycloak.confidential-port = 443 +keycloak.auth-server-url = https://gruppenkeycloak.herokuapp.com/auth +hhu_keycloak.token-uri = https://gruppenkeycloak.herokuapp.com/auth/realms/master/protocol/openid-connect/token keycloak.principal-attribute = preferred_username keycloak.realm = master keycloak.resource = gruppen-app diff --git a/src/test/java/mops/gruppen2/architecture/ControllerTest.java b/src/test/java/mops/gruppen2/architecture/ControllerTest.java index 0920791..5867549 100644 --- a/src/test/java/mops/gruppen2/architecture/ControllerTest.java +++ b/src/test/java/mops/gruppen2/architecture/ControllerTest.java @@ -30,7 +30,7 @@ class ControllerTest { public static final ArchRule controllerClassesShouldBeInControllerPackage = classes() .that().areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) - .should().resideInAPackage("..web"); + .should().resideInAPackage("..controller"); @ArchTest public static final ArchRule classesInControllerPackageShouldHaveControllerInName = classes() diff --git a/src/test/java/mops/gruppen2/architecture/ServiceTest.java b/src/test/java/mops/gruppen2/architecture/ServiceTest.java index f71a9aa..84477a6 100644 --- a/src/test/java/mops/gruppen2/architecture/ServiceTest.java +++ b/src/test/java/mops/gruppen2/architecture/ServiceTest.java @@ -25,11 +25,11 @@ class ServiceTest { @ArchTest public static final ArchRule serviceClassesShouldBeInServicePackage = classes() .that().areAnnotatedWith(Service.class) - .should().resideInAPackage("..service.."); + .should().resideInAPackage("..service"); @ArchTest public static final ArchRule classesInServicePackageShouldHaveServiceInName = classes() - .that().resideInAPackage("..service..") + .that().resideInAPackage("..service") .should().haveSimpleNameEndingWith("Service"); @ArchIgnore