diff --git a/src/main/java/mops/gruppen2/domain/event/Event.java b/src/main/java/mops/gruppen2/domain/event/Event.java index eee06b4..9f87d20 100644 --- a/src/main/java/mops/gruppen2/domain/event/Event.java +++ b/src/main/java/mops/gruppen2/domain/event/Event.java @@ -82,6 +82,18 @@ public abstract class Event { applyEvent(group); } + public void apply(Group group) throws EventException { + log.trace("Event wird angewendet:\t{}", this); + + if (version == 0) { + throw new BadArgumentException("Event wurde nicht initialisiert."); + } + + checkGroupIdMatch(group.getId()); + group.updateVersion(version); + applyEvent(group); + } + private void checkGroupIdMatch(UUID groupid) throws IdMismatchException { // CreateGroupEvents müssen die Id erst initialisieren if (this instanceof CreateGroupEvent) { diff --git a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java index e17778a..31ea0b8 100644 --- a/src/main/java/mops/gruppen2/domain/service/EventStoreService.java +++ b/src/main/java/mops/gruppen2/domain/service/EventStoreService.java @@ -5,12 +5,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.exception.BadPayloadException; +import mops.gruppen2.domain.service.helper.CommonHelper; import mops.gruppen2.domain.service.helper.FileHelper; import mops.gruppen2.persistance.EventRepository; import mops.gruppen2.persistance.dto.EventDTO; import org.springframework.stereotype.Service; import java.sql.Timestamp; +import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -102,6 +104,14 @@ public class EventStoreService { return getEventsFromDTOs(eventStore.findGroupEvents(groupId.toString())); } + public List findGroupEvents(List ids) { + return ids.stream() + .map(id -> eventStore.findGroupEvents(id.toString())) + .map(EventStoreService::getEventsFromDTOs) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + public List findGroupPayloads(UUID groupId) { return eventStore.findGroupPayloads(groupId.toString()); } @@ -109,4 +119,12 @@ public class EventStoreService { public List findGroupDTOs(UUID groupid) { return eventStore.findGroupEvents(groupid.toString()); } + + public List findChangedGroups(long eventid) { + return CommonHelper.stringsToUUID(eventStore.findChangedGroupIds(eventid)); + } + + public long findMaxEventId() { + return eventStore.findMaxEventId(); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java index 906d979..ec2e925 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/APIHelper.java @@ -3,13 +3,25 @@ package mops.gruppen2.domain.service.helper; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.infrastructure.api.GroupRequestWrapper; +import mops.gruppen2.infrastructure.api.GroupWrapper; + +import java.util.List; +import java.util.stream.Collectors; //TODO: sinnvolles format @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class APIHelper { - /*public static GroupRequestWrapper wrap(long status, List groupList) { - return new GroupRequestWrapper(status, groupList); - }*/ + public static GroupRequestWrapper wrap(long status, List groupList) { + return new GroupRequestWrapper(status, wrap(groupList)); + } + + public static List wrap(List groups) { + return groups.stream() + .map(GroupWrapper::new) + .collect(Collectors.toUnmodifiableList()); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java index 4694501..25a8575 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/CommonHelper.java @@ -4,7 +4,9 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -13,4 +15,10 @@ public final class CommonHelper { public static boolean uuidIsEmpty(UUID uuid) { return "00000000-0000-0000-0000-000000000000".equals(uuid.toString()); } + + public static List stringsToUUID(List changedGroupIds) { + return changedGroupIds.stream() + .map(UUID::fromString) + .collect(Collectors.toUnmodifiableList()); + } } diff --git a/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java b/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java index f4f9011..022166d 100644 --- a/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java +++ b/src/main/java/mops/gruppen2/domain/service/helper/ProjectionHelper.java @@ -7,6 +7,9 @@ import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.infrastructure.GroupCache; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -19,6 +22,22 @@ import java.util.UUID; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ProjectionHelper { + + public static List project(List events) { + Map groups = new HashMap<>(); + + if (events.isEmpty()) { + return Collections.emptyList(); + } + + log.trace(groups); + log.trace(events); + + events.forEach(event -> event.apply(getOrCreateGroup(groups, event.getGroupid()))); + + return new ArrayList<>(groups.values()); + } + public static void project(Map groups, List events, GroupCache cache) { if (events.isEmpty()) { return; diff --git a/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java b/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java index 2fb3fba..75b628d 100644 --- a/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java +++ b/src/main/java/mops/gruppen2/infrastructure/api/GroupWrapper.java @@ -1,4 +1,31 @@ package mops.gruppen2.infrastructure.api; +import lombok.Value; +import mops.gruppen2.domain.model.group.Group; +import mops.gruppen2.domain.model.group.Type; +import mops.gruppen2.domain.model.group.User; + +import java.util.List; +import java.util.UUID; + +@Value public class GroupWrapper { + + UUID groupid; + Type type; + UUID parent; + String title; + String description; + List admins; + List regulars; + + public GroupWrapper(Group group) { + groupid = group.getId(); + type = group.getType(); + parent = group.getParent(); + title = group.getTitle(); + description = group.getDescription(); + admins = group.getAdmins(); + regulars = group.getRegulars(); + } } diff --git a/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java b/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java index ac02052..3e3f9b9 100644 --- a/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java +++ b/src/main/java/mops/gruppen2/infrastructure/controller/APIController.java @@ -1,13 +1,27 @@ package mops.gruppen2.infrastructure.controller; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import mops.gruppen2.aspect.annotation.TraceMethodCalls; +import mops.gruppen2.domain.model.group.Group; import mops.gruppen2.domain.service.EventStoreService; +import mops.gruppen2.domain.service.helper.APIHelper; +import mops.gruppen2.domain.service.helper.ProjectionHelper; +import mops.gruppen2.infrastructure.GroupCache; +import mops.gruppen2.infrastructure.api.GroupRequestWrapper; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + /** * Api zum Datenabgleich. */ @@ -18,8 +32,7 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/gruppen2/api") public class APIController { - //TODO: redo api - + private final GroupCache cache; private final EventStoreService eventStoreService; /** @@ -28,38 +41,41 @@ 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(), - projectionHelper.projectChangedGroups(eventId)); - }*/ + ProjectionHelper.project(eventStoreService.findGroupEvents(eventStoreService.findChangedGroups(eventId)))); + } /** * Gibt die Gruppen-IDs von Gruppen, in welchen der übergebene Nutzer teilnimmt, zurück. */ - /*@GetMapping("/usergroups/{id}") + @GetMapping("/usergroups/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt Gruppen zurück, in welchen ein Nutzer teilnimmt") public List getApiUserGroups(@ApiParam("Nutzer-Id") @PathVariable("id") String userId) { - return CommonHelper.uuidsToString(eventStoreService.findExistingUserGroups(userId)); - }*/ + return cache.userGroups(userId).stream() + .map(Group::getId) + .map(UUID::toString) + .collect(Collectors.toUnmodifiableList()); + } /** * Konstruiert eine einzelne, vollständige Gruppe. */ - /*@GetMapping("/group/{id}") + @GetMapping("/group/{id}") @Secured("ROLE_api_user") @ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück") public Group getApiGroup(@ApiParam("Gruppen-Id der gefordeten Gruppe") @PathVariable("id") String groupId) { - return projectionHelper.projectGroupById(UUID.fromString(groupId)); - }*/ + return cache.group(UUID.fromString(groupId)); + } } diff --git a/src/main/java/mops/gruppen2/persistance/EventRepository.java b/src/main/java/mops/gruppen2/persistance/EventRepository.java index 5ce23fb..8d21c79 100644 --- a/src/main/java/mops/gruppen2/persistance/EventRepository.java +++ b/src/main/java/mops/gruppen2/persistance/EventRepository.java @@ -23,4 +23,10 @@ public interface EventRepository extends CrudRepository { @Query("SELECT event_payload FROM event WHERE group_id = :groupid") List findGroupPayloads(@Param("groupid") String groupId); + + @Query("SELECT MAX(event_id) FROM event") + long findMaxEventId(); + + @Query("SELECT DISTINCT group_id FROM event WHERE event_id > :eventid") + List findChangedGroupIds(@Param("eventid") long eventid); } diff --git a/src/main/resources/templates/details.html b/src/main/resources/templates/details.html index cb43a23..a271bb4 100644 --- a/src/main/resources/templates/details.html +++ b/src/main/resources/templates/details.html @@ -20,7 +20,7 @@
- +
diff --git a/src/test/java/mops/gruppen2/TestBuilder.java b/src/test/java/mops/gruppen2/TestBuilder.java deleted file mode 100644 index d1c666a..0000000 --- a/src/test/java/mops/gruppen2/TestBuilder.java +++ /dev/null @@ -1,297 +0,0 @@ -package mops.gruppen2; - -public class TestBuilder { - - /*private static final Faker faker = new Faker(); - - public static Account account(String name) { - return new Account(name, - "", - "", - "", - "", - null); - } - - public static Group apply(Group group, Event... events) { - for (Event event : events) { - event.apply(group); - } - - return group; - } - - public static Group apply(Event... events) { - return apply(new Group(), events); - } - - *//** - * Baut eine UUID. - * - * @param id Integer id - * - * @return UUID - *//* - public static UUID uuidMock(int id) { - String idString = String.valueOf(Math.abs(id + 1)); - return UUID.fromString("00000000-0000-0000-0000-" - + "0".repeat(11 - idString.length()) - + idString); - } - - *//** - * Generiert ein EventLog mit mehreren Gruppen und Usern. - * - * @param count Gruppenanzahl - * @param membercount Mitgliederanzahl pro Gruppe - * - * @return Eventliste - *//* - public static List completePublicGroups(int count, int membercount) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> completePublicGroup(membercount)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - public static List completePrivateGroups(int count, int membercount) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> completePrivateGroup(membercount)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - public static List completePublicGroup(int membercount) { - List eventList = new ArrayList<>(); - UUID groupId = UUID.randomUUID(); - - eventList.add(createPublicGroupEvent(groupId)); - eventList.add(updateGroupTitleEvent(groupId)); - eventList.add(updateGroupDescriptionEvent(groupId)); - eventList.add(new SetLimitEvent(groupId, "fgsadggas", new Limit(Long.MAX_VALUE))); - eventList.addAll(addUserEvents(membercount, groupId)); - - return eventList; - } - - public static List completePrivateGroup(int membercount) { - List eventList = new ArrayList<>(); - UUID groupId = UUID.randomUUID(); - - eventList.add(createPrivateGroupEvent(groupId)); - eventList.add(updateGroupTitleEvent(groupId)); - eventList.add(updateGroupDescriptionEvent(groupId)); - eventList.add(new SetLimitEvent(groupId, "fgsadggas", new Limit(Long.MAX_VALUE))); - eventList.addAll(addUserEvents(membercount, groupId)); - - return eventList; - } - - public static List completePublicGroup() { - return completePublicGroup(100); - } - - public static List completePrivateGroup() { - return completePrivateGroup(100); - } - - *//** - * Generiert mehrere CreateGroupEvents, 1 <= groupId <= count. - * - * @param count Anzahl der verschiedenen Gruppen - * - * @return Eventliste - *//* - public static List createPublicGroupEvents(int count) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> createPublicGroupEvent()) - .collect(Collectors.toList()); - } - - public static List createPrivateGroupEvents(int count) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> createPublicGroupEvent()) - .collect(Collectors.toList()); - } - - public static List createMixedGroupEvents(int count) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> faker.random().nextInt(0, 1) > 0.5 - ? createPublicGroupEvent() - : createPrivateGroupEvent()) - .collect(Collectors.toList()); - } - - public static List createPrivateGroupEvents(UUID groupId) { - return (new ArrayList<>()).addAll(createGroupEvent(groupId)), - new SetTypeEvent(groupId); - } - - public static Event createPrivateGroupEvent() { - return createPrivateGroupEvent(UUID.randomUUID()); - } - - public static Event createPublicGroupEvent(UUID groupId) { - return createGroupEvent(groupId, Type.PUBLIC); - } - - public static Event createPublicGroupEvent() { - return createPublicGroupEvent(UUID.randomUUID()); - } - - public static Event createGroupEvent(UUID groupId) { - return new CreateGroupEvent(groupId, - faker.random().hex(), - LocalDateTime.now()); - } - - public static Event createLectureEvent() { - return createLectureEvent(UUID.randomUUID()); - } - - public static Event createLectureEvent(UUID groupId) { - return new CreateGroupEvent( - groupId, - faker.random().hex(), - null, - Type.LECTURE - ); - } - - *//** - * Generiert mehrere AddUserEvents für eine Gruppe, 1 <= user_id <= count. - * - * @param count Anzahl der Mitglieder - * @param groupId Gruppe, zu welcher geaddet wird - * - * @return Eventliste - *//* - public static List addUserEvents(int count, UUID groupId) { - return IntStream.range(0, count) - .parallel() - .mapToObj(i -> addUserEvent(groupId, String.valueOf(i))) - .collect(Collectors.toList()); - } - - public static Event addUserEvent(UUID groupId, String userId) { - String firstname = firstname(); - String lastname = lastname(); - - return new AddMemberEvent( - groupId, - userId, - firstname, - lastname, - firstname + "." + lastname + "@mail.de" - ); - } - - public static Event addUserEvent(UUID groupId) { - return addUserEvent(groupId, faker.random().hex()); - } - - // Am besten einfach nicht benutzen - public static List deleteUserEvents(int count, List eventList) { - List removeEvents = new ArrayList<>(); - List shuffle = eventList.parallelStream() - .filter(event -> event instanceof AddMemberEvent) - .collect(Collectors.toList()); - - Collections.shuffle(shuffle); - - for (Event event : shuffle) { - removeEvents.add(new KickMemberEvent(event.getGroupid(), event.getTarget())); - - if (removeEvents.size() >= count) { - break; - } - } - - return removeEvents; - } - - *//** - * Erzeugt mehrere DeleteUserEvents, sodass eine Gruppe komplett geleert wird. - * - * @param group Gruppe welche geleert wird - * - * @return Eventliste - *//* - public static List deleteUserEvents(Group group) { - return group.getMemberships().parallelStream() - .map(user -> deleteUserEvent(group.getGroupid(), user.getUserId())) - .collect(Collectors.toList()); - } - - public static Event deleteUserEvent(UUID groupId, String userId) { - return new KickMemberEvent( - groupId, - userId - ); - } - - public static Event updateGroupDescriptionEvent(UUID groupId) { - return updateGroupDescriptionEvent(groupId, quote()); - } - - public static Event updateGroupDescriptionEvent(UUID groupId, String description) { - return new SetDescriptionEvent( - groupId, - faker.random().hex(), - description - ); - } - - public static Event updateGroupTitleEvent(UUID groupId) { - return updateGroupTitleEvent(groupId, champion()); - } - - public static Event updateGroupTitleEvent(UUID groupId, String title) { - return new SetTitleEvent( - groupId, - faker.random().hex(), - title - ); - } - - public static Event updateUserLimitMaxEvent(UUID groupId) { - return new SetLimitEvent(groupId, firstname(), Long.MAX_VALUE); - } - - public static Event updateRoleEvent(UUID groupId, String userId, Role role) { - return new UpdateRoleEvent( - groupId, - userId, - role - ); - } - - public static Event deleteGroupEvent(UUID groupId) { - return new DestroyGroupEvent(groupId, faker.random().hex()); - } - - private static String firstname() { - return clean(faker.name().firstName()); - } - - private static String lastname() { - return clean(faker.name().lastName()); - } - - private static String champion() { - return clean(faker.leagueOfLegends().champion()); - } - - private static String quote() { - return clean(faker.leagueOfLegends().quote()); - } - - private static String clean(String string) { - return string.replaceAll("['\";,]", ""); - }*/ -}