Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

14 changes: 14 additions & 0 deletions src/main/java/io/nickreuter/retroapi/notification/EventType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.nickreuter.retroapi.notification;

public enum EventType {
CREATE,
UPDATE,
DELETE,
TIMER_START,
TIMER_STOP,
FOCUS,
FOCUS_CLEAR,
SORT,
PHASE,
RETRO_FINISHED
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ public AuthorizationManager<Message<?>> messageAuthorizationManager(MessageMatch
messages
.simpTypeMatchers(UNSUBSCRIBE, DISCONNECT).permitAll()
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/topic/*.thoughts").access(this::isAuthorizedRetroSubscription)
.simpSubscribeDestMatchers("/topic/*.finished").access(this::isAuthorizedRetroSubscription)
.simpSubscribeDestMatchers("/topic/*.action-items").access((this::isAuthorizedTeamSubscription))
.simpSubscribeDestMatchers("/topic/retros.*.thoughts").access(this::isAuthorizedRetroSubscription)
.simpSubscribeDestMatchers("/topic/retros.*.events").access(this::isAuthorizedRetroSubscription)
.simpSubscribeDestMatchers("/topic/teams.*.action-items").access(this::isAuthorizedTeamSubscription)
.simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll()
.anyMessage().denyAll();
return messages.build();
Expand Down Expand Up @@ -158,7 +158,7 @@ ChannelInterceptor csrfChannelInterceptor() {
}

private AuthorizationDecision isAuthorizedRetroSubscription(Supplier<Authentication> authentication, MessageAuthorizationContext<?> object) {
var ids = getIdFromTopic(object, "^/topic/(?<retroId>.*)\\..*");
var ids = getIdFromTopic(object, "^/topic/retros\\.(?<retroId>[^.]+)\\..*");
if (!ids.find()) {
return new AuthorizationDecision(false);
}
Expand All @@ -181,7 +181,7 @@ private AuthorizationDecision isAuthorizedRetroSubscription(Supplier<Authenticat
}

private AuthorizationDecision isAuthorizedTeamSubscription(Supplier<Authentication> authentication, MessageAuthorizationContext<?> object) {
var ids = getIdFromTopic(object, "^/topic/(?<teamId>.*)\\..*$");
var ids = getIdFromTopic(object, "^/topic/teams\\.(?<teamId>[^.]+)\\..*$");
AuthorizationDecision isAuthorized = ids.find()
// TODO: Add error handling for when this fails because the UUID is too big
? new AuthorizationDecision(userMappingAuthorizationService.isUserMemberOfTeam(authentication.get(), UUID.fromString(ids.group("teamId"))))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package io.nickreuter.retroapi.notification.event;

import io.nickreuter.retroapi.notification.ActionType;
import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.team.actionitem.ActionItemEntity;

import java.util.UUID;

public class ActionItemEvent extends BaseEvent{
private static final String ROUTE_STRING = "/topic/%s.action-items";
private static final String ROUTE_STRING = "/topic/teams.%s.action-items";
private final UUID teamId;

public ActionItemEvent(Object source, ActionType actionType, ActionItemEntity payload, UUID teamId) {
super(source, actionType, payload);
public ActionItemEvent(Object source, EventType eventType, ActionItemEntity payload, UUID teamId) {
super(source, eventType, payload);
this.teamId = teamId;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package io.nickreuter.retroapi.notification.event;

import io.nickreuter.retroapi.notification.ActionType;
import io.nickreuter.retroapi.notification.EventType;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;

@Getter
public abstract class BaseEvent extends ApplicationEvent {
private final ActionType actionType;
private final EventType eventType;
private final Object payload;

public BaseEvent(Object source, ActionType actionType, Object payload) {
public BaseEvent(Object source, EventType eventType, Object payload) {
super(source);
this.actionType = actionType;
this.eventType = eventType;
this.payload = payload;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package io.nickreuter.retroapi.notification.event;

import io.nickreuter.retroapi.notification.ActionType;
import io.nickreuter.retroapi.notification.EventType;

import java.util.UUID;

public class RetroFinishedEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/%s.finished";
private static final String ROUTE_STRING = "/topic/retros.%s.events";
private final UUID retroId;

public RetroFinishedEvent(Object source, ActionType actionType, boolean isFinished, UUID retroId) {
super(source, actionType, isFinished);
public RetroFinishedEvent(Object source, boolean isFinished, UUID retroId) {
super(source, EventType.RETRO_FINISHED, isFinished);
this.retroId = retroId;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package io.nickreuter.retroapi.notification.event;

import io.nickreuter.retroapi.notification.ActionType;
import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.retro.thought.ThoughtEntity;

import java.util.UUID;

public class ThoughtEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/%s.thoughts";
private static final String ROUTE_STRING = "/topic/retros.%s.thoughts";
private final UUID retroId;

public ThoughtEvent(Object source, ActionType actionType, ThoughtEntity payload, UUID retroId) {
super(source, actionType, payload);
public ThoughtEvent(Object source, EventType eventType, ThoughtEntity payload, UUID retroId) {
super(source, eventType, payload);
this.retroId = retroId;
}

Expand Down
3 changes: 1 addition & 2 deletions src/main/java/io/nickreuter/retroapi/retro/RetroService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.nickreuter.retroapi.retro;

import io.nickreuter.retroapi.notification.ActionType;
import io.nickreuter.retroapi.notification.event.RetroFinishedEvent;
import io.nickreuter.retroapi.retro.template.Template;
import org.springframework.context.ApplicationEventPublisher;
Expand Down Expand Up @@ -53,6 +52,6 @@ public void setFinished(UUID retroId, boolean finished) throws RetroNotFoundExce
var retro = retroRepository.findById(retroId).orElseThrow(RetroNotFoundException::new);
retro.setFinished(finished);
retroRepository.save(retro);
applicationEventPublisher.publishEvent(new RetroFinishedEvent(this, ActionType.UPDATE, finished, retroId));
applicationEventPublisher.publishEvent(new RetroFinishedEvent(this, finished, retroId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.nickreuter.retroapi.retro.event;

import java.util.UUID;

public record FocusRequest(UUID thoughtId) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.nickreuter.retroapi.retro.event;

public record PhaseRequest(String phase) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.nickreuter.retroapi.retro.event;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

@RestController
@RequestMapping("/api/teams/{teamId}/retros/{retroId}/events")
public class RetroEventController {
private final RetroEventService retroEventService;

public RetroEventController(RetroEventService retroEventService) {
this.retroEventService = retroEventService;
}

@PostMapping("/timer-start")
@PreAuthorize("@userMappingAuthorizationService.isUserMemberOfTeam(authentication, #teamId)")
public ResponseEntity<Void> startTimer(@PathVariable UUID teamId, @PathVariable UUID retroId, @RequestBody TimerStartRequest request) {
retroEventService.publishTimerStart(retroId, request.durationSeconds());
return ResponseEntity.ok().build();
}

@PostMapping("/timer-stop")
@PreAuthorize("@userMappingAuthorizationService.isUserMemberOfTeam(authentication, #teamId)")
public ResponseEntity<Void> stopTimer(@PathVariable UUID teamId, @PathVariable UUID retroId) {
retroEventService.publishTimerStop(retroId);
return ResponseEntity.ok().build();
}

@PostMapping("/focus")
@PreAuthorize("@userMappingAuthorizationService.isUserMemberOfTeam(authentication, #teamId)")
public ResponseEntity<Void> focusThought(@PathVariable UUID teamId, @PathVariable UUID retroId, @RequestBody FocusRequest request) {
retroEventService.publishFocus(retroId, request.thoughtId());
return ResponseEntity.ok().build();
}

@PostMapping("/focus-clear")
@PreAuthorize("@userMappingAuthorizationService.isUserMemberOfTeam(authentication, #teamId)")
public ResponseEntity<Void> clearFocus(@PathVariable UUID teamId, @PathVariable UUID retroId) {
retroEventService.publishFocusClear(retroId);
return ResponseEntity.ok().build();
}

@PostMapping("/sort")
@PreAuthorize("@userMappingAuthorizationService.isUserMemberOfTeam(authentication, #teamId)")
public ResponseEntity<Void> sortColumn(@PathVariable UUID teamId, @PathVariable UUID retroId, @RequestBody SortRequest request) {
retroEventService.publishSort(retroId, request.column(), request.direction());
return ResponseEntity.ok().build();
}

@PostMapping("/phase")
@PreAuthorize("@userMappingAuthorizationService.isUserMemberOfTeam(authentication, #teamId)")
public ResponseEntity<Void> changePhase(@PathVariable UUID teamId, @PathVariable UUID retroId, @RequestBody PhaseRequest request) {
retroEventService.publishPhase(retroId, request.phase());
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.nickreuter.retroapi.retro.event;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.Map;
import java.util.UUID;

@Service
public class RetroEventService {
private final ApplicationEventPublisher applicationEventPublisher;

public RetroEventService(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

public void publishTimerStart(UUID retroId, int durationSeconds) {
var payload = Map.of("durationSeconds", durationSeconds, "startedAt", Instant.now().toString());
applicationEventPublisher.publishEvent(new RetroTimerStartEvent(this, payload, retroId));
}

public void publishTimerStop(UUID retroId) {
applicationEventPublisher.publishEvent(new RetroTimerStopEvent(this, null, retroId));
}

public void publishFocus(UUID retroId, UUID thoughtId) {
var payload = Map.of("thoughtId", thoughtId.toString());
applicationEventPublisher.publishEvent(new RetroFocusEvent(this, payload, retroId));
}

public void publishFocusClear(UUID retroId) {
applicationEventPublisher.publishEvent(new RetroFocusClearEvent(this, null, retroId));
}

public void publishSort(UUID retroId, String column, String direction) {
var payload = Map.of("column", column, "direction", direction);
applicationEventPublisher.publishEvent(new RetroSortEvent(this, payload, retroId));
}

public void publishPhase(UUID retroId, String phase) {
var payload = Map.of("phase", phase);
applicationEventPublisher.publishEvent(new RetroPhaseEvent(this, payload, retroId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.nickreuter.retroapi.retro.event;

import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.notification.event.BaseEvent;

import java.util.UUID;

public class RetroFocusClearEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/retros.%s.events";
private final UUID retroId;

public RetroFocusClearEvent(Object source, Object payload, UUID retroId) {
super(source, EventType.FOCUS_CLEAR, payload);
this.retroId = retroId;
}

@Override
public String getRoute() {
return String.format(ROUTE_STRING, retroId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.nickreuter.retroapi.retro.event;

import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.notification.event.BaseEvent;

import java.util.UUID;

public class RetroFocusEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/retros.%s.events";
private final UUID retroId;

public RetroFocusEvent(Object source, Object payload, UUID retroId) {
super(source, EventType.FOCUS, payload);
this.retroId = retroId;
}

@Override
public String getRoute() {
return String.format(ROUTE_STRING, retroId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.nickreuter.retroapi.retro.event;

import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.notification.event.BaseEvent;

import java.util.UUID;

public class RetroPhaseEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/retros.%s.events";
private final UUID retroId;

public RetroPhaseEvent(Object source, Object payload, UUID retroId) {
super(source, EventType.PHASE, payload);
this.retroId = retroId;
}

@Override
public String getRoute() {
return String.format(ROUTE_STRING, retroId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.nickreuter.retroapi.retro.event;

import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.notification.event.BaseEvent;

import java.util.UUID;

public class RetroSortEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/retros.%s.events";
private final UUID retroId;

public RetroSortEvent(Object source, Object payload, UUID retroId) {
super(source, EventType.SORT, payload);
this.retroId = retroId;
}

@Override
public String getRoute() {
return String.format(ROUTE_STRING, retroId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.nickreuter.retroapi.retro.event;

import io.nickreuter.retroapi.notification.EventType;
import io.nickreuter.retroapi.notification.event.BaseEvent;

import java.util.UUID;

public class RetroTimerStartEvent extends BaseEvent {
private static final String ROUTE_STRING = "/topic/retros.%s.events";
private final UUID retroId;

public RetroTimerStartEvent(Object source, Object payload, UUID retroId) {
super(source, EventType.TIMER_START, payload);
this.retroId = retroId;
}

@Override
public String getRoute() {
return String.format(ROUTE_STRING, retroId);
}
}
Loading
Loading