Commit 6f62faa9 authored by Klaus-Dieter Quibeldey-Cirkel's avatar Klaus-Dieter Quibeldey-Cirkel
Browse files

Merge branch 'staging' into 'master'

Staging

See merge request !159
parents 15e6930b 091d3974
Pipeline #92508 passed with stages
in 1 minute and 58 seconds
......@@ -10,11 +10,11 @@ services:
args:
- USER=${DEV_USER}
environment:
- SPRING_RABBITMQ_HOST=fragjetzt-rabbitmq
- SPRING_R2DBC_HOST=fragjetzt-postgres
- APP_MAIL_HOST=fragjetzt-mailhog
- APP_MAIL_SENDER_ADDRESS=postmaster@localhost
- SERVER_ROOT_URL=http://localhost:4200
SPRING_RABBITMQ_HOST: 'fragjetzt-rabbitmq'
SPRING_R2DBC_HOST: 'fragjetzt-postgres'
APP_MAIL_HOST: 'fragjetzt-mailhog'
APP_MAIL_SENDER_ADDRESS: 'postmaster@localhost'
SERVER_ROOT_URL: 'http://localhost:4200'
volumes:
- 'cache:/cache'
- './:/app'
......
package de.thm.arsnova.frag.jetzt.backend;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingCategory;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingSession;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingWord;
import de.thm.arsnova.frag.jetzt.backend.model.event.*;
import de.thm.arsnova.frag.jetzt.backend.service.persistence.BrainstormingWordRepository;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.UUID;
......@@ -34,9 +37,34 @@ public class RoomEventSource {
send(new BrainstormingCreated(payload, session.getRoomId()));
}
public void sessionClosed(BrainstormingSession session) {
BrainstormingClosedPayload payload = new BrainstormingClosedPayload(session);
send(new BrainstormingClosed(payload, session.getRoomId()));
public void sessionWordCreated(BrainstormingWord word, UUID roomId) {
BrainstormingWordCreatedPayload payload = new BrainstormingWordCreatedPayload(word);
send(new BrainstormingWordCreated(payload, roomId));
}
public void sessionVotesReset(UUID sessionId, UUID roomId) {
BrainstormingVotesResetPayload payload = new BrainstormingVotesResetPayload(sessionId);
send(new BrainstormingVotesReset(payload, roomId));
}
public void sessionCategorizationReset(UUID sessionId, UUID roomId) {
BrainstormingCategorizationResetPayload payload = new BrainstormingCategorizationResetPayload(sessionId);
send(new BrainstormingCategorizationReset(payload, roomId));
}
public void sessionWordPatched(UUID wordId, UUID roomId, Map<String, Object> changes) {
BrainstormingWordPatchedPayload payload = new BrainstormingWordPatchedPayload(wordId, changes);
send(new BrainstormingWordPatched(payload, roomId));
}
public void sessionCategoriesUpdated(UUID roomId, List<BrainstormingCategory> categoryList) {
BrainstormingCategoriesUpdatedPayload payload = new BrainstormingCategoriesUpdatedPayload(categoryList);
send(new BrainstormingCategoriesUpdated(payload, roomId));
}
public void sessionPatched(UUID sessionId, UUID roomId, Map<String, Object> changes) {
BrainstormingPatchedPayload payload = new BrainstormingPatchedPayload(sessionId, changes);
send(new BrainstormingPatched(payload, roomId));
}
public void sessionWordVoted(UUID wordId, UUID roomId) {
......
package de.thm.arsnova.frag.jetzt.backend.controller;
import de.thm.arsnova.frag.jetzt.backend.handler.BrainstormingCommandHandler;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingCategory;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingSession;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingVote;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingWord;
import de.thm.arsnova.frag.jetzt.backend.model.command.*;
import de.thm.arsnova.frag.jetzt.backend.service.BrainstormingSessionFindQueryService;
import de.thm.arsnova.frag.jetzt.backend.service.BrainstormingSessionService;
......@@ -15,6 +17,8 @@ import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController("BrainstormingController")
......@@ -23,19 +27,27 @@ public class BrainstormingController extends AbstractEntityController {
private static final Logger logger = LoggerFactory.getLogger(BrainstormingController.class);
protected static final String REQUEST_MAPPING = "/brainstorming";
private static final String CLOSE_MAPPING = "/close";
private static final String WORD_ID_MAPPING = "/{wordId}";
private static final String UPVOTE_MAPPING = "/upvote";
private static final String DOWNVOTE_MAPPING = "/downvote";
private static final String VOTE_WORD_MAPPING = "/vote/{word}";
private static final String RESET_VOTE_MAPPING = "/reset-vote";
private static final String RESET_RATING_MAPPING = "/reset-rating";
private static final String RESET_CATEGORIZATION_MAPPING = "/reset-categorization";
private static final String CATEGORY_MAPPING = "/category/{roomId}";
private static final String WORD_MAPPING = "/word/{word}";
private static final String PATCH_WORD_MAPPING = "/patch-word/{wordId}";
private final BrainstormingCommandHandler commandHandler;
private final BrainstormingSessionService service;
private final BrainstormingSessionFindQueryService findQueryService;
@Autowired
public BrainstormingController(BrainstormingCommandHandler commandHandler,
BrainstormingSessionService service,
BrainstormingSessionFindQueryService findQueryService) {
public BrainstormingController(
BrainstormingCommandHandler commandHandler,
BrainstormingSessionService service,
BrainstormingSessionFindQueryService findQueryService
) {
this.commandHandler = commandHandler;
this.service = service;
this.findQueryService = findQueryService;
......@@ -47,10 +59,22 @@ public class BrainstormingController extends AbstractEntityController {
.switchIfEmpty(Mono.error(NotFoundException::new));
}
@PostMapping(GET_MAPPING + CLOSE_MAPPING)
public Mono<BrainstormingSession> close(@PathVariable UUID id) {
CloseBrainstormingPayload payload = new CloseBrainstormingPayload(id);
return this.commandHandler.handle(new CloseBrainstorming(payload));
@PatchMapping(PATCH_MAPPING)
public Mono<BrainstormingSession> patch(@PathVariable UUID id, @RequestBody final Map<String, Object> changes) {
PatchBrainstormingPayload payload = new PatchBrainstormingPayload(id, changes);
return this.commandHandler.handle(new PatchBrainstorming(payload));
}
@PostMapping(GET_MAPPING + WORD_MAPPING + POST_MAPPING)
public Mono<BrainstormingWord> post(@PathVariable UUID id, @PathVariable final String word) {
CreateBrainstormingWordPayload payload = new CreateBrainstormingWordPayload(word, id);
return this.commandHandler.handle(new CreateBrainstormingWord(payload));
}
@PatchMapping(PATCH_WORD_MAPPING)
public Mono<BrainstormingWord> patchWord(@PathVariable UUID wordId, @RequestBody Map<String, Object> changes){
PatchBrainstormingWordPayload payload = new PatchBrainstormingWordPayload(wordId, changes);
return this.commandHandler.handle(new PatchBrainstormingWord(payload));
}
@PostMapping(POST_MAPPING)
......@@ -65,24 +89,47 @@ public class BrainstormingController extends AbstractEntityController {
return this.commandHandler.handle(new DeleteBrainstorming(payload));
}
@PostMapping(GET_MAPPING + UPVOTE_MAPPING)
public Mono<BrainstormingVote> upvote(@PathVariable UUID id, @RequestBody String word) {
CreateBrainstormingVotePayload payload = new CreateBrainstormingVotePayload(word, true, id);
@PostMapping(WORD_ID_MAPPING + UPVOTE_MAPPING)
public Mono<BrainstormingVote> upvote(@PathVariable UUID wordId) {
CreateBrainstormingVotePayload payload = new CreateBrainstormingVotePayload(wordId, true);
return this.commandHandler.handle(new CreateBrainstormingVote(payload));
}
@PostMapping(GET_MAPPING + DOWNVOTE_MAPPING)
public Mono<BrainstormingVote> downvote(@PathVariable UUID id, @RequestBody String word) {
CreateBrainstormingVotePayload payload = new CreateBrainstormingVotePayload(word, false, id);
@PostMapping(WORD_ID_MAPPING + DOWNVOTE_MAPPING)
public Mono<BrainstormingVote> downvote(@PathVariable UUID wordId) {
CreateBrainstormingVotePayload payload = new CreateBrainstormingVotePayload(wordId, false);
return this.commandHandler.handle(new CreateBrainstormingVote(payload));
}
@DeleteMapping(GET_MAPPING + VOTE_WORD_MAPPING)
public Mono<Void> deleteVote(@PathVariable UUID id, @PathVariable String word) {
DeleteBrainstormingVotePayload payload = new DeleteBrainstormingVotePayload(word, id);
@DeleteMapping(WORD_ID_MAPPING + RESET_VOTE_MAPPING)
public Mono<Void> deleteVote(@PathVariable UUID wordId) {
DeleteBrainstormingVotePayload payload = new DeleteBrainstormingVotePayload(wordId);
return commandHandler.handle(new DeleteBrainstormingVote(payload));
}
@GetMapping(CATEGORY_MAPPING)
public Flux<BrainstormingCategory> getCategories(@PathVariable UUID roomId) {
return this.service.getCategories(roomId);
}
@PostMapping(CATEGORY_MAPPING + POST_MAPPING)
public Flux<BrainstormingCategory> updateCategories(@PathVariable UUID roomId, @RequestBody List<String> categories) {
UpdateBrainstormingCategoriesPayload payload = new UpdateBrainstormingCategoriesPayload(roomId, categories);
return commandHandler.handle(new UpdateBrainstormingCategories(payload));
}
@PostMapping(GET_MAPPING + RESET_RATING_MAPPING)
public Mono<Void> deleteAllVotes(@PathVariable UUID id) {
ResetBrainstormingVotesPayload payload = new ResetBrainstormingVotesPayload(id);
return commandHandler.handle(new ResetBrainstormingVotes(payload));
}
@PostMapping(GET_MAPPING + RESET_CATEGORIZATION_MAPPING)
public Mono<Void> deleteAllCategories(@PathVariable UUID id) {
ResetBrainstormingCategorizationPayload payload = new ResetBrainstormingCategorizationPayload(id);
return commandHandler.handle(new ResetBrainstormingCategorization(payload));
}
@PostMapping(FIND_MAPPING)
public Flux<BrainstormingSession> find(@RequestBody final FindQuery<BrainstormingSession> findQuery) {
logger.debug("Resolving find query: {}", findQuery);
......
package de.thm.arsnova.frag.jetzt.backend.handler;
import de.thm.arsnova.frag.jetzt.backend.RoomEventSource;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingCategory;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingSession;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingVote;
import de.thm.arsnova.frag.jetzt.backend.model.BrainstormingWord;
......@@ -10,17 +11,15 @@ import de.thm.arsnova.frag.jetzt.backend.service.BrainstormingVoteService;
import de.thm.arsnova.frag.jetzt.backend.service.persistence.BrainstormingSessionRepository;
import de.thm.arsnova.frag.jetzt.backend.service.persistence.BrainstormingWordRepository;
import de.thm.arsnova.frag.jetzt.backend.web.exceptions.BadRequestException;
import de.thm.arsnova.frag.jetzt.backend.web.exceptions.ForbiddenException;
import de.thm.arsnova.frag.jetzt.backend.web.exceptions.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import reactor.util.function.Tuple2;
@Component
public class BrainstormingCommandHandler {
......@@ -53,6 +52,28 @@ public class BrainstormingCommandHandler {
this.eventer = eventer;
}
public Mono<Void> handle(ResetBrainstormingVotes command) {
logger.trace("got new command: " + command.toString());
ResetBrainstormingVotesPayload payload = command.getPayload();
return voteService.deleteAllBySessionId(payload.getSessionId())
.count()
.flatMap(count -> service.get(payload.getSessionId()))
.doOnSuccess(session -> eventer.sessionVotesReset(session.getId(), session.getRoomId()))
.then();
}
public Mono<Void> handle(ResetBrainstormingCategorization command) {
logger.trace("got new command: " + command.toString());
ResetBrainstormingCategorizationPayload payload = command.getPayload();
return wordRepository.resetBySessionId(payload.getSessionId())
.then(service.get(payload.getSessionId()))
.switchIfEmpty(Mono.error(NotFoundException::new))
.doOnSuccess(session -> eventer.sessionCategorizationReset(session.getId(), session.getRoomId()))
.then();
}
public Mono<BrainstormingSession> handle(CreateBrainstorming command) {
logger.trace("got new command: " + command.toString());
......@@ -60,19 +81,64 @@ public class BrainstormingCommandHandler {
BrainstormingSession session = new BrainstormingSession();
session.setRoomId(payload.getRoomId());
session.setTitle(payload.getTitle());
session.setActive(payload.isActive());
session.setMaxWordLength(payload.getMaxWordLength());
session.setMaxWordCount(payload.getMaxWordCount());
session.setLanguage(payload.getLanguage());
session.setRatingAllowed(payload.isRatingAllowed());
session.setIdeasFrozen(payload.isIdeasFrozen());
session.setIdeasTimeDuration(payload.getIdeasTimeDuration());
session.setIdeasEndTimestamp(payload.getIdeasEndTimestamp());
return this.service.create(session)
.doOnSuccess(eventer::sessionCreated);
}
public Mono<BrainstormingSession> handle(CloseBrainstorming command) {
public Mono<BrainstormingWord> handle(CreateBrainstormingWord command) {
logger.trace("got new command: " + command.toString());
CreateBrainstormingWordPayload payload = command.getPayload();
return this.verifyWord(payload.getName())
.flatMap(word -> this.service.createWord(payload.getSessionId(), word))
.flatMap(tuple2 -> Mono.zip(
Mono.just(tuple2.getT1()),
Mono.just(tuple2.getT2()),
repository.findById(tuple2.getT1().getSessionId())
))
.doOnSuccess(data -> {
if (!data.getT2()) {
this.eventer.sessionWordCreated(data.getT1(), data.getT3().getRoomId());
}
})
.map(Tuple2::getT1);
}
public Flux<BrainstormingCategory> handle(UpdateBrainstormingCategories command) {
logger.trace("got new command: " + command.toString());
CloseBrainstormingPayload payload = command.getPayload();
return this.service.close(payload.getId())
.doOnSuccess(eventer::sessionClosed);
UpdateBrainstormingCategoriesPayload payload = command.getPayload();
return this.service.updateCategories(payload.getRoomId(), payload.getCategories())
.collectList()
.doOnSuccess((list) -> this.eventer.sessionCategoriesUpdated(payload.getRoomId(), list))
.flatMapMany(Flux::fromIterable);
}
public Mono<BrainstormingSession> handle(PatchBrainstorming command) {
logger.trace("got new command: " + command.toString());
PatchBrainstormingPayload payload = command.getPayload();
return this.service.get(payload.getId())
.flatMap(session -> this.service.patch(session, payload.getChanges()))
.doOnSuccess(s -> this.eventer.sessionPatched(s.getId(), s.getRoomId(), payload.getChanges()));
}
public Mono<BrainstormingWord> handle(PatchBrainstormingWord command) {
logger.trace("got new command: " + command.toString());
PatchBrainstormingWordPayload payload = command.getPayload();
return this.wordRepository.findById(payload.getId())
.switchIfEmpty(Mono.error(NotFoundException::new))
.flatMap(word -> this.service.patchWord(word, payload.getChanges()))
.doOnSuccess(tuple2 -> this.eventer.sessionWordPatched(payload.getId(), tuple2.getT1(), payload.getChanges()))
.map(Tuple2::getT2);
}
public Mono<Void> handle(DeleteBrainstorming command) {
......@@ -87,49 +153,43 @@ public class BrainstormingCommandHandler {
public Mono<BrainstormingVote> handle(CreateBrainstormingVote command) {
logger.trace("got new command: " + command.toString());
AtomicReference<UUID> roomId = new AtomicReference<>();
AtomicReference<UUID> wordId = new AtomicReference<>();
CreateBrainstormingVotePayload payload = command.getPayload();
return this.verifyWord(payload.getWord())
.flatMap(w -> this.repository.findById(payload.getSessionId()))
return wordRepository.findById(payload.getWordId())
.switchIfEmpty(Mono.error(NotFoundException::new))
.flatMap(w -> Mono.zip(
Mono.just(w),
repository.findById(w.getSessionId())
))
.switchIfEmpty(Mono.error(new NotFoundException("Session does not exist!")))
.filter(BrainstormingSession::isActive)
.switchIfEmpty(Mono.error(new ForbiddenException("Session already closed")))
.flatMap(s -> {
roomId.set(s.getRoomId());
return this.wordRepository.findBySessionIdAndWord(payload.getSessionId(), payload.getWord());
})
.switchIfEmpty(this.wordRepository
.save(new BrainstormingWord(payload.getSessionId(), payload.getWord(), 0, 0)))
.flatMap(word -> {
wordId.set(word.getId());
return this.voteService
.create(new BrainstormingVote(null, word.getId(), payload.isUpvote()));
})
.doOnSuccess(s -> eventer.sessionWordVoted(wordId.get(), roomId.get()));
.flatMap(tuple2 -> Mono.zip(Mono.just(tuple2.getT1()), service.checkCanCreateRating(tuple2.getT2())))
.flatMap(tuple2 -> Mono.zip(
this.voteService
.create(new BrainstormingVote(null, tuple2.getT1().getId(), payload.isUpvote())),
Mono.just(tuple2.getT2().getRoomId())
))
.doOnSuccess(tuple2 -> eventer.sessionWordVoted(tuple2.getT1().getWordId(), tuple2.getT2()))
.map(Tuple2::getT1);
}
public Mono<Void> handle(DeleteBrainstormingVote command) {
logger.trace("got new command: " + command.toString());
AtomicReference<UUID> roomId = new AtomicReference<>();
AtomicReference<UUID> wordId = new AtomicReference<>();
DeleteBrainstormingVotePayload payload = command.getPayload();
return this.verifyWord(payload.getWord())
.flatMap(w -> this.repository.findById(payload.getSessionId()))
return wordRepository.findById(payload.getWordId())
.switchIfEmpty(Mono.error(NotFoundException::new))
.flatMap(w -> Mono.zip(
Mono.just(w),
repository.findById(w.getSessionId())
))
.switchIfEmpty(Mono.error(new NotFoundException("Session does not exist!")))
.filter(BrainstormingSession::isActive)
.switchIfEmpty(Mono.error(new ForbiddenException("Session already closed")))
.flatMap(s -> {
roomId.set(s.getRoomId());
return this.wordRepository.findBySessionIdAndWord(payload.getSessionId(), payload.getWord());
})
.switchIfEmpty(Mono.error(new NotFoundException("Word does not exist!")))
.flatMap(word -> {
wordId.set(word.getId());
return this.voteService.deleteByWordId(word.getId());
})
.doOnSuccess(s -> eventer.sessionWordVoted(wordId.get(), roomId.get()));
.flatMap(tuple2 -> Mono.zip(Mono.just(tuple2.getT1()), service.checkCanCreateRating(tuple2.getT2())))
.flatMap(tuple2 -> this.voteService.deleteByWordId(tuple2.getT1().getId())
.then(Mono.zip(
Mono.just(tuple2.getT1().getId()),
Mono.just(tuple2.getT2().getRoomId())
)))
.doOnSuccess(tuple2 -> eventer.sessionWordVoted(tuple2.getT1(), tuple2.getT2()))
.then();
}
private Mono<String> verifyWord(String word) {
......
......@@ -105,7 +105,8 @@ public class CommentCommandHandler {
newComment.setKeywordsFromQuestioner(payload.getKeywordsFromQuestioner());
newComment.setLanguage(payload.getLanguage());
newComment.setQuestionerName(payload.getQuestionerName());
newComment.setBrainstormingQuestion(payload.isBrainstormingQuestion());
newComment.setBrainstormingSessionId(payload.getBrainstormingSessionId());
newComment.setBrainstormingWordId(payload.getBrainstormingWordId());
newComment.setCommentReference(payload.getCommentReference());
return service.create(newComment)
......@@ -169,7 +170,6 @@ public class CommentCommandHandler {
old.setKeywordsFromSpacy(p.getKeywordsFromSpacy());
old.setKeywordsFromQuestioner(p.getKeywordsFromQuestioner());
old.setLanguage(p.getLanguage());
old.setBrainstormingQuestion(p.isBrainstormingQuestion());
return this.service.update(old)
.doOnSuccess(updated -> {
......
package de.thm.arsnova.frag.jetzt.backend.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Table;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Objects;
import java.util.UUID;
@Table
public class BrainstormingCategory implements Persistable<UUID> {
@Id
private UUID id;
private UUID roomId;
private String name;
private Timestamp createdAt = Timestamp.from(Instant.now());
private Timestamp updatedAt;
@Override
public boolean isNew() {
return id == null;
}
@Override
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getRoomId() {
return roomId;
}
public void setRoomId(UUID roomId) {
this.roomId = roomId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Timestamp getCreatedAt() {
return createdAt;
}
public Timestamp getUpdatedAt() {
return updatedAt;
}
@Override
public String toString() {
return "BrainstormingCategory{" +
"id=" + id +
", roomId=" + roomId +
", name='" + name + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BrainstormingCategory category = (BrainstormingCategory) o;
return Objects.equals(id, category.id) &&
Objects.equals(roomId, category.roomId) &&
Objects.equals(name, category.name) &&
Objects.equals(createdAt, category.createdAt) &&
Objects.equals(updatedAt, category.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, roomId, name, createdAt, updatedAt);
}
}
\ No newline at end of file
......@@ -14,35 +14,24 @@ import java.util.UUID;
@Table
public class BrainstormingSession implements Persistable<UUID> {
public static class BrainstormingSessionWordInfo {
private int upvotes;
private int downvotes;
public static class BrainstormingWordWithMeta {
private BrainstormingWord word;
private Boolean ownHasUpvoted;
public BrainstormingSessionWordInfo() {
public BrainstormingWordWithMeta() {
}
public BrainstormingSessionWordInfo(int upvotes, int downvotes, Boolean ownHasUpvoted) {
this.upvotes = upvotes;
this.downvotes = downvotes;
public BrainstormingWordWithMeta(BrainstormingWord word, Boolean ownHasUpvoted) {
this.word = word;
this.ownHasUpvoted = ownHasUpvoted;
}
public int getUpvotes() {
return upvotes;
}
public void setUpvotes(int upvotes) {
this.upvotes = upvotes;
public BrainstormingWord getWord() {
return word;