Skip to content

Commit ed0e71c

Browse files
authored
Merge pull request #39 from remotemobprogramming/goals
add goal to room
2 parents cf293a4 + b27c4ea commit ed0e71c

File tree

10 files changed

+432
-102
lines changed

10 files changed

+432
-102
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
meta {
2+
name: Delete goal for room
3+
type: http
4+
seq: 4
5+
}
6+
7+
delete {
8+
url: {{baseUrl}}/{{roomId}}/goal
9+
body: json
10+
auth: none
11+
}
12+
13+
body:json {
14+
{
15+
"user": "{{user}}"
16+
}
17+
}
18+
19+
vars:pre-request {
20+
roomId: test123
21+
user: TestUser
22+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
meta {
2+
name: Get goal for room
3+
type: http
4+
seq: 5
5+
}
6+
7+
get {
8+
url: {{baseUrl}}/{{roomId}}/goal
9+
body: none
10+
auth: none
11+
}
12+
13+
vars:pre-request {
14+
roomId: test123
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
meta {
2+
name: Set goal for room
3+
type: http
4+
seq: 3
5+
}
6+
7+
put {
8+
url: {{baseUrl}}/{{roomId}}/goal
9+
body: json
10+
auth: none
11+
}
12+
13+
body:json {
14+
{
15+
"goal": "{{goal}}",
16+
"user": "{{user}}"
17+
}
18+
}
19+
20+
vars:pre-request {
21+
roomId: test123
22+
user: TestUser
23+
goal: This is an awesome goal!
24+
}

src/main/java/sh/mob/timer/web/Room.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,43 @@ final class Room {
2121
public static final TimerRequest NULL_TIMER_REQUEST =
2222
new TimerRequest(0L, null, null, null, null);
2323
private final String name;
24+
2425
private final List<TimerRequest> timerRequests = new CopyOnWriteArrayList<>();
25-
private final Sinks.Many<TimerRequest> sink =
26+
private final Sinks.Many<TimerRequest> timerRequestSink =
2627
Sinks.many().replay().latestOrDefault(NULL_TIMER_REQUEST);
2728

29+
private Goal currentGoal = Goal.NO_GOAL;
30+
private final Sinks.Many<Goal> goalRequestSink =
31+
Sinks.many().replay().latestOrDefault(Goal.NO_GOAL);
32+
2833
Room(String name) {
2934
this.name = name;
3035
}
3136

32-
public void add(Long timer, String user, Instant requested) {
37+
public void addTimer(Long timer, String user, Instant requested) {
3338
var nextUser = findNextUser(user);
3439
var timerRequest = new TimerRequest(timer, requested, user, nextUser, TimerType.TIMER);
3540
timerRequests.add(timerRequest);
36-
sink.tryEmitNext(timerRequest);
41+
timerRequestSink.tryEmitNext(timerRequest);
42+
}
43+
44+
public void setGoal(String text, String user, Instant requested) {
45+
var newGoal = new Goal(text, user, requested);
46+
currentGoal = newGoal ;
47+
goalRequestSink.tryEmitNext(newGoal);
48+
}
49+
50+
public void deleteGoal(String user, Instant requested) {
51+
if(currentGoal.goal() != null){
52+
currentGoal = Goal.deleted(user, requested);
53+
goalRequestSink.tryEmitNext(currentGoal);
54+
log.info(
55+
"Delete current goal by user {} for room {}",
56+
user,
57+
name);
58+
} else {
59+
log.info("Try to delete current goal by user {} for room {}, but there is no current goal.", user, name);
60+
}
3761
}
3862

3963
private String findNextUser(String user) {
@@ -70,11 +94,15 @@ public void addBreaktimer(Long breaktimer, String user) {
7094
lastTimerRequest().map(TimerRequest::getNextUser).orElse(null),
7195
TimerType.BREAKTIMER);
7296
timerRequests.add(timerRequest);
73-
sink.tryEmitNext(timerRequest);
97+
timerRequestSink.tryEmitNext(timerRequest);
98+
}
99+
100+
public Sinks.Many<TimerRequest> timerRequestSink() {
101+
return timerRequestSink;
74102
}
75103

76-
public Sinks.Many<TimerRequest> sink() {
77-
return sink;
104+
public Sinks.Many<Goal> goalRequestSink() {
105+
return goalRequestSink;
78106
}
79107

80108
Optional<TimerRequest> lastTimerRequest() {
@@ -89,7 +117,7 @@ public void removeOldTimerRequests() {
89117
this.timerRequests.removeIf(
90118
timerRequest -> now.minus(24, HOURS).isAfter(timerRequest.getRequested()));
91119
if (timerRequests.isEmpty()) {
92-
sink.tryEmitNext(NULL_TIMER_REQUEST);
120+
timerRequestSink.tryEmitNext(NULL_TIMER_REQUEST);
93121
log.info("Emptied room {}", name);
94122
}
95123
}
@@ -98,6 +126,14 @@ public String name() {
98126
return name;
99127
}
100128

129+
public Goal currentGoal() {
130+
return currentGoal;
131+
}
132+
133+
public boolean hasGoal() {
134+
return currentGoal.goal != null;
135+
}
136+
101137
public List<TimerRequest> historyWithoutLatest() {
102138
if (timerRequests.isEmpty()) {
103139
return List.of();
@@ -117,6 +153,14 @@ private static boolean isTimerActive(TimerRequest timerRequest, Instant now) {
117153
&& timerRequest.getRequested().plus(timerRequest.getTimer(), MINUTES).isAfter(now);
118154
}
119155

156+
public record Goal(String goal, String user, Instant requested){
157+
public static final Goal NO_GOAL = new Goal(null, null, null);
158+
159+
public static Goal deleted(String user, Instant requested){
160+
return new Goal(null, user, requested);
161+
}
162+
}
163+
120164
public static final class TimerRequest {
121165

122166
enum TimerType {

src/main/java/sh/mob/timer/web/RoomApiController.java

Lines changed: 53 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,17 @@
88
import org.slf4j.LoggerFactory;
99
import org.springframework.http.HttpStatus;
1010
import org.springframework.http.MediaType;
11+
import org.springframework.http.ResponseEntity;
1112
import org.springframework.http.codec.ServerSentEvent;
1213
import org.springframework.http.server.reactive.ServerHttpResponse;
13-
import org.springframework.web.bind.annotation.GetMapping;
14-
import org.springframework.web.bind.annotation.PathVariable;
15-
import org.springframework.web.bind.annotation.PutMapping;
16-
import org.springframework.web.bind.annotation.RequestBody;
17-
import org.springframework.web.bind.annotation.RequestMapping;
18-
import org.springframework.web.bind.annotation.ResponseStatus;
19-
import org.springframework.web.bind.annotation.RestController;
14+
import org.springframework.web.bind.annotation.*;
2015
import reactor.core.publisher.Flux;
2116
import sh.mob.timer.web.Room.TimerRequest;
2217

2318
@RestController
2419
@RequestMapping()
2520
public class RoomApiController {
2621

27-
private static final String SMOKETEST_ROOM_NAME = "testroom-310a9c47-515c-4ad7-a229-ae8efbab7387";
2822
private static final Logger log = LoggerFactory.getLogger(RoomApiController.class);
2923
private final RoomRepository roomRepository;
3024
private final Clock clock;
@@ -50,11 +44,17 @@ public Flux<ServerSentEvent<Object>> getEventStream(
5044
var room = roomRepository.get(roomId);
5145

5246
var timerRequestFlux =
53-
room.sink()
47+
room.timerRequestSink()
5448
.asFlux()
5549
.map(
5650
timerRequest ->
5751
ServerSentEvent.builder().event("TIMER_REQUEST").data(timerRequest).build());
52+
var goalRequestFlux =
53+
room.goalRequestSink()
54+
.asFlux()
55+
.map(
56+
goalRequest ->
57+
ServerSentEvent.builder().event("GOAL_REQUEST").data(goalRequest).build());
5858
var keepAliveFlux =
5959
Flux.interval(Duration.ofSeconds(5L))
6060
.map(
@@ -67,7 +67,7 @@ public Flux<ServerSentEvent<Object>> getEventStream(
6767
Flux.just(room.historyWithoutLatest())
6868
.map(list -> ServerSentEvent.builder().event("INITIAL_HISTORY").data(list).build());
6969

70-
return Flux.concat(initialHistory, keepAliveFlux.mergeWith(timerRequestFlux));
70+
return Flux.concat(initialHistory, keepAliveFlux.mergeWith(timerRequestFlux).mergeWith(goalRequestFlux));
7171
}
7272

7373
@PutMapping("/{roomId:[A-Za-z0-9-_]+}")
@@ -76,14 +76,14 @@ public void publishEvent(@PathVariable String roomId, @RequestBody PutTimerReque
7676
var room = roomRepository.get(roomId);
7777
if (timerRequest.timer() != null) {
7878
long timer = truncateTooLongTimers(timerRequest.timer());
79-
room.add(
79+
room.addTimer(
8080
timer, timerRequest.user(), Instant.now(clock));
8181
log.info(
8282
"Add timer {} by user {} for room {}",
8383
timerRequest.timer,
8484
timerRequest.user,
8585
room.name());
86-
incrementTimerStatsExceptForTestRoom(room, timer);
86+
stats.incrementTimer(room.name(), timer);
8787
} else if (timerRequest.breaktimer() != null) {
8888
long breaktimer = truncateTooLongTimers(timerRequest.breaktimer());
8989
room.addBreaktimer(breaktimer, timerRequest.user());
@@ -92,91 +92,63 @@ public void publishEvent(@PathVariable String roomId, @RequestBody PutTimerReque
9292
timerRequest.breaktimer(),
9393
timerRequest.user,
9494
room.name());
95-
incrementBreakTimerStatsExceptForTestRoom(room, breaktimer);
95+
stats.incrementBreaktimer(room.name(), breaktimer);
9696
} else {
9797
log.warn("Could not understand PUT request for room {}", roomId);
9898
}
9999
}
100100

101-
private void incrementBreakTimerStatsExceptForTestRoom(Room room, long breaktimer) {
102-
if (!Objects.equals(room.name(), SMOKETEST_ROOM_NAME)) {
103-
stats.incrementBreaktimer(breaktimer);
101+
@PutMapping("/{roomId:[A-Za-z0-9-_]+}/goal")
102+
@ResponseStatus(HttpStatus.ACCEPTED)
103+
public void putGoal(@PathVariable String roomId, @RequestBody PutGoalRequest goalRequest) {
104+
var room = roomRepository.get(roomId);
105+
if (goalRequest.goal() != null) {
106+
String goal = truncateTooLongGoal(goalRequest.goal());
107+
room.setGoal(
108+
goal, goalRequest.user(), Instant.now(clock));
109+
log.info(
110+
"Add goal \"{}\" by user {} for room {}",
111+
goalRequest.goal(),
112+
goalRequest.user(),
113+
room.name());
114+
stats.incrementGoalCount(room.name());
115+
} else {
116+
log.warn("Could not understand PUT goal request for room {}", roomId);
104117
}
105118
}
106119

107-
private void incrementTimerStatsExceptForTestRoom(Room room, long timer) {
108-
if (!Objects.equals(room.name(), SMOKETEST_ROOM_NAME)) {
109-
stats.incrementTimer(timer);
120+
@DeleteMapping("/{roomId:[A-Za-z0-9-_]+}/goal")
121+
@ResponseStatus(HttpStatus.ACCEPTED)
122+
public void deleteGoal(@PathVariable String roomId, @RequestBody DeleteGoalRequest deleteGoalRequest) {
123+
var room = roomRepository.get(roomId);
124+
room.deleteGoal(deleteGoalRequest.user(), Instant.now(clock));
125+
}
126+
127+
@GetMapping("/{roomId:[A-Za-z0-9-_]+}/goal")
128+
@ResponseStatus(HttpStatus.NO_CONTENT)
129+
public ResponseEntity<GoalResponse> getGoal(@PathVariable String roomId) {
130+
var room = roomRepository.get(roomId);
131+
if (!room.hasGoal()){
132+
return ResponseEntity.noContent().build();
110133
}
134+
return ResponseEntity.ofNullable(GoalResponse.of(room.currentGoal()));
111135
}
112136

113137
private static long truncateTooLongTimers(Long timer) {
114138
return Math.min(60 * 24, Math.max(0, timer));
115139
}
116140

117-
static final class PutTimerRequest {
118-
119-
private final Long timer;
120-
private final Long breaktimer;
121-
private final String user;
122-
123-
PutTimerRequest(Long timer, Long breaktimer, String user) {
124-
this.timer = timer;
125-
this.user = user;
126-
this.breaktimer = breaktimer;
127-
}
128-
129-
public Long timer() {
130-
return timer;
131-
}
132-
133-
public Long breaktimer() {
134-
return breaktimer;
135-
}
136-
137-
public String user() {
138-
return user;
139-
}
140-
141-
public Long getTimer() {
142-
return timer;
143-
}
144-
145-
public Long getBreaktimer() {
146-
return breaktimer;
147-
}
148-
149-
public String getUser() {
150-
return user;
151-
}
152-
153-
@Override
154-
public boolean equals(Object obj) {
155-
if (obj == this) return true;
156-
if (obj == null || obj.getClass() != this.getClass()) return false;
157-
var that = (PutTimerRequest) obj;
158-
return Objects.equals(this.timer, that.timer)
159-
&& Objects.equals(this.breaktimer, that.breaktimer)
160-
&& Objects.equals(this.user, that.user);
161-
}
162-
163-
@Override
164-
public int hashCode() {
165-
return Objects.hash(timer, breaktimer, user);
166-
}
141+
private static String truncateTooLongGoal(String goal) {
142+
return goal.length() > 256 ? goal.substring(0,256-1-3) + "...": goal;
143+
}
167144

168-
@Override
169-
public String toString() {
170-
return "PutTimerRequest["
171-
+ "timer="
172-
+ timer
173-
+ ", "
174-
+ "breaktimer="
175-
+ breaktimer
176-
+ ", "
177-
+ "user="
178-
+ user
179-
+ ']';
145+
public record GoalResponse(String goal){
146+
public static GoalResponse of(Room.Goal goal){
147+
return new GoalResponse(goal.goal());
180148
}
181149
}
150+
151+
public record PutGoalRequest(String goal, String user){}
152+
public record DeleteGoalRequest(String user){}
153+
public record PutTimerRequest(Long timer, Long breaktimer, String user){}
182154
}

src/main/java/sh/mob/timer/web/RoomRepository.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ Room get(String room) {
3131
});
3232
}
3333

34+
void deleteAll(){
35+
repository.clear();
36+
}
37+
3438
@Scheduled(fixedRateString = "PT1M")
3539
void cleanUpUnusedRooms() {
3640
repository.forEach((key, room) -> room.removeOldTimerRequests());
@@ -51,7 +55,7 @@ public long count() {
5155

5256
public long countConnections() {
5357
return repository.values().stream()
54-
.mapToLong(room -> room.sink().currentSubscriberCount())
58+
.mapToLong(room -> room.timerRequestSink().currentSubscriberCount())
5559
.sum();
5660
}
5761

0 commit comments

Comments
 (0)