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 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 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 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 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 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 updateMatchScore(WebSocketConnection connection, MatchScore score) { return getById(score.matchId(), connection) .chain(matchModel -> { int old_win = matchModel.win(); Optional 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 updateMatchEnd(WebSocketConnection connection, MatchEnd matchEnd) { List 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> updateEndAndTree(MatchModel mm, List 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 node = treeModels.stream().flatMap(t -> t.flat().stream()).toList(); List 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 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 recalculateMatch(WebSocketConnection connection, RecalculateMatch data) { ArrayList 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 -> matchRepository.delete("id IN ?1 AND category = ?2", data.matchesToRemove, cm) .call(__ -> SSMatch.sendDeleteMatch(connection, data.matchesToRemove))) .call(cm -> 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> uni = Uni.createFrom().item(new ArrayList<>()); for (AddMatch match : data.newMatch) uni = uni.call(l -> creatMatch(categoryModel, match).invoke(l::add)); return uni; } ) .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 newMatch, HashMap matchOrderToUpdate, HashMap matchPouleToUpdate, List matchesToRemove) { } }