349 lines
18 KiB
Java

package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
import fr.titionfire.ffsaf.domain.entity.TreeEntity;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import fr.titionfire.ffsaf.ws.PermLevel;
import fr.titionfire.ffsaf.ws.send.SSMatch;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.panache.common.Sort;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;
import java.util.*;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
@RegisterForReflection
public class RMatch {
private static final Logger LOGGER = Logger.getLogger(RMatch.class);
@Inject
MatchRepository matchRepository;
@Inject
CombRepository combRepository;
@Inject
CategoryRepository categoryRepository;
@Inject
TreeRepository treeRepository;
@Inject
CompetitionGuestRepository competitionGuestRepository;
private Uni<MatchModel> getById(long id, WebSocketConnection connection) {
return matchRepository.findById(id)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Matche non trouver");
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}));
}
private Uni<MatchModel> creatMatch(CategoryModel categoryModel, AddMatch m) {
return Uni.createFrom().item(() -> {
MatchModel matchModel = new MatchModel();
matchModel.setCategory(categoryModel);
matchModel.setCategory_ord(m.categorie_ord);
matchModel.setEnd(false);
matchModel.setPoule(m.poule);
return matchModel;
})
.call(mm -> m.c1() >= 0 ?
combRepository.findById(m.c1()).invoke(mm::setC1_id) :
competitionGuestRepository.findById(m.c1() * -1).invoke(mm::setC1_guest))
.call(mm -> m.c2() >= 0 ?
combRepository.findById(m.c2()).invoke(mm::setC2_id) :
competitionGuestRepository.findById(m.c2() * -1).invoke(mm::setC2_guest));
}
@WSReceiver(code = "addMatch", permission = PermLevel.ADMIN)
public Uni<Void> addMatch(WebSocketConnection connection, AddMatch m) {
return categoryRepository.findById(m.categorie)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}))
.chain(categoryModel -> creatMatch(categoryModel, m))
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm)))
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchComb", permission = PermLevel.ADMIN)
public Uni<Void> updateMatchComb(WebSocketConnection connection, MatchComb match) {
return getById(match.id(), connection)
.call(mm -> match.c1() != null ?
match.c1() >= 0 ?
combRepository.findById(match.c1()).invoke(model -> {
mm.setC1_id(model);
mm.setC1_guest(null);
}) :
competitionGuestRepository.findById(match.c1() * -1).invoke(model -> {
mm.setC1_id(null);
mm.setC1_guest(model);
}) :
Uni.createFrom().nullItem().invoke(__ -> {
mm.setC1_id(null);
mm.setC1_guest(null);
}))
.call(mm -> match.c2() != null ?
match.c2() >= 0 ?
combRepository.findById(match.c2()).invoke(model -> {
mm.setC2_id(model);
mm.setC2_guest(null);
}) :
competitionGuestRepository.findById(match.c2() * -1).invoke(model -> {
mm.setC2_id(null);
mm.setC2_guest(model);
}) :
Uni.createFrom().nullItem().invoke(__ -> {
mm.setC2_id(null);
mm.setC2_guest(null);
}))
.chain(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchOrder", permission = PermLevel.ADMIN)
public Uni<Void> updateMatchComb(WebSocketConnection connection, MatchOrder order) {
return getById(order.id(), connection)
.call(m -> matchRepository.update(
"category_ord = category_ord + 1 WHERE category_ord >= ?1 AND category_ord < ?2", order.pos,
m.getCategory_ord()))
.call(m -> matchRepository.update(
"category_ord = category_ord - 1 WHERE category_ord <= ?1 AND category_ord > ?2", order.pos,
m.getCategory_ord()))
.invoke(m -> m.setCategory_ord(order.pos))
.call(m -> Panache.withTransaction(() -> matchRepository.persist(m)))
.call(mm -> SSMatch.sendMatchOrder(connection, order))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchScore", permission = PermLevel.TABLE)
public Uni<Void> updateMatchScore(WebSocketConnection connection, MatchScore score) {
return getById(score.matchId(), connection)
.chain(matchModel -> {
int old_win = matchModel.win();
Optional<ScoreEmbeddable> optional = matchModel.getScores().stream()
.filter(s -> s.getN_round() == score.n_round()).findAny();
boolean b = score.s1() != -1000 || score.s2() != -1000;
if (optional.isPresent()) {
if (b) {
optional.get().setS1(score.s1());
optional.get().setS2(score.s2());
} else {
matchModel.getScores().remove(optional.get());
}
} else if (b) {
matchModel.getScores().add(new ScoreEmbeddable(score.n_round(), score.s1(), score.s2()));
}
return Panache.withTransaction(() -> matchRepository.persist(matchModel))
.call(mm -> {
if (mm.isEnd() && mm.win() != old_win && mm.getCategory_ord() == -42) {
return updateEndAndTree(mm, new ArrayList<>())
.call(l -> SSMatch.sendMatch(connection, l));
}
return Uni.createFrom().nullItem();
});
})
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchEnd", permission = PermLevel.TABLE)
public Uni<Void> updateMatchEnd(WebSocketConnection connection, MatchEnd matchEnd) {
List<MatchEntity> toSend = new ArrayList<>();
return getById(matchEnd.matchId(), connection)
.chain(mm -> {
if (mm.getCategory_ord() == -42 && mm.win() == 0) { // Tournois
mm.setDate(null);
mm.setEnd(false);
} else {
mm.setDate((matchEnd.end) ? new Date() : null);
mm.setEnd(matchEnd.end);
}
return Panache.withTransaction(() -> matchRepository.persist(mm));
})
.invoke(mm -> toSend.add(MatchEntity.fromModel(mm)))
.chain(mm -> updateEndAndTree(mm, toSend))
.call(__ -> SSMatch.sendMatch(connection, toSend))
.replaceWithVoid();
}
private Uni<List<MatchEntity>> updateEndAndTree(MatchModel mm, List<MatchEntity> toSend) {
return (mm.getCategory_ord() != -42) ?
Uni.createFrom().item(toSend) :
treeRepository.list("category = ?1 AND level != 0", Sort.ascending("level"), mm.getCategory().getId())
.chain(treeModels -> {
List<TreeModel> node = treeModels.stream().flatMap(t -> t.flat().stream()).toList();
List<TreeEntity> trees = treeModels.stream().map(TreeEntity::fromModel).toList();
for (int i = 0; i < trees.size() - 1; i++) {
TreeEntity.setAssociated(trees.get(i), trees.get(i + 1));
}
TreeEntity root = trees.stream()
.filter(t -> t.getMatchNode(mm.getId()) != null)
.findFirst()
.orElseThrow();
TreeEntity currentNode = root.getMatchNode(mm.getId());
if (currentNode == null) {
LOGGER.error(
"currentNode is empty for " + mm.getId() + " in " + mm.getCategory().getId());
return Uni.createFrom().voidItem();
}
TreeEntity parent = TreeEntity.getParent(root, currentNode);
if (parent == null) {
LOGGER.error("parent is empty for " + mm.getId() + " in " + mm.getCategory().getId());
return Uni.createFrom().voidItem();
}
int w = mm.win();
MembreModel toSetWin = null;
MembreModel toSetLose = null;
CompetitionGuestModel toSetWinGuest = null;
CompetitionGuestModel toSetLoseGuest = null;
if (mm.isEnd() && w != 0) {
toSetWin = (w > 0) ? mm.getC1_id() : mm.getC2_id();
toSetLose = (w > 0) ? mm.getC2_id() : mm.getC1_id();
toSetWinGuest = (w > 0) ? mm.getC1_guest() : mm.getC2_guest();
toSetLoseGuest = (w > 0) ? mm.getC2_guest() : mm.getC1_guest();
}
MatchModel modelWin = node.stream()
.filter(n -> Objects.equals(n.getId(), parent.getId()))
.findAny()
.map(TreeModel::getMatch)
.orElseThrow();
MatchModel modelLose = (parent.getAssociatedNode() == null) ? null : node.stream()
.filter(n -> Objects.equals(n.getId(), parent.getAssociatedNode().getId()))
.findAny()
.map(TreeModel::getMatch)
.orElseThrow();
if (currentNode.equals(parent.getLeft())) {
modelWin.setC1_id(toSetWin);
modelWin.setC1_guest(toSetWinGuest);
if (modelLose != null) {
modelLose.setC1_id(toSetLose);
modelLose.setC1_guest(toSetLoseGuest);
}
} else if (currentNode.equals(parent.getRight())) {
modelWin.setC2_id(toSetWin);
modelWin.setC2_guest(toSetWinGuest);
if (modelLose != null) {
modelLose.setC2_id(toSetLose);
modelLose.setC2_guest(toSetLoseGuest);
}
}
return Panache.withTransaction(() -> matchRepository.persist(modelWin)
.invoke(mm2 -> toSend.add(MatchEntity.fromModel(mm2)))
.call(__ -> modelLose == null ? Uni.createFrom().nullItem() :
matchRepository.persist(modelLose)
.invoke(mm2 -> toSend.add(MatchEntity.fromModel(mm2)))
)
.replaceWithVoid());
})
.map(__ -> toSend);
}
@WSReceiver(code = "deleteMatch", permission = PermLevel.ADMIN)
public Uni<Void> deleteMatch(WebSocketConnection connection, Long idMatch) {
return getById(idMatch, connection)
.chain(matchModel -> Panache.withTransaction(() -> matchRepository.delete(matchModel)))
.call(__ -> SSMatch.sendDeleteMatch(connection, idMatch))
.replaceWithVoid();
}
@WSReceiver(code = "recalculateMatch", permission = PermLevel.ADMIN)
public Uni<Void> recalculateMatch(WebSocketConnection connection, RecalculateMatch data) {
ArrayList<MatchEntity> matches = new ArrayList<>();
return categoryRepository.findById(data.categorie)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}))
.call(cm -> Panache.withTransaction(
() -> matchRepository.delete("id IN ?1 AND category = ?2", data.matchesToRemove, cm))
.call(__ -> SSMatch.sendDeleteMatch(connection, data.matchesToRemove)))
.call(cm -> Panache.withSession(() -> matchRepository.list("id IN ?1 AND category = ?2",
Stream.concat(data.matchOrderToUpdate.keySet().stream(),
data.matchPouleToUpdate.keySet().stream())
.distinct().toList(), cm)
.invoke(matchModels -> matchModels.forEach(model -> {
if (data.matchPouleToUpdate.containsKey(model.getId()))
model.setPoule(data.matchPouleToUpdate.get(model.getId()));
if (data.matchOrderToUpdate.containsKey(model.getId()))
model.setCategory_ord(data.matchOrderToUpdate.get(model.getId()));
}))
.call(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
.invoke(mm -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList())))
)
.chain(categoryModel -> {
Uni<List<MatchModel>> uni = Uni.createFrom().item(new ArrayList<>());
for (AddMatch match : data.newMatch)
uni = uni.call(l -> creatMatch(categoryModel, match).invoke(l::add));
Uni<List<MatchModel>> finalUni = uni;
return Panache.withSession(() -> finalUni);
}
)
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm))
.invoke(__ -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList())))
.call(__ -> SSMatch.sendMatch(connection, matches))
.replaceWithVoid();
}
@RegisterForReflection
public record MatchComb(long id, Long c1, Long c2) {
}
@RegisterForReflection
public record MatchScore(long matchId, int n_round, int s1, int s2) {
}
@RegisterForReflection
public record MatchEnd(long matchId, boolean end) {
}
@RegisterForReflection
public record MatchOrder(long id, long pos) {
}
@RegisterForReflection
public record AddMatch(long categorie, long categorie_ord, char poule, long c1, long c2) {
}
public record RecalculateMatch(long categorie, List<AddMatch> newMatch, HashMap<Long, Integer> matchOrderToUpdate,
HashMap<Long, Character> matchPouleToUpdate, List<Long> matchesToRemove) {
}
}