diff --git a/pom.xml b/pom.xml
index 52baa01..2c0622e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,6 +133,11 @@
io.quarkus
quarkus-mailer
+
+
+ io.quarkus
+ quarkus-websockets-next
+
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CardboardModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CardboardModel.java
new file mode 100644
index 0000000..255f83e
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/CardboardModel.java
@@ -0,0 +1,42 @@
+package fr.titionfire.ffsaf.data.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@RegisterForReflection
+
+@Entity
+@Table(name = "cardboard")
+public class CardboardModel {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "comb", referencedColumnName = "id")
+ MembreModel comb;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "guest_comb", referencedColumnName = "id")
+ CompetitionGuestModel guestComb;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "match", referencedColumnName = "id")
+ MatchModel match;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "compet", referencedColumnName = "id")
+ CompetitionModel compet;
+
+ int red;
+ int yellow;
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java
index d32dac0..5f381cf 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java
@@ -42,4 +42,6 @@ public class CategoryModel {
List tree;
Integer type;
+
+ String liceName = "1";
}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java
new file mode 100644
index 0000000..697de65
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java
@@ -0,0 +1,50 @@
+package fr.titionfire.ffsaf.data.model;
+
+import fr.titionfire.ffsaf.utils.Categorie;
+import fr.titionfire.ffsaf.utils.Genre;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@RegisterForReflection
+
+@Entity
+@Table(name = "competition_guest")
+public class CompetitionGuestModel {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "competition", referencedColumnName = "id")
+ CompetitionModel competition;
+
+ String lname = "";
+ String fname = "";
+
+ Categorie categorie = null;
+
+ String club = null;
+
+ Genre genre = null;
+
+ String country = "fr";
+
+ Integer weight = null;
+
+ public CompetitionGuestModel(String s) {
+ this.fname = s.substring(0, s.indexOf(" "));
+ this.lname = s.substring(s.indexOf(" ") + 1);
+ }
+
+ public String getName() {
+ return fname + " " + lname;
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java
index 77b4b94..8834b92 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java
@@ -55,10 +55,18 @@ public class CompetitionModel {
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
List insc;
+ @OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ List guests = new ArrayList<>();
+
+
List banMembre = new ArrayList<>();
String owner;
+ List admin = new ArrayList<>();
+ @Column(name = "table_")
+ List table = new ArrayList<>();
+
String data1;
String data2;
String data3;
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java
index a20694e..a6b9d73 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java
@@ -7,6 +7,7 @@ import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
@Getter
@@ -32,13 +33,17 @@ public class MatchModel {
@JoinColumn(name = "c1", referencedColumnName = "id")
MembreModel c1_id = null;
- String c1_str = null;
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "c1_guest", referencedColumnName = "id")
+ CompetitionGuestModel c1_guest = null;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "c2", referencedColumnName = "id")
MembreModel c2_id = null;
- String c2_str = null;
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "c2_guest", referencedColumnName = "id")
+ CompetitionGuestModel c2_guest = null;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "id_category", referencedColumnName = "id")
@@ -48,9 +53,45 @@ public class MatchModel {
boolean isEnd = true;
+ Date date = null;
+
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
List scores = new ArrayList<>();
char poule = 'A';
+
+ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @JoinColumn(name = "match", referencedColumnName = "id")
+ List cardboard = new ArrayList<>();
+
+ public String getC1Name() {
+ if (c1_id != null)
+ return c1_id.fname + " " + c1_id.lname;
+ if (c1_guest != null)
+ return c1_guest.fname + " " + c1_guest.lname;
+ return "";
+ }
+
+ public String getC2Name() {
+ if (c2_id != null)
+ return c2_id.fname + " " + c2_id.lname;
+ if (c2_guest != null)
+ return c2_guest.fname + " " + c2_guest.lname;
+ return "";
+ }
+
+ public int win() {
+ int sum = 0;
+ for (ScoreEmbeddable score : this.getScores()) {
+ if (score.getS1() == -1000 || score.getS2() == -1000)
+ continue;
+
+ if (score.getS1() > score.getS2())
+ sum++;
+ else if (score.getS1() < score.getS2())
+ sum--;
+ }
+ return sum;
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java
index ed567e2..02c0f90 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java
@@ -56,4 +56,14 @@ public class RegisterModel {
this.categorie = categorie;
this.club = club;
}
+
+ public String getName() {
+ return membre.fname + " " + membre.lname;
+ }
+
+ public ClubModel getClub2() {
+ if (club == null)
+ return membre.club;
+ return club;
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java
index 092b950..2f0e556 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java
@@ -7,6 +7,9 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
+import java.util.ArrayList;
+import java.util.List;
+
@Getter
@Setter
@AllArgsConstructor
@@ -36,4 +39,20 @@ public class TreeModel {
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(referencedColumnName = "id")
TreeModel right;
+
+ public List flat() {
+ List out = new ArrayList<>();
+ this.flat(out);
+ return out;
+ }
+
+ private void flat(List out) {
+ out.add(this);
+
+ if (this.right != null)
+ this.right.flat(out);
+
+ if (this.left != null)
+ this.left.flat(out);
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/CardboardRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/CardboardRepository.java
new file mode 100644
index 0000000..11fc320
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/data/repository/CardboardRepository.java
@@ -0,0 +1,9 @@
+package fr.titionfire.ffsaf.data.repository;
+
+import fr.titionfire.ffsaf.data.model.CardboardModel;
+import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class CardboardRepository implements PanacheRepositoryBase {
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java
index 8271b3b..eb3d5b9 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java
@@ -1,9 +1,19 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CategoryModel;
+import fr.titionfire.ffsaf.utils.CompetitionSystem;
+import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
+import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CategoryRepository implements PanacheRepositoryBase {
+
+ public Uni create(CategoryModel categoryModel) {
+ categoryModel.setSystem(CompetitionSystem.INTERNAL);
+ return Panache.withTransaction(() -> this.persist(categoryModel)
+ .invoke(categoryModel1 -> categoryModel1.setSystemId(categoryModel1.getId())))
+ .chain(this::persist);
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/CompetitionGuestRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/CompetitionGuestRepository.java
new file mode 100644
index 0000000..6e0a959
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/data/repository/CompetitionGuestRepository.java
@@ -0,0 +1,9 @@
+package fr.titionfire.ffsaf.data.repository;
+
+import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
+import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class CompetitionGuestRepository implements PanacheRepositoryBase {
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java
index ab284c4..46044b7 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java
@@ -1,9 +1,30 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.MatchModel;
+import fr.titionfire.ffsaf.utils.CompetitionSystem;
+import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
+import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
+import java.util.List;
+
@ApplicationScoped
public class MatchRepository implements PanacheRepositoryBase {
+
+ public Uni create(MatchModel matchModel) {
+ matchModel.setSystem(CompetitionSystem.INTERNAL);
+ return Panache.withTransaction(() -> this.persistAndFlush(matchModel)
+ .invoke(matchModel1 -> matchModel1.setSystemId(matchModel1.getId())))
+ .chain(this::persist);
+ }
+
+ public Uni create(List matchModel) {
+ matchModel.forEach(model -> model.setSystem(CompetitionSystem.INTERNAL));
+ return Panache.withTransaction(() -> this.persist(matchModel)
+ .call(__ -> this.flush())
+ .invoke(__ -> matchModel.forEach(model -> model.setSystemId(model.getId())))
+ .map(__ -> matchModel))
+ .chain(this::persist);
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java
index 57bb22b..e38aee0 100644
--- a/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java
+++ b/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java
@@ -2,8 +2,22 @@ package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.TreeModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
+import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
+import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class TreeRepository implements PanacheRepositoryBase {
+
+ @WithTransaction
+ public Uni deleteTree(TreeModel entity) {
+ Uni uni = Uni.createFrom().item(false);
+ if (entity == null)
+ return uni;
+ if (entity.getLeft() != null)
+ uni = uni.chain(__ -> this.deleteTree(entity.getLeft()));
+ if (entity.getRight() != null)
+ uni = uni.chain(__ -> this.deleteTree(entity.getRight()));
+ return uni.chain(__ -> this.deleteById(entity.getId()));
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/entity/CardboardEntity.java b/src/main/java/fr/titionfire/ffsaf/domain/entity/CardboardEntity.java
new file mode 100644
index 0000000..ca15a38
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/CardboardEntity.java
@@ -0,0 +1,26 @@
+package fr.titionfire.ffsaf.domain.entity;
+
+import fr.titionfire.ffsaf.data.model.CardboardModel;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+@RegisterForReflection
+public class CardboardEntity {
+ long comb_id;
+ long match_id;
+ long compet_id;
+
+ int red;
+ int yellow;
+
+ public static CardboardEntity fromModel(CardboardModel model) {
+ return new CardboardEntity(
+ model.getComb() != null ? model.getComb().getId() : model.getGuestComb().getId() * -1,
+ model.getMatch().getId(),
+ model.getCompet().getId(),
+ model.getRed(), model.getYellow());
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java
new file mode 100644
index 0000000..4126db2
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java
@@ -0,0 +1,56 @@
+package fr.titionfire.ffsaf.domain.entity;
+
+import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
+import fr.titionfire.ffsaf.data.model.MembreModel;
+import fr.titionfire.ffsaf.data.model.RegisterModel;
+import fr.titionfire.ffsaf.utils.Categorie;
+import fr.titionfire.ffsaf.utils.Genre;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+@RegisterForReflection
+public class CombEntity {
+ private long id;
+ private String lname;
+ private String fname;
+ Categorie categorie;
+ String club_uuid;
+ String club_str;
+ Genre genre;
+ String country;
+ int overCategory;
+ Integer weight;
+
+ public static CombEntity fromModel(MembreModel model) {
+ if (model == null)
+ return null;
+
+ return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
+ model.getClub() == null ? null : model.getClub().getClubId(),
+ model.getClub() == null ? "Sans club" : model.getClub().getName(), model.getGenre(), model.getCountry(),
+ 0, null);
+ }
+
+
+ public static CombEntity fromModel(CompetitionGuestModel model) {
+ if (model == null)
+ return null;
+
+ return new CombEntity(model.getId() * -1, model.getLname(), model.getFname(), model.getCategorie(), null,
+ model.getClub(), model.getGenre(), model.getCountry(), 0, model.getWeight());
+ }
+
+ public static CombEntity fromModel(RegisterModel registerModel) {
+ if (registerModel == null || registerModel.getMembre() == null)
+ return null;
+ MembreModel model = registerModel.getMembre();
+
+ return new CombEntity(model.getId(), model.getLname(), model.getFname(), registerModel.getCategorie(),
+ registerModel.getClub2() == null ? null : registerModel.getClub2().getClubId(),
+ registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(),
+ model.getCountry(), registerModel.getOverCategory(), registerModel.getWeight());
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/entity/MatchEntity.java b/src/main/java/fr/titionfire/ffsaf/domain/entity/MatchEntity.java
new file mode 100644
index 0000000..49b7e77
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/MatchEntity.java
@@ -0,0 +1,56 @@
+package fr.titionfire.ffsaf.domain.entity;
+
+import fr.titionfire.ffsaf.data.model.MatchModel;
+import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@RegisterForReflection
+public class MatchEntity {
+ private long id;
+ private CombEntity c1;
+ private CombEntity c2;
+ private long categorie_ord = 0;
+ private boolean isEnd;
+ private long categorie;
+ private Date date;
+ private List scores;
+ private char poule;
+ private List cardboard;
+
+ public static MatchEntity fromModel(MatchModel model) {
+ if (model == null)
+ return null;
+ return new MatchEntity(model.getId(),
+ (model.getC1_id() == null) ? CombEntity.fromModel(model.getC1_guest()) : CombEntity.fromModel(
+ model.getC1_id()),
+ (model.getC2_id() == null) ? CombEntity.fromModel(model.getC2_guest()) : CombEntity.fromModel(
+ model.getC2_id()),
+ model.getCategory_ord(), model.isEnd(), model.getCategory().getId(), model.getDate(),
+ model.getScores(),
+ model.getPoule(),
+ (model.getCardboard() == null) ? new ArrayList<>() : model.getCardboard().stream()
+ .map(CardboardEntity::fromModel).toList());
+ }
+
+ public int win() {
+ int sum = 0;
+ for (ScoreEmbeddable score : scores) {
+ if (score.getS1() == -1000 || score.getS2() == -1000)
+ continue;
+
+ if (score.getS1() > score.getS2())
+ sum++;
+ else if (score.getS1() < score.getS2())
+ sum--;
+ }
+ return sum;
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/entity/TreeEntity.java b/src/main/java/fr/titionfire/ffsaf/domain/entity/TreeEntity.java
new file mode 100644
index 0000000..6f65263
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/TreeEntity.java
@@ -0,0 +1,71 @@
+package fr.titionfire.ffsaf.domain.entity;
+
+import fr.titionfire.ffsaf.data.model.TreeModel;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+@RegisterForReflection
+public class TreeEntity {
+ private Long id;
+ private Long categorie;
+ private Integer level;
+ private MatchEntity match;
+ private TreeEntity left;
+ private TreeEntity right;
+ private TreeEntity associatedNode;
+
+ public static TreeEntity fromModel(TreeModel model) {
+ if (model == null)
+ return null;
+
+ return new TreeEntity(model.getId(), model.getCategory(), model.getLevel(), MatchEntity.fromModel(model.getMatch()), fromModel(model.getLeft()),
+ fromModel(model.getRight()), null);
+ }
+
+ public TreeEntity getMatchNode(Long matchId) {
+ if (this.match != null && this.match.getId() == matchId) {
+ return this;
+ } else {
+ if (this.left != null) {
+ TreeEntity left = this.left.getMatchNode(matchId);
+ if (left != null) {
+ return left;
+ }
+ }
+ if (this.right != null) {
+ TreeEntity right = this.right.getMatchNode(matchId);
+ if (right != null) {
+ return right;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static TreeEntity getParent(TreeEntity current, TreeEntity target) {
+ if (current == null) {
+ return null;
+ } else if (current.equals(target)) {
+ return null;
+ } else if (target.equals(current.left) || target.equals(current.right)) {
+ return current;
+ } else {
+ TreeEntity left = getParent(current.left, target);
+ if (left != null)
+ return left;
+ return getParent(current.right, target);
+ }
+ }
+
+ public static void setAssociated(TreeEntity current, TreeEntity next) {
+ if (current == null || next == null) {
+ return;
+ }
+ current.setAssociatedNode(next);
+ setAssociated(current.getLeft(), next.getLeft());
+ setAssociated(current.getRight(), next.getRight());
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java
index 19cbee0..0485ede 100644
--- a/src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java
+++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java
@@ -1,9 +1,6 @@
package fr.titionfire.ffsaf.domain.service;
-import fr.titionfire.ffsaf.data.model.MatchModel;
-import fr.titionfire.ffsaf.data.model.MembreModel;
-import fr.titionfire.ffsaf.data.model.CategoryModel;
-import fr.titionfire.ffsaf.data.model.TreeModel;
+import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.data.CategoryData;
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
@@ -45,10 +42,13 @@ public class CategoryService {
@Inject
CompetPermService permService;
+ @Inject
+ CompetitionGuestRepository competitionGuestRepository;
+
public Uni getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system)
.firstResult()
- .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
+ .onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
.map(CategoryData::fromModel);
}
@@ -171,6 +171,15 @@ public class CategoryService {
.invoke(o2 -> o2.forEach(m -> o.membres.put(m.getId(), m)))
)
)
+ .call(o -> Mutiny.fetch(o.category.getCompet().getGuests())
+ .invoke(o2 -> o2.forEach(m -> o.guest.put(m.getFname() + " " + m.getLname(), m)))
+ .map(o2 -> data.getMatches().stream().flatMap(m -> Stream.of(m.getC1_str(), m.getC2_str())
+ .filter(Objects::nonNull)).distinct().filter(s -> !o.guest.containsKey(s)).map(
+ CompetitionGuestModel::new).toList())
+ .call(o3 -> o3.isEmpty() ? Uni.createFrom().nullItem() :
+ Uni.join().all(o3.stream().map(o4 -> competitionGuestRepository.persist(o4)).toList())
+ .andFailFast())
+ .invoke(o2 -> o2.forEach(m -> o.guest.put(m.getFname() + " " + m.getLname(), m))))
.invoke(in -> {
ArrayList node = new ArrayList<>();
for (TreeModel treeModel : in.category.getTree())
@@ -214,8 +223,8 @@ public class CategoryService {
}
mm.setCategory(in.category);
mm.setCategory_ord(m.getCategory_ord());
- mm.setC1_str(m.getC1_str());
- mm.setC2_str(m.getC2_str());
+ mm.setC1_guest(in.guest.getOrDefault(m.getC1_str(), null));
+ mm.setC2_guest(in.guest.getOrDefault(m.getC2_str(), null));
mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null));
mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null));
mm.setEnd(m.isEnd());
@@ -238,6 +247,7 @@ public class CategoryService {
private static class WorkData {
CategoryModel category;
HashMap membres = new HashMap<>();
+ HashMap guest = new HashMap<>();
List match = new ArrayList<>();
List toRmMatch;
List unlinkNode;
@@ -246,7 +256,7 @@ public class CategoryService {
public Uni> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
- .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
+ .onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
.call(o -> permService.hasEditPerm(securityCtx, o.getCompet()))
.call(o -> Mutiny.fetch(o.getTree())
.call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :
@@ -260,7 +270,7 @@ public class CategoryService {
Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in)))
)
)
- .call(o -> matchRepository.delete("poule.id = ?1", o.getId()))
+ .call(o -> matchRepository.delete("category.id = ?1", o.getId()))
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())));
}
}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java
index 5e3ba1e..448b915 100644
--- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java
+++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java
@@ -56,7 +56,7 @@ public class CompetPermService {
CompletableFuture f = new CompletableFuture<>();
SReqCompet.getConfig(serverCustom.clients, id, f);
try {
- return f.get(1500, TimeUnit.MILLISECONDS);
+ return f.get(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
@@ -71,7 +71,8 @@ public class CompetPermService {
.chain(competitionModels -> {
CompletableFuture> f = new CompletableFuture<>();
SReqCompet.getAllHaveAccess(serverCustom.clients, securityCtx.getSubject(), f);
- return Uni.createFrom().future(f, Duration.ofMillis(1500))
+ return Uni.createFrom().future(f, Duration.ofMillis(500))
+ .onFailure().recoverWithItem(new HashMap<>())
.map(map_ -> {
HashMap map = new HashMap<>();
map_.forEach((key, value) -> map.put(Long.parseLong(key), value));
@@ -89,7 +90,7 @@ public class CompetPermService {
.onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));
Uni> none = cacheNoneAccess.getAsync(securityCtx.getSubject(),
- k -> competitionRepository.list("system = ?1", CompetitionSystem.NONE)
+ k -> competitionRepository.list("system = ?1", CompetitionSystem.INTERNAL)
.map(competitionModels -> {
HashMap map = new HashMap<>();
for (CompetitionModel model : competitionModels) {
@@ -184,7 +185,7 @@ public class CompetPermService {
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
throw new DForbiddenException();
- if (o.getSystem() == CompetitionSystem.NONE)
+ if (o.getSystem() == CompetitionSystem.INTERNAL)
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
return Uni.createFrom().nullItem();
@@ -219,13 +220,58 @@ public class CompetPermService {
if (o.getSystem() == CompetitionSystem.SAFCA)
return hasSafcaEditPerm(securityCtx, o.getId());
- if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
- throw new DForbiddenException();
-
- if (o.getSystem() == CompetitionSystem.NONE)
- if (securityCtx.isClubAdmin())
+ if (o.getSystem() == CompetitionSystem.INTERNAL) {
+ if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
return Uni.createFrom().nullItem();
+ if (o.getAdmin().contains(securityCtx.getSubject()))
+ return Uni.createFrom().nullItem();
+
+ throw new DForbiddenException();
+ }
+
+ throw new DForbiddenException();
+ })
+ );
+ }
+
+ /**
+ * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
+ */
+ public Uni hasTablePerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
+ return hasTablePerm(securityCtx, Uni.createFrom().item(competitionModel));
+ }
+
+ /**
+ * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
+ */
+ public Uni hasTablePerm(SecurityCtx securityCtx, long id) {
+ return hasTablePerm(securityCtx, competitionRepository.findById(id));
+ }
+
+ /**
+ * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
+ */
+ public Uni hasTablePerm(SecurityCtx securityCtx, Uni in) {
+ return in.call(Unchecked.function(o -> {
+ if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin"))
+ return Uni.createFrom().nullItem();
+
+ if (o.getSystem() == CompetitionSystem.SAFCA)
+ return hasSafcaTablePerm(securityCtx, o.getId());
+
+ if (o.getSystem() == CompetitionSystem.INTERNAL) {
+ if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
+ return Uni.createFrom().nullItem();
+
+ if (o.getAdmin().contains(securityCtx.getSubject()))
+ return Uni.createFrom().nullItem();
+ if (o.getTable().contains(securityCtx.getSubject()))
+ return Uni.createFrom().nullItem();
+
+ throw new DForbiddenException();
+ }
+
throw new DForbiddenException();
})
);
@@ -236,8 +282,8 @@ public class CompetPermService {
Uni.createFrom().nullItem()
:
getSafcaConfig(id).chain(Unchecked.function(o -> {
- if (!o.admin().contains(UUID.fromString(securityCtx.getSubject())) && !o.table()
- .contains(UUID.fromString(securityCtx.getSubject())))
+ if (!o.admin().contains(UUID.fromString(securityCtx.getSubject()))
+ && !o.table().contains(UUID.fromString(securityCtx.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}));
@@ -253,4 +299,16 @@ public class CompetPermService {
return Uni.createFrom().nullItem();
}));
}
+
+ private Uni> hasSafcaTablePerm(SecurityCtx securityCtx, long id) {
+ return securityCtx.roleHas("safca_super_admin") ?
+ Uni.createFrom().nullItem()
+ :
+ getSafcaConfig(id).chain(Unchecked.function(o -> {
+ if (!o.admin().contains(UUID.fromString(securityCtx.getSubject()))
+ && !o.table().contains(UUID.fromString(securityCtx.getSubject())))
+ throw new DForbiddenException();
+ return Uni.createFrom().nullItem();
+ }));
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java
index 52c0db5..7e4caa8 100644
--- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java
+++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java
@@ -1,9 +1,6 @@
package fr.titionfire.ffsaf.domain.service;
-import fr.titionfire.ffsaf.data.model.CompetitionModel;
-import fr.titionfire.ffsaf.data.model.HelloAssoRegisterModel;
-import fr.titionfire.ffsaf.data.model.MembreModel;
-import fr.titionfire.ffsaf.data.model.RegisterModel;
+import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
@@ -16,10 +13,9 @@ import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
-import fr.titionfire.ffsaf.utils.CompetitionSystem;
-import fr.titionfire.ffsaf.utils.RegisterMode;
-import fr.titionfire.ffsaf.utils.SecurityCtx;
-import fr.titionfire.ffsaf.utils.Utils;
+import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
+import fr.titionfire.ffsaf.utils.*;
+import fr.titionfire.ffsaf.ws.send.SRegister;
import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.quarkus.hibernate.reactive.panache.Panache;
@@ -64,6 +60,9 @@ public class CompetitionService {
@Inject
CombRepository combRepository;
+ @Inject
+ CompetitionGuestRepository competitionGuestRepository;
+
@Inject
ServerCustom serverCustom;
@@ -83,6 +82,9 @@ public class CompetitionService {
@Inject
Vertx vertx;
+ @Inject
+ SRegister sRegister;
+
@Inject
@CacheName("safca-config")
Cache cache;
@@ -103,12 +105,13 @@ public class CompetitionService {
if (id == 0) {
return Uni.createFrom()
.item(new CompetitionData(null, "", "", "", "", new Date(), new Date(),
- CompetitionSystem.NONE, RegisterMode.FREE, new Date(), new Date(), true,
+ CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true,
null, "", "", null, true, "", "", "", ""));
}
return permService.hasAdminViewPerm(securityCtx, id)
.chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc())
- .map(insc -> CompetitionData.fromModel(competitionModel).addInsc(insc)))
+ .chain(insc -> Mutiny.fetch(competitionModel.getGuests())
+ .map(guest -> CompetitionData.fromModel(competitionModel).addInsc(insc, guest))))
.chain(data ->
vertx.getOrCreateContext().executeBlocking(() -> {
keycloakService.getUser(UUID.fromString(data.getOwner()))
@@ -159,6 +162,14 @@ public class CompetitionService {
.map(pouleModels -> pouleModels.stream().map(CompetitionData::fromModel).toList());
}
+ public Uni> getAllSystemTable(SecurityCtx securityCtx,
+ CompetitionSystem system) {
+ return repository.list("system = ?1", system)
+ .chain(l -> Uni.join().all(l.stream().map(cm -> permService.hasTablePerm(securityCtx, cm)).toList())
+ .andCollectFailures())
+ .map(l -> l.stream().filter(Objects::nonNull).map(CompetitionData::fromModel).toList());
+ }
+
public Uni addOrUpdate(SecurityCtx securityCtx, CompetitionData data) {
if (data.getId() == null) {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
@@ -175,6 +186,7 @@ public class CompetitionService {
model.setSystem(data.getSystem());
model.setClub(clubModel);
model.setInsc(new ArrayList<>());
+ model.setGuests(new ArrayList<>());
model.setUuid(UUID.randomUUID().toString());
model.setOwner(securityCtx.getSubject());
@@ -184,7 +196,7 @@ public class CompetitionService {
}).map(CompetitionData::fromModel)
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem())
- .call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate(
+ .call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem());
} else {
return permService.hasEditPerm(securityCtx, data.getId())
@@ -208,7 +220,7 @@ public class CompetitionService {
}).map(CompetitionData::fromModel)
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem())
- .call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate(
+ .call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem());
}
}
@@ -235,11 +247,18 @@ public class CompetitionService {
public Uni> getRegister(SecurityCtx securityCtx, Long id, String source) {
if ("admin".equals(source))
return permService.hasEditPerm(securityCtx, id)
- .chain(c -> Mutiny.fetch(c.getInsc()))
- .onItem().transformToMulti(Multi.createFrom()::iterable)
- .onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
- .map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences()))
- .collect().asList();
+ .chain(c -> {
+ Uni> uni = Mutiny.fetch(c.getInsc())
+ .onItem().transformToMulti(Multi.createFrom()::iterable)
+ .onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
+ .map(cm -> SimpleRegisterComb.fromModel(cm, cm.getMembre().getLicences()))
+ .collect().asList();
+ return uni
+ .call(l -> Mutiny.fetch(c.getGuests())
+ .map(guest -> guest.stream().map(SimpleRegisterComb::fromModel).toList())
+ .invoke(l::addAll));
+ });
+
if ("club".equals(source))
return Uni.createFrom().nullItem()
.invoke(Unchecked.consumer(__ -> {
@@ -262,17 +281,44 @@ public class CompetitionService {
public Uni addRegisterComb(SecurityCtx securityCtx, Long id, RegisterRequestData data,
String source) {
if ("admin".equals(source))
- return permService.hasEditPerm(securityCtx, id)
- .chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
- .call(combModel -> {
- if (c.getBanMembre() == null)
- c.setBanMembre(new ArrayList<>());
- c.getBanMembre().remove(combModel.getId());
- return Panache.withTransaction(() -> repository.persist(c));
- })
- .chain(combModel -> updateRegister(data, c, combModel, true)))
- .chain(r -> Mutiny.fetch(r.getMembre().getLicences())
- .map(licences -> SimpleRegisterComb.fromModel(r, licences)));
+ if (data.getLicence() != -1) { // not a guest
+ return permService.hasEditPerm(securityCtx, id)
+ .chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
+ .call(combModel -> Mutiny.fetch(combModel.getLicences()))
+ .call(combModel -> {
+ if (c.getBanMembre() == null)
+ c.setBanMembre(new ArrayList<>());
+ c.getBanMembre().remove(combModel.getId());
+ return Panache.withTransaction(() -> repository.persist(c));
+ })
+ .chain(combModel -> updateRegister(data, c, combModel, true)))
+ .map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()));
+ } else {
+ return permService.hasEditPerm(securityCtx, id)
+ .chain(c -> competitionGuestRepository.findById(data.getId() * -1)
+ .map(g -> {
+ if (g != null)
+ return g;
+ CompetitionGuestModel model = new CompetitionGuestModel();
+ model.setCompetition(c);
+ return model;
+ }))
+ .chain(model -> {
+ model.setFname(data.getFname());
+ model.setLname(data.getLname());
+ model.setGenre(data.getGenre());
+ model.setClub(data.getClub());
+ model.setCountry(data.getCountry());
+ model.setWeight(data.getWeight());
+ model.setCategorie(data.getCategorie());
+
+ return Panache.withTransaction(() -> competitionGuestRepository.persist(model))
+ .call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
+ sRegister.sendRegister(model.getCompetition().getUuid(),
+ r) : Uni.createFrom().voidItem());
+ })
+ .map(SimpleRegisterComb::fromModel);
+ }
if ("club".equals(source))
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
@@ -283,6 +329,7 @@ public class CompetitionService {
throw new DBadRequestException("Inscription fermée");
}))
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
+ .call(combModel -> Mutiny.fetch(combModel.getLicences()))
.invoke(Unchecked.consumer(model -> {
if (!securityCtx.isInClubGroup(model.getClub().getId()))
throw new DForbiddenException();
@@ -291,8 +338,7 @@ public class CompetitionService {
"Vous n'avez pas le droit d'inscrire ce membre (par décision de l'administrateur de la compétition)");
}))
.chain(combModel -> updateRegister(data, c, combModel, false)))
- .chain(r -> Mutiny.fetch(r.getMembre().getLicences())
- .map(licences -> SimpleRegisterComb.fromModel(r, licences)));
+ .map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()));
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
@@ -347,15 +393,18 @@ public class CompetitionService {
SReqRegister.sendIfNeed(serverCustom.clients,
new CompetitionData.SimpleRegister(r.getMembre().getId(),
r.getOverCategory(), r.getWeight(), r.getCategorie(),
- (r.getClub() == null) ? null : r.getClub().getId()), c.getId());
+ (r.getClub() == null) ? null : r.getClub().getId(),
+ (r.getClub() == null) ? null : r.getClub().getName()), c.getId());
}
return r;
}))
- .chain(r -> Panache.withTransaction(() -> registerRepository.persist(r)));
+ .chain(r -> Panache.withTransaction(() -> registerRepository.persist(r)))
+ .call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
+ sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem());
}
private Uni findComb(Long licence, String fname, String lname) {
- if (licence != null && licence != 0) {
+ if (licence != null && licence > 0) {
return combRepository.find("licence = ?1", licence).firstResult()
.invoke(Unchecked.consumer(combModel -> {
if (combModel == null)
@@ -398,30 +447,45 @@ public class CompetitionService {
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
}))
- .call(cm -> membreService.getByAccountId(securityCtx.getSubject())
+ .call(cm -> membreService.getById(combId)
.invoke(Unchecked.consumer(model -> {
+ if (model == null)
+ throw new DNotFoundException("Membre " + combId + " n'existe pas");
if (!securityCtx.isInClubGroup(model.getClub().getId()))
throw new DForbiddenException();
})))
.chain(c -> deleteRegister(combId, c, false));
return repository.findById(id)
- .invoke(Unchecked.consumer(cm -> {
- if (cm.getRegisterMode() != RegisterMode.FREE)
- throw new DForbiddenException();
- if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
- throw new DBadRequestException("Inscription fermée");
- }))
+ .call(cm -> membreService.getByAccountId(securityCtx.getSubject())
+ .invoke(Unchecked.consumer(model -> {
+ if (cm.getRegisterMode() != RegisterMode.FREE || !Objects.equals(model.getId(), combId))
+ throw new DForbiddenException();
+ if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
+ throw new DBadRequestException("Inscription fermée");
+ })))
.chain(c -> deleteRegister(combId, c, false));
}
private Uni deleteRegister(Long combId, CompetitionModel c, boolean admin) {
+ if (admin && combId < 0) {
+ return competitionGuestRepository.find("competition = ?1 AND id = ?2", c, combId * -1).firstResult()
+ .onFailure().transform(t -> new DBadRequestException("Combattant non inscrit"))
+ .call(Unchecked.function(
+ model -> Panache.withTransaction(() -> competitionGuestRepository.delete(model))
+ .call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
+ sRegister.sendRegisterRemove(c.getUuid(), combId) : Uni.createFrom()
+ .voidItem())))
+ .replaceWithVoid();
+ }
return registerRepository.find("competition = ?1 AND membre.id = ?2", c, combId).firstResult()
.onFailure().transform(t -> new DBadRequestException("Combattant non inscrit"))
.call(Unchecked.function(registerModel -> {
if (!admin && registerModel.isLockEdit())
throw new DForbiddenException("Modification bloquée par l'administrateur de la compétition");
- return Panache.withTransaction(() -> registerRepository.delete(registerModel));
+ return Panache.withTransaction(() -> registerRepository.delete(registerModel))
+ .call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
+ sRegister.sendRegisterRemove(c.getUuid(), combId) : Uni.createFrom().voidItem());
}))
.replaceWithVoid();
}
@@ -440,6 +504,10 @@ public class CompetitionService {
.andCollectFailures()))
.call(competitionModel -> Panache.withTransaction(
() -> categoryRepository.delete("compet = ?1", competitionModel)))
+ .call(competitionModel -> Panache.withTransaction(
+ () -> registerRepository.delete("competition = ?1", competitionModel)))
+ .call(competitionModel -> Panache.withTransaction(
+ () -> competitionGuestRepository.delete("competition = ?1", competitionModel)))
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())))
.invoke(o -> SReqCompet.rmCompet(serverCustom.clients, id))
.call(__ -> cache.invalidate(id));
@@ -528,8 +596,13 @@ public class CompetitionService {
continue;
uni = uni.call(__ -> Panache.withTransaction(
- () -> registerRepository.delete("competition = ?1 AND membre = ?2",
- reg.getCompetition(), reg.getMembre())));
+ () -> registerRepository.delete("competition = ?1 AND membre = ?2",
+ reg.getCompetition(), reg.getMembre())))
+ .call(r -> reg.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
+ sRegister.sendRegisterRemove(reg.getCompetition().getUuid(),
+ reg.getMembre().getId()) : Uni.createFrom().voidItem()).onFailure()
+ .recoverWithNull()
+ ;
}
return uni;
@@ -541,7 +614,8 @@ public class CompetitionService {
public Uni registerHelloAsso(NotificationData data) {
String organizationSlug = data.getOrganizationSlug();
String formSlug = data.getFormSlug();
- RegisterRequestData req = new RegisterRequestData(null, "", "", null, 0, false);
+ RegisterRequestData req = new RegisterRequestData(null, "", "", null, 0, false, null, Categorie.CADET, Genre.NA,
+ null, "fr");
return repository.find("data1 = ?1 AND data2 = ?2", organizationSlug, formSlug).firstResult()
.onFailure().recoverWithNull()
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java
index 279d43d..71dda91 100644
--- a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java
+++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java
@@ -1,9 +1,11 @@
package fr.titionfire.ffsaf.domain.service;
+import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
-import fr.titionfire.ffsaf.data.repository.CombRepository;
-import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
+import fr.titionfire.ffsaf.data.repository.CombRepository;
+import fr.titionfire.ffsaf.data.repository.CompetitionGuestRepository;
+import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.rest.data.MatchData;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
@@ -33,6 +35,9 @@ public class MatchService {
@Inject
CompetPermService permService;
+ @Inject
+ CompetitionGuestRepository competitionGuestRepository;
+
public Uni getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
@@ -75,8 +80,6 @@ public class MatchService {
}
)
.chain(o -> {
- o.setC1_str(data.getC1_str());
- o.setC2_str(data.getC2_str());
o.setCategory_ord(data.getCategory_ord());
o.getScores().clear();
o.getScores().addAll(data.getScores());
@@ -88,6 +91,20 @@ public class MatchService {
.chain(() -> (data.getC1_id() == null) ?
Uni.createFrom().nullItem() : combRepository.findById(data.getC2_id()))
.invoke(o::setC2_id)
+ .chain(() -> (data.getC1_str() == null) ?
+ Uni.createFrom()
+ .item((CompetitionGuestModel) null) : competitionGuestRepository.find(
+ "fname = ?1 AND lname = ?2",
+ data.getC1_str().substring(0, data.getC1_str().indexOf(" ")),
+ data.getC1_str().substring(data.getC1_str().indexOf(" ") + 1)).firstResult())
+ .invoke(o::setC1_guest)
+ .chain(() -> (data.getC2_str() == null) ?
+ Uni.createFrom()
+ .item((CompetitionGuestModel) null) : competitionGuestRepository.find(
+ "fname = ?1 AND lname = ?2",
+ data.getC2_str().substring(0, data.getC2_str().indexOf(" ")),
+ data.getC2_str().substring(data.getC2_str().indexOf(" ") + 1)).firstResult())
+ .invoke(o::setC2_guest)
.chain(() -> Panache.withTransaction(() -> repository.persist(o)));
})
.map(MatchData::fromModel);
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java
new file mode 100644
index 0000000..ecd48a4
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java
@@ -0,0 +1,400 @@
+package fr.titionfire.ffsaf.domain.service;
+
+import fr.titionfire.ffsaf.data.model.*;
+import fr.titionfire.ffsaf.data.repository.CategoryRepository;
+import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
+import fr.titionfire.ffsaf.data.repository.MatchRepository;
+import fr.titionfire.ffsaf.data.repository.RegisterRepository;
+import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
+import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
+import fr.titionfire.ffsaf.utils.*;
+import io.quarkus.hibernate.reactive.panache.common.WithSession;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+import io.smallrye.mutiny.unchecked.Unchecked;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.Builder;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+@WithSession
+@ApplicationScoped
+public class ResultService {
+
+ @Inject
+ CompetitionRepository compRepository;
+
+ @Inject
+ RegisterRepository registerRepository;
+
+ @Inject
+ MembreService membreService;
+
+ @Inject
+ CategoryRepository categoryRepository;
+
+ @Inject
+ MatchRepository matchRepository;
+
+ private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("lang.String");
+
+ public Uni> getList(SecurityCtx securityCtx) {
+ return membreService.getByAccountId(securityCtx.getSubject())
+ .chain(m -> registerRepository.list("membre = ?1", m))
+ .onItem().transformToMulti(Multi.createFrom()::iterable)
+ .onItem().call(r -> Mutiny.fetch(r.getCompetition()))
+ .onItem().transform(r -> new Object[]{r.getCompetition().getUuid(), r.getCompetition().getName(),
+ r.getCompetition().getDate()})
+ .collect().asList();
+ }
+
+ public Uni> getCategory(String uuid, SecurityCtx securityCtx) {
+ return hasAccess(uuid, securityCtx)
+ .chain(m -> categoryRepository.list("compet.uuid = ?1", uuid)
+ .chain(cats -> matchRepository.list("(c1_id = ?1 OR c2_id = ?1 OR True) AND category IN ?2", //TODO rm OR True
+ m.getMembre(), cats)))
+ .map(matchModels -> {
+ HashMap> map = new HashMap<>();
+ for (MatchModel matchModel : matchModels) {
+ if (!map.containsKey(matchModel.getCategory().getId()))
+ map.put(matchModel.getCategory().getId(), new ArrayList<>());
+ map.get(matchModel.getCategory().getId()).add(matchModel);
+ }
+
+ return map.values();
+ })
+ .onItem()
+ .transformToMulti(Multi.createFrom()::iterable)
+ .onItem().call(list -> Mutiny.fetch(list.get(0).getCategory().getTree()))
+ .onItem().transform(this::getData)
+ .collect().asList();
+
+ }
+
+ private ResultCategoryData getData(List matchModels) {
+ ResultCategoryData out = new ResultCategoryData();
+
+ CategoryModel categoryModel = matchModels.get(0).getCategory();
+ out.setName(categoryModel.getName());
+ out.setType(categoryModel.getType());
+
+ getArray2(matchModels, out);
+ getTree(categoryModel.getTree(), out);
+ return out;
+ }
+
+ private void getArray2(List matchModels_, ResultCategoryData out) {
+ List matchModels = matchModels_.stream().filter(o -> o.getCategory_ord() >= 0).toList();
+
+ HashMap> matchMap = new HashMap<>();
+ for (MatchModel model : matchModels) {
+ char g = model.getPoule();
+ if (!matchMap.containsKey(g))
+ matchMap.put(g, new ArrayList<>());
+ matchMap.get(g).add(model);
+ }
+
+ matchMap.forEach((c, matchEntities) -> {
+ List matchs = matchEntities.stream()
+ .sorted(Comparator.comparing(MatchModel::getCategory_ord))
+ .map(ResultCategoryData.PouleArrayData::fromModel)
+ .toList();
+
+ List rankArray = matchEntities.stream()
+ .flatMap(m -> Stream.of(m.getC1Name(), m.getC2Name()))
+ .distinct()
+ .map(combName -> {
+ AtomicInteger w = new AtomicInteger(0);
+ AtomicInteger pointMake = new AtomicInteger(0);
+ AtomicInteger pointTake = new AtomicInteger(0);
+
+ matchEntities.stream()
+ .filter(m -> m.isEnd() && (m.getC1Name().equals(combName) || m.getC2Name()
+ .equals(combName)))
+ .forEach(matchModel -> {
+ int win = matchModel.win();
+ if ((matchModel.getC1Name()
+ .equals(combName) && win > 0) || matchModel.getC2Name()
+ .equals(combName) && win < 0)
+ w.getAndIncrement();
+
+ for (ScoreEmbeddable score : matchModel.getScores()) {
+ if (score.getS1() <= -900 || score.getS2() <= -900)
+ continue;
+ if (matchModel.getC1Name().equals(combName)) {
+ pointMake.addAndGet(score.getS1());
+ pointTake.addAndGet(score.getS2());
+ } else {
+ pointMake.addAndGet(score.getS2());
+ pointTake.addAndGet(score.getS1());
+ }
+ }
+ });
+ float pointRate = (pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get();
+
+ return new ResultCategoryData.RankArray(0, combName, w.get(),
+ pointMake.get(), pointTake.get(), pointRate);
+ })
+ .sorted(Comparator
+ .comparing(ResultCategoryData.RankArray::getWin)
+ .thenComparing(ResultCategoryData.RankArray::getPointRate).reversed())
+ .toList();
+ out.getMatchs().put(c, matchs);
+
+ int lastWin = -1;
+ float pointRate = 0;
+ int rank = 0;
+ for (ResultCategoryData.RankArray rankArray1 : rankArray) {
+ if (rankArray1.getWin() != lastWin || pointRate != rankArray1.getPointRate()) {
+ lastWin = rankArray1.getWin();
+ pointRate = rankArray1.getPointRate();
+ rank++;
+ }
+ rankArray1.setRank(rank);
+ }
+ out.getRankArray().put(c, rankArray);
+ });
+ }
+
+ private static void convertTree(TreeModel src, TreeNode dst) {
+ dst.setData(ResultCategoryData.TreeData.from(src.getMatch()));
+ if (src.getLeft() != null) {
+ dst.setLeft(new TreeNode<>());
+ convertTree(src.getLeft(), dst.getLeft());
+ }
+ if (src.getRight() != null) {
+ dst.setRight(new TreeNode<>());
+ convertTree(src.getRight(), dst.getRight());
+ }
+ }
+
+ private void getTree(List treeModels, ResultCategoryData out) {
+ ArrayList> trees = new ArrayList<>();
+ treeModels.stream().filter(t -> t.getLevel() != 0).forEach(treeModel -> {
+ TreeNode root = new TreeNode<>();
+ convertTree(treeModel, root);
+ trees.add(root);
+ });
+ out.setTrees(trees);
+ }
+
+ public Uni getAllCombArray(String uuid, SecurityCtx securityCtx) {
+ return hasAccess(uuid, securityCtx)
+ .chain(cm_register -> registerRepository.list("competition.uuid = ?1", uuid)
+ .chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
+ .map(matchModels -> new Pair<>(registers, matchModels)))
+ .map(pair -> {
+ List registers = pair.getKey();
+ List matchModels = pair.getValue();
+
+ CombsArrayData.CombsArrayDataBuilder builder = CombsArrayData.builder();
+
+ List combs = matchModels.stream()
+ .flatMap(m -> Stream.of(m.getC1Name(), m.getC2Name()))
+ .filter(Objects::nonNull)
+ .distinct()
+ .map(combName -> {
+ var builder2 = CombsArrayData.CombsData.builder();
+ AtomicInteger w = new AtomicInteger(0);
+ AtomicInteger l = new AtomicInteger(0);
+ AtomicInteger pointMake = new AtomicInteger();
+ AtomicInteger pointTake = new AtomicInteger();
+
+ matchModels.stream()
+ .filter(m -> m.isEnd() && (m.getC1Name().equals(combName)
+ || m.getC2Name().equals(combName)))
+ .forEach(matchModel -> {
+ int win = matchModel.win();
+ if ((combName.equals(matchModel.getC1Name()) && win > 0) ||
+ combName.equals(matchModel.getC2Name()) && win < 0) {
+ w.getAndIncrement();
+ } else {
+ l.getAndIncrement();
+ }
+
+ matchModel.getScores().stream()
+ .filter(s -> s.getS1() > -900 && s.getS2() > -900)
+ .forEach(score -> {
+ if (combName.equals(matchModel.getC1Name())) {
+ pointMake.addAndGet(score.getS1());
+ pointTake.addAndGet(score.getS2());
+ } else {
+ pointMake.addAndGet(score.getS2());
+ pointTake.addAndGet(score.getS1());
+ }
+ });
+ });
+
+ Categorie categorie = null;
+ ClubModel club = null;
+
+ Optional register = registers.stream()
+ .filter(r -> r.getName().equals(combName)).findFirst();
+ if (register.isPresent()) {
+ categorie = register.get().getCategorie();
+ club = register.get().getClub2();
+ }
+
+ builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
+ builder2.name(combName);
+ builder2.w(w.get());
+ builder2.l(l.get());
+ builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
+ builder2.club((club == null) ? BUNDLE.getString("no.licence") : club.getName());
+ builder2.pointMake(pointMake.get());
+ builder2.pointTake(pointTake.get());
+ builder2.ratioPoint(
+ (pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
+
+ return builder2.build();
+ })
+ .sorted(Comparator.comparing(CombsArrayData.CombsData::name))
+ .toList();
+
+ builder.nb_insc(combs.size());
+ builder.tt_match((int) matchModels.stream().filter(MatchModel::isEnd).count());
+ builder.point(combs.stream().mapToInt(CombsArrayData.CombsData::pointMake).sum());
+ builder.combs(combs);
+
+ return builder.build();
+ })
+ );
+ }
+
+
+ @Builder
+ @RegisterForReflection
+ public static record CombsArrayData(int nb_insc, int tt_match, long point, List combs) {
+ @Builder
+ @RegisterForReflection
+ public static record CombsData(String cat, String club, String name, int w, int l, float ratioVictoire,
+ float ratioPoint, int pointMake, int pointTake) {
+ }
+ }
+
+ public Uni getClubArray(String uuid, SecurityCtx securityCtx) {
+ ClubArrayData.ClubArrayDataBuilder builder = ClubArrayData.builder();
+
+ return hasAccess(uuid, securityCtx)
+ .invoke(cm_register -> builder.name(cm_register.getClub2().getName()))
+ .chain(cm_register -> registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid,
+ cm_register.getClub2())
+ .chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
+ .map(matchModels -> new Pair<>(registers, matchModels)))
+ .map(pair -> {
+ List registers = pair.getKey();
+ List matchModels = pair.getValue();
+
+ builder.nb_insc(registers.size());
+
+ AtomicInteger tt_win = new AtomicInteger(0);
+ AtomicInteger tt_match = new AtomicInteger(0);
+
+ List combData = registers.stream()
+ .map(register -> {
+
+ var builder2 = ClubArrayData.CombData.builder();
+ AtomicInteger w = new AtomicInteger(0);
+ AtomicInteger l = new AtomicInteger(0);
+ AtomicInteger pointMake = new AtomicInteger();
+ AtomicInteger pointTake = new AtomicInteger();
+
+ matchModels.stream()
+ .filter(m -> m.isEnd() && (register.getMembre().equals(m.getC1_id())
+ || register.getMembre().equals(m.getC2_id())))
+ .forEach(matchModel -> {
+ int win = matchModel.win();
+ if ((register.getMembre()
+ .equals(matchModel.getC1_id()) && win > 0) ||
+ register.getMembre()
+ .equals(matchModel.getC2_id()) && win < 0) {
+ w.getAndIncrement();
+ } else {
+ l.getAndIncrement();
+ }
+
+ matchModel.getScores().stream()
+ .filter(s -> s.getS1() > -900 && s.getS2() > -900)
+ .forEach(score -> {
+ if (register.getMembre()
+ .equals(matchModel.getC1_id())) {
+ pointMake.addAndGet(score.getS1());
+ pointTake.addAndGet(score.getS2());
+ } else {
+ pointMake.addAndGet(score.getS2());
+ pointTake.addAndGet(score.getS1());
+ }
+ });
+ });
+
+ Categorie categorie = register.getCategorie();
+ if (categorie == null)
+ categorie = register.getMembre().getCategorie();
+
+ builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
+ builder2.name(register.getName());
+ builder2.w(w.get());
+ builder2.l(l.get());
+ builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
+ builder2.pointMake(pointMake.get());
+ builder2.pointTake(pointTake.get());
+ builder2.ratioPoint(
+ (pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
+
+ tt_win.addAndGet(w.get());
+ tt_match.addAndGet(w.get() + l.get());
+
+ return builder2.build();
+ })
+ .sorted(Comparator.comparing(ClubArrayData.CombData::name))
+ .toList();
+
+ builder.nb_match(tt_match.get());
+ builder.match_w(tt_win.get());
+ builder.ratioVictoire((float) combData.stream().filter(c -> c.l + c.w != 0)
+ .mapToDouble(ClubArrayData.CombData::ratioVictoire).average().orElse(0L));
+ builder.pointMake(combData.stream().mapToInt(ClubArrayData.CombData::pointMake).sum());
+ builder.pointTake(combData.stream().mapToInt(ClubArrayData.CombData::pointTake).sum());
+ builder.ratioPoint((float) combData.stream().filter(c -> c.l + c.w != 0)
+ .mapToDouble(ClubArrayData.CombData::ratioPoint).average().orElse(0L));
+ builder.combs(combData);
+
+ return builder.build();
+ })
+ );
+ }
+
+ @Builder
+ @RegisterForReflection
+ public static record ClubArrayData(String name, int nb_insc, int nb_match, int match_w, float ratioVictoire,
+ float ratioPoint, int pointMake, int pointTake, List combs) {
+ @Builder
+ @RegisterForReflection
+ public static record CombData(String cat, String name, int w, int l, float ratioVictoire,
+ float ratioPoint, int pointMake, int pointTake) {
+ }
+ }
+
+ private Uni hasAccess(String uuid, SecurityCtx securityCtx) {
+ return registerRepository.find("membre.userId = ?1 AND competition.uuid = ?2", securityCtx.getSubject(), uuid)
+ .firstResult()
+ .invoke(Unchecked.consumer(o -> {
+ if (o == null)
+ throw new DForbiddenException("Access denied");
+ }));
+ }
+
+ private Uni hasAccess(Long compId, SecurityCtx securityCtx) {
+ return registerRepository.find("membre.userId = ?1 AND competition.id = ?2", securityCtx.getSubject(), compId)
+ .firstResult()
+ .invoke(Unchecked.consumer(o -> {
+ if (o == null)
+ throw new DForbiddenException("Access denied");
+ }));
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java
index 16ddb09..fdaf2ea 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java
@@ -186,6 +186,6 @@ public class AffiliationRequestEndpoints {
public Uni getStatus(
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) throws URISyntaxException {
return Utils.getMediaFile(id, media, "aff_request/status", "affiliation_request_" + id,
- Uni.createFrom().nullItem());
+ Uni.createFrom().nullItem(), false);
}
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java
index 7048e4f..a329192 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java
@@ -335,7 +335,7 @@ public class ClubEndpoints {
@Parameter(description = "Identifiant long (clubId) de club") @PathParam("clubId") String clubId) {
return clubService.getByClubId(clubId).chain(Unchecked.function(clubModel -> {
try {
- return Utils.getMediaFile((clubModel != null) ? clubModel.getId() : -1, media, "ppClub",
+ return Utils.getMediaFileNoDefault((clubModel != null) ? clubModel.getId() : -1, media, "ppClub",
Uni.createFrom().nullItem());
} catch (URISyntaxException e) {
throw new InternalError();
@@ -358,7 +358,7 @@ public class ClubEndpoints {
return clubService.getById(id).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> {
try {
return Utils.getMediaFile(clubModel.getId(), media, "clubStatus",
- "statue-" + clubModel.getName(), Uni.createFrom().nullItem());
+ "statue-" + clubModel.getName(), Uni.createFrom().nullItem(), false);
} catch (URISyntaxException e) {
throw new InternalError();
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java
index 51c0d9f..9b9fabf 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java
@@ -47,4 +47,12 @@ public class CompetitionAdminEndpoints {
public Uni> getAllSystemAdmin(@PathParam("system") CompetitionSystem system) {
return service.getAllSystemAdmin(securityCtx, system);
}
+
+ @GET
+ @Path("all/{system}/table")
+ @Authenticated
+ @Produces(MediaType.APPLICATION_JSON)
+ public Uni> getAllSystemTable(@PathParam("system") CompetitionSystem system) {
+ return service.getAllSystemTable(securityCtx, system);
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java
new file mode 100644
index 0000000..4016887
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java
@@ -0,0 +1,48 @@
+package fr.titionfire.ffsaf.rest;
+
+import fr.titionfire.ffsaf.domain.service.ResultService;
+import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
+import fr.titionfire.ffsaf.utils.SecurityCtx;
+import io.quarkus.security.Authenticated;
+import io.smallrye.mutiny.Uni;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+
+import java.util.List;
+
+@Authenticated
+@Path("api/result")
+public class ResultEndpoints {
+
+ @Inject
+ ResultService resultService;
+
+ @Inject
+ SecurityCtx securityCtx;
+
+ @GET
+ @Path("list")
+ public Uni> getList() {
+ return resultService.getList(securityCtx);
+ }
+
+ @GET
+ @Path("{uuid}")
+ public Uni> getCategory(@PathParam("uuid") String uuid) {
+ return resultService.getCategory(uuid, securityCtx);
+ }
+
+ @GET
+ @Path("{uuid}/club")
+ public Uni getClub(@PathParam("uuid") String uuid) {
+ return resultService.getClubArray(uuid, securityCtx);
+ }
+
+ @GET
+ @Path("{uuid}/comb")
+ public Uni getComb(@PathParam("uuid") String uuid) {
+ return resultService.getAllCombArray(uuid, securityCtx);
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java
index d6b07d1..8b33c2c 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java
@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.rest.data;
+import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.utils.Categorie;
@@ -11,6 +12,7 @@ import lombok.Data;
import java.util.Date;
import java.util.List;
+import java.util.stream.Stream;
@Data
@AllArgsConstructor
@@ -57,9 +59,9 @@ public class CompetitionData {
model.getAdresse(), "", model.getDate(), model.getTodate(), null,
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
null, model.getClub().getName(), "", null, false,
- "","", "","");
+ "", "", "", "");
- if (model.getRegisterMode() == RegisterMode.HELLOASSO){
+ if (model.getRegisterMode() == RegisterMode.HELLOASSO) {
out.setData1(model.getData1());
out.setData2(model.getData2());
}
@@ -67,10 +69,15 @@ public class CompetitionData {
return out;
}
- public CompetitionData addInsc(List insc) {
- this.registers = insc.stream()
- .map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight(),
- i.getCategorie(), (i.getClub() == null) ? null : i.getClub().getId())).toList();
+ public CompetitionData addInsc(List insc, List guests) {
+ this.registers = Stream.concat(
+ insc.stream()
+ .map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight(),
+ i.getCategorie(), (i.getClub() == null) ? null : i.getClub().getId(),
+ (i.getClub() == null) ? null : i.getClub().getName())),
+ guests.stream()
+ .map(i -> new SimpleRegister(i.getId() * -1, 0, i.getWeight(),
+ i.getCategorie(), null, i.getClub()))).toList();
return this;
}
@@ -83,5 +90,6 @@ public class CompetitionData {
Integer weight;
Categorie categorie;
Long club;
+ String club_str;
}
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java
index 143d0ad..7b13012 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java
@@ -28,8 +28,10 @@ public class MatchData {
return null;
return new MatchData(model.getSystemId(),
- (model.getC1_id() == null) ? null : model.getC1_id().getId(), model.getC1_str(),
- (model.getC2_id() == null) ? null : model.getC2_id().getId(), model.getC2_str(),
+ (model.getC1_id() == null) ? null : model.getC1_id().getId(),
+ (model.getC1_guest() == null) ? null : model.getC1_guest().getName(),
+ (model.getC2_id() == null) ? null : model.getC2_id().getId(),
+ (model.getC2_guest() == null) ? null : model.getC2_guest().getName(),
model.getCategory().getId(), model.getCategory_ord(), model.isEnd(), model.getPoule(),
model.getScores());
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java
index 11fa9aa..3b482f1 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java
@@ -1,5 +1,7 @@
package fr.titionfire.ffsaf.rest.data;
+import fr.titionfire.ffsaf.utils.Categorie;
+import fr.titionfire.ffsaf.utils.Genre;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -17,4 +19,11 @@ public class RegisterRequestData {
private Integer weight;
private int overCategory;
private boolean lockEdit = false;
+
+ // for guest registration only
+ private Long id = null;
+ private Categorie categorie = Categorie.CADET;
+ private Genre genre = Genre.NA;
+ private String club = null;
+ private String country = null;
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/ResultCategoryData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/ResultCategoryData.java
new file mode 100644
index 0000000..2054e52
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/ResultCategoryData.java
@@ -0,0 +1,59 @@
+package fr.titionfire.ffsaf.rest.data;
+
+import fr.titionfire.ffsaf.data.model.MatchModel;
+import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
+import fr.titionfire.ffsaf.utils.TreeNode;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@RegisterForReflection
+public class ResultCategoryData {
+ int type;
+ String name;
+ HashMap> matchs = new HashMap<>();
+ HashMap> rankArray = new HashMap<>();
+ ArrayList> trees;
+
+ @Data
+ @AllArgsConstructor
+ @RegisterForReflection
+ public static class RankArray {
+ int rank;
+ String name;
+ int win;
+ int pointMake;
+ int pointTake;
+ float pointRate;
+ }
+
+ @RegisterForReflection
+ public record PouleArrayData(String red, boolean red_w, List score, boolean blue_w, String blue, boolean end) {
+ public static PouleArrayData fromModel(MatchModel matchModel) {
+ return new PouleArrayData(
+ matchModel.getC1Name(),
+ matchModel.isEnd() && matchModel.win() > 0,
+ matchModel.isEnd() ?
+ matchModel.getScores().stream().map(s -> new Integer[]{s.getS1(), s.getS2()}).toList()
+ : new ArrayList<>(),
+ matchModel.isEnd() && matchModel.win() < 0,
+ matchModel.getC2Name(),
+ matchModel.isEnd());
+ }
+ }
+
+ @RegisterForReflection
+ public static record TreeData(long id, String c1FullName, String c2FullName, List scores,
+ boolean end) {
+ public static TreeData from(MatchModel match) {
+ return new TreeData(match.getId(), match.getC1Name(), match.getC2Name(), match.getScores(), match.isEnd());
+ }
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java
index 1125357..148c950 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java
@@ -1,9 +1,12 @@
package fr.titionfire.ffsaf.rest.data;
+import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
+import fr.titionfire.ffsaf.utils.Categorie;
+import fr.titionfire.ffsaf.utils.Genre;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
@@ -18,8 +21,9 @@ public class SimpleRegisterComb {
private long id;
private String fname;
private String lname;
- private String genre;
- private String categorie;
+ private Genre genre;
+ private String country;
+ private Categorie categorie;
private SimpleClubModel club;
private Integer licence;
private Integer weight;
@@ -30,11 +34,18 @@ public class SimpleRegisterComb {
public static SimpleRegisterComb fromModel(RegisterModel register, List licences) {
MembreModel membreModel = register.getMembre();
return new SimpleRegisterComb(membreModel.getId(), membreModel.getFname(), membreModel.getLname(),
- membreModel.getGenre().name(),
- (register.getCategorie() == null) ? "Catégorie inconnue" : register.getCategorie().getName(),
+ membreModel.getGenre(), membreModel.getCountry(),
+ (register.getCategorie() == null) ? null : register.getCategorie(),
SimpleClubModel.fromModel(register.getClub()), membreModel.getLicence(), register.getWeight(),
register.getOverCategory(),
licences.stream().anyMatch(l -> l.isValidate() && l.getSaison() == Utils.getSaison()),
register.isLockEdit());
}
+
+ public static SimpleRegisterComb fromModel(CompetitionGuestModel guest) {
+ return new SimpleRegisterComb(guest.getId() * -1, guest.getFname(), guest.getLname(),
+ guest.getGenre(), guest.getCountry(), guest.getCategorie(),
+ new SimpleClubModel(null, guest.getClub(), "fr", null),
+ null, guest.getWeight(), 0, false, false);
+ }
}
diff --git a/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java b/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java
index 32cb79a..a183d81 100644
--- a/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java
+++ b/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java
@@ -1,5 +1,5 @@
package fr.titionfire.ffsaf.utils;
public enum CompetitionSystem {
- SAFCA, NONE
+ SAFCA, INTERNAL
}
diff --git a/src/main/java/fr/titionfire/ffsaf/utils/TreeNode.java b/src/main/java/fr/titionfire/ffsaf/utils/TreeNode.java
new file mode 100644
index 0000000..c175692
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/utils/TreeNode.java
@@ -0,0 +1,35 @@
+package fr.titionfire.ffsaf.utils;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@RegisterForReflection
+public class TreeNode {
+ private T data;
+ private TreeNode left;
+ private TreeNode right;
+
+ public TreeNode(T data) {
+ this(data, null, null);
+ }
+
+ public int death() {
+ int dg = 0;
+ int dd = 0;
+
+ if (this.right != null)
+ dg = this.right.death();
+
+ if (this.left != null)
+ dg = this.left.death();
+
+ return 1 + Math.max(dg, dd);
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java
index b4a74a9..550c6e8 100644
--- a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java
+++ b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java
@@ -150,11 +150,16 @@ public class Utils {
public static Uni getMediaFile(long id, String media, String dirname,
Uni> uniBase) throws URISyntaxException {
- return getMediaFile(id, media, dirname, null, uniBase);
+ return getMediaFile(id, media, dirname, null, uniBase, true);
+ }
+
+ public static Uni getMediaFileNoDefault(long id, String media, String dirname,
+ Uni> uniBase) throws URISyntaxException {
+ return getMediaFile(id, media, dirname, null, uniBase, false);
}
public static Uni getMediaFile(long id, String media, String dirname, String out_filename,
- Uni> uniBase) throws URISyntaxException {
+ Uni> uniBase, boolean default_) throws URISyntaxException {
Future> future = CompletableFuture.supplyAsync(() -> {
FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
File[] files = new File(media, dirname).listFiles(filter);
@@ -182,19 +187,25 @@ public class Utils {
return uniBase.chain(__ -> Uni.createFrom().future(future)
.chain(filePair -> {
if (filePair == null) {
- return Uni.createFrom().future(future2).map(data -> {
- if (data == null)
- return Response.noContent().build();
+ if (default_) {
+ return Uni.createFrom().future(future2).map(data -> {
+ if (data == null)
+ return Response.noContent().build();
- String mimeType = "image/apng";
- Response.ResponseBuilder resp = Response.ok(data);
- resp.type(MediaType.APPLICATION_OCTET_STREAM);
- resp.header(HttpHeaders.CONTENT_LENGTH, data.length);
- resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
- resp.header(HttpHeaders.CONTENT_DISPOSITION,
- "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\""));
- return resp.build();
- });
+ String mimeType = "image/apng";
+ Response.ResponseBuilder resp = Response.ok(data);
+ resp.type(MediaType.APPLICATION_OCTET_STREAM);
+ resp.header(HttpHeaders.CONTENT_LENGTH, data.length);
+ resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
+ resp.header(HttpHeaders.CONTENT_DISPOSITION,
+ "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\""));
+ return resp.build();
+ });
+ } else {
+ Response.ResponseBuilder resp = Response.status(404);
+ resp.header(HttpHeaders.CACHE_CONTROL, "max-age=600");
+ return Uni.createFrom().item(resp.build());
+ }
} else {
return Uni.createFrom().item(() -> {
String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName());
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java
new file mode 100644
index 0000000..c2256d2
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java
@@ -0,0 +1,207 @@
+package fr.titionfire.ffsaf.ws;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
+import fr.titionfire.ffsaf.domain.service.CompetPermService;
+import fr.titionfire.ffsaf.net2.MessageType;
+import fr.titionfire.ffsaf.utils.SecurityCtx;
+import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
+import fr.titionfire.ffsaf.ws.recv.*;
+import fr.titionfire.ffsaf.ws.send.JsonUni;
+import io.quarkus.hibernate.reactive.panache.common.WithSession;
+import io.quarkus.security.Authenticated;
+import io.quarkus.websockets.next.*;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+import io.smallrye.mutiny.unchecked.Unchecked;
+import jakarta.annotation.PostConstruct;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.ForbiddenException;
+import org.jboss.logging.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
+
+@Authenticated
+@WebSocket(path = "api/ws/competition/{uuid}")
+public class CompetitionWS {
+ private static final Logger LOGGER = Logger.getLogger(CompetitionWS.class);
+
+ private static final HashMap>> waitingResponse = new HashMap<>();
+
+ @Inject
+ RMatch rMatch;
+
+ @Inject
+ RCategorie rCategorie;
+
+ @Inject
+ RRegister rRegister;
+
+ @Inject
+ RCardboard rCardboard;
+
+ @Inject
+ SecurityCtx securityCtx;
+
+ @Inject
+ CompetPermService competPermService;
+
+ @Inject
+ CompetitionRepository competitionRepository;
+
+ HashMap wsMethods = new HashMap<>();
+
+ public void getWSReceiverMethods(Class> clazz, Object object) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(WSReceiver.class)) {
+ if (method.getParameterCount() <= 1) {
+ LOGGER.warnf("@WSReceiver has no parameters for method %s", method.getName());
+ continue;
+ }
+ if (!method.getReturnType().equals(Uni.class)) {
+ LOGGER.warnf("@WSReceiver has returned unexpected type %s", method.getReturnType());
+ continue;
+ }
+ // method.setAccessible(true);
+ wsMethods.put(method, object);
+ }
+ }
+ }
+
+ @PostConstruct
+ void init() {
+ getWSReceiverMethods(RMatch.class, rMatch);
+ getWSReceiverMethods(RCategorie.class, rCategorie);
+ getWSReceiverMethods(RRegister.class, rRegister);
+ getWSReceiverMethods(RCardboard.class, rCardboard);
+ }
+
+ @OnOpen
+ @WithSession
+ Uni open(WebSocketConnection connection) {
+ LOGGER.infof("Opening CompetitionWS for %s", connection.pathParam("uuid"));
+ LOGGER.debugf("Active connections: %d", connection.getOpenConnections().size());
+
+ return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
+ .invoke(Unchecked.consumer(cm -> {
+ if (cm == null)
+ throw new ForbiddenException();
+ }))
+ .call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> PermLevel.ADMIN)
+ .onFailure()
+ .recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> PermLevel.TABLE))
+ .onFailure()
+ .recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW))
+ .invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem.toString()))
+ .invoke(prem -> LOGGER.infof("Connection permission: %s", prem))
+ .onFailure().transform(t -> new ForbiddenException()))
+ .invoke(__ -> {
+ connection.userData().put(UserData.TypedKey.forString("subject"), securityCtx.getSubject());
+ waitingResponse.put(connection, new HashMap<>());
+ })
+ .map(cm -> {
+ WelcomeInfo welcomeInfo = new WelcomeInfo();
+
+ welcomeInfo.setName(cm.getName());
+ welcomeInfo.setPerm(connection.userData().get(UserData.TypedKey.forString("prem")));
+
+ return new MessageOut(UUID.randomUUID(), "welcomeInfo", MessageType.NOTIFY, welcomeInfo);
+ });
+ }
+
+ @OnClose
+ void close(WebSocketConnection connection) {
+ LOGGER.infof("Closing CompetitionWS for %s ", connection.pathParam("uuid"));
+ LOGGER.debugf("Active connections: %d", connection.getOpenConnections().size());
+
+ waitingResponse.remove(connection);
+ }
+
+ private MessageOut makeReply(MessageIn message, Object data) {
+ return new MessageOut(message.uuid(), message.code(), MessageType.REPLY, data);
+ }
+
+ private MessageOut makeError(MessageIn message, Object data) {
+ return new MessageOut(message.uuid(), message.code(), MessageType.ERROR, data);
+ }
+
+ @OnTextMessage
+ Multi processAsync(WebSocketConnection connection, MessageIn message) {
+ System.out.println(message);
+ if (message.type() == MessageType.REPLY || message.type() == MessageType.ERROR) {
+ try {
+ JsonUni> jsonUni = waitingResponse.get(connection).get(message.uuid());
+ if (jsonUni == null) {
+ LOGGER.debugf("No JsonUni found for %s", message.uuid());
+ if (message.type() == MessageType.ERROR)
+ LOGGER.errorf("request %s make error %s", message.uuid(), message.data());
+ return null;
+ }
+ waitingResponse.get(connection).remove(message.uuid());
+
+ if (message.type() == MessageType.ERROR)
+ return jsonUni.castAndError(message.data()).onFailure()
+ .invoke(t -> LOGGER.error(t.getMessage(), t))
+ .replaceWith((MessageOut) null).toMulti().filter(__ -> false);
+
+ return jsonUni.castAndChain(message.data()).onFailure()
+ .invoke(t -> LOGGER.error(t.getMessage(), t))
+ .replaceWith((MessageOut) null).toMulti().filter(__ -> false);
+ } catch (JsonProcessingException e) {
+ LOGGER.warn(e.getMessage(), e);
+ }
+ }
+
+ try {
+ for (Map.Entry entry : wsMethods.entrySet()) {
+ Method method = entry.getKey();
+ WSReceiver wsReceiver = method.getAnnotation(WSReceiver.class);
+ PermLevel perm = PermLevel.valueOf(connection.userData().get(UserData.TypedKey.forString("prem")));
+ if (wsReceiver.code().equalsIgnoreCase(message.code())) {
+ if (wsReceiver.permission().ordinal() > perm.ordinal())
+ return Uni.createFrom().item(makeError(message, "Permission denied")).toMulti();
+ return ((Uni>) method.invoke(entry.getValue(), connection,
+ MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
+ .map(o -> makeReply(message, o))
+ .onFailure()
+ .recoverWithItem(t -> {
+ LOGGER.error(t.getMessage(), t);
+ return makeError(message, t.getMessage());
+ }).toMulti()
+ .filter(__ -> message.type() == MessageType.REQUEST);
+ }
+ }
+ return Uni.createFrom().item(makeError(message, "No receiver method found")).toMulti();
+ } catch (IllegalAccessException | InvocationTargetException | JsonProcessingException e) {
+ LOGGER.warn(e.getMessage(), e);
+ return Uni.createFrom().item(makeError(message, e.getMessage())).toMulti();
+ }
+
+ // return Uni.createFrom().item(new Message<>(message.uuid(), message.code(), MessageType.REPLY, "ko"));
+ }
+
+ public static Uni sendNotifyToOtherEditor(WebSocketConnection connection, String code, Object data) {
+ String uuid = connection.pathParam("uuid");
+
+ List> queue = new ArrayList<>();
+ queue.add(Uni.createFrom().voidItem()); // For avoid empty queue
+
+ connection.getOpenConnections().forEach(c -> {
+ if (uuid.equals(c.pathParam("uuid"))) {
+ queue.add(c.sendText(new MessageOut(UUID.randomUUID(), code, MessageType.NOTIFY, data)));
+ }
+ });
+
+ return Uni.join().all(queue).andCollectFailures().onFailure().recoverWithNull().replaceWithVoid();
+ }
+
+ @OnError
+ Uni error(WebSocketConnection connection, ForbiddenException t) {
+ return connection.close(CloseReason.INTERNAL_SERVER_ERROR);
+ //return "forbidden: " + securityCtx.getSubject();
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/MessageIn.java b/src/main/java/fr/titionfire/ffsaf/ws/MessageIn.java
new file mode 100644
index 0000000..99532ec
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/MessageIn.java
@@ -0,0 +1,12 @@
+package fr.titionfire.ffsaf.ws;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import fr.titionfire.ffsaf.net2.MessageType;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+import java.util.UUID;
+
+@RegisterForReflection
+public record MessageIn (UUID uuid, String code, MessageType type, JsonNode data){
+
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/MessageOut.java b/src/main/java/fr/titionfire/ffsaf/ws/MessageOut.java
new file mode 100644
index 0000000..4db5c3e
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/MessageOut.java
@@ -0,0 +1,11 @@
+package fr.titionfire.ffsaf.ws;
+
+import fr.titionfire.ffsaf.net2.MessageType;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+import java.util.UUID;
+
+@RegisterForReflection
+public record MessageOut(UUID uuid, String code, MessageType type, Object data){
+
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/PermLevel.java b/src/main/java/fr/titionfire/ffsaf/ws/PermLevel.java
new file mode 100644
index 0000000..3e7a2e6
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/PermLevel.java
@@ -0,0 +1,8 @@
+package fr.titionfire.ffsaf.ws;
+
+public enum PermLevel {
+ NONE,
+ VIEW,
+ TABLE,
+ ADMIN,
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/data/WelcomeInfo.java b/src/main/java/fr/titionfire/ffsaf/ws/data/WelcomeInfo.java
new file mode 100644
index 0000000..eebe679
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/data/WelcomeInfo.java
@@ -0,0 +1,11 @@
+package fr.titionfire.ffsaf.ws.data;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Data;
+
+@Data
+@RegisterForReflection
+public class WelcomeInfo {
+ private String name;
+ private String perm;
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCardboard.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCardboard.java
new file mode 100644
index 0000000..59b6360
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCardboard.java
@@ -0,0 +1,128 @@
+package fr.titionfire.ffsaf.ws.recv;
+
+import fr.titionfire.ffsaf.data.model.CardboardModel;
+import fr.titionfire.ffsaf.data.model.MatchModel;
+import fr.titionfire.ffsaf.data.repository.CardboardRepository;
+import fr.titionfire.ffsaf.data.repository.MatchRepository;
+import fr.titionfire.ffsaf.domain.entity.CardboardEntity;
+import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
+import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
+import fr.titionfire.ffsaf.ws.PermLevel;
+import fr.titionfire.ffsaf.ws.send.SSCardboard;
+import io.quarkus.hibernate.reactive.panache.Panache;
+import io.quarkus.hibernate.reactive.panache.common.WithSession;
+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 lombok.Data;
+
+import java.util.Objects;
+
+@WithSession
+@ApplicationScoped
+@RegisterForReflection
+public class RCardboard {
+
+ @Inject
+ MatchRepository matchRepository;
+
+ @Inject
+ CardboardRepository cardboardRepository;
+
+ 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");
+ }));
+ }
+
+ @WSReceiver(code = "sendCardboardChange", permission = PermLevel.TABLE)
+ public Uni sendCardboardChange(WebSocketConnection connection, SendCardboard card) {
+ return getById(card.matchId, connection)
+ .chain(matchModel -> cardboardRepository.find("(comb.id = ?1 OR guestComb.id = ?2) AND match.id = ?3",
+ card.combId, card.combId * -1, card.matchId).firstResult()
+ .chain(model -> {
+ if (model != null) {
+ model.setRed(model.getRed() + card.red);
+ model.setYellow(model.getYellow() + card.yellow);
+ return Panache.withTransaction(() -> cardboardRepository.persist(model));
+ }
+ CardboardModel cardboardModel = new CardboardModel();
+
+ cardboardModel.setCompet(matchModel.getCategory().getCompet());
+ cardboardModel.setMatch(matchModel);
+ cardboardModel.setRed(card.red);
+ cardboardModel.setYellow(card.yellow);
+ cardboardModel.setComb(null);
+ cardboardModel.setGuestComb(null);
+
+ if (card.combId >= 0) {
+ if (matchModel.getC1_id() != null && matchModel.getC1_id().getId() == card.combId)
+ cardboardModel.setComb(matchModel.getC1_id());
+ if (matchModel.getC2_id() != null && matchModel.getC2_id().getId() == card.combId)
+ cardboardModel.setComb(matchModel.getC2_id());
+ } else {
+ if (matchModel.getC1_guest() != null && matchModel.getC1_guest()
+ .getId() == card.combId * -1)
+ cardboardModel.setGuestComb(matchModel.getC1_guest());
+ if (matchModel.getC2_guest() != null && matchModel.getC2_guest()
+ .getId() == card.combId * -1)
+ cardboardModel.setGuestComb(matchModel.getC2_guest());
+ }
+
+ if (cardboardModel.getComb() == null && cardboardModel.getGuestComb() == null)
+ return Uni.createFrom().nullItem();
+ return Panache.withTransaction(() -> cardboardRepository.persist(cardboardModel));
+ }))
+ .call(model -> SSCardboard.sendCardboard(connection, CardboardEntity.fromModel(model)))
+ .replaceWithVoid();
+ }
+
+ @WSReceiver(code = "getCardboardWithoutThis", permission = PermLevel.VIEW)
+ public Uni getCardboardWithoutThis(WebSocketConnection connection, Long matchId) {
+ return getById(matchId, connection)
+ .chain(matchModel -> cardboardRepository.list("compet = ?1 AND match != ?2", matchModel.getCategory().getCompet(), matchModel)
+ .map(models -> {
+ CardboardAllMatch out = new CardboardAllMatch();
+
+ models.stream().filter(c -> (matchModel.getC1_id() != null
+ && Objects.equals(c.getComb(), matchModel.getC1_id()))
+ || (matchModel.getC1_guest() != null
+ && Objects.equals(c.getGuestComb(), matchModel.getC1_guest())))
+ .forEach(c -> {
+ out.c1_yellow += c.getYellow();
+ out.c1_red += c.getRed();
+ });
+
+ models.stream().filter(c -> (matchModel.getC2_id() != null
+ && Objects.equals(c.getComb(), matchModel.getC2_id()))
+ || (matchModel.getC2_guest() != null
+ && Objects.equals(c.getGuestComb(), matchModel.getC2_guest())))
+ .forEach(c -> {
+ out.c2_yellow += c.getYellow();
+ out.c2_red += c.getRed();
+ });
+
+ return out;
+ }));
+ }
+
+ @RegisterForReflection
+ public record SendCardboard(long matchId, long combId, int yellow, int red) {
+ }
+
+ @Data
+ @RegisterForReflection
+ public static class CardboardAllMatch {
+ int c1_yellow = 0;
+ int c1_red = 0;
+ int c2_yellow = 0;
+ int c2_red = 0;
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java
new file mode 100644
index 0000000..816cbea
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java
@@ -0,0 +1,229 @@
+package fr.titionfire.ffsaf.ws.recv;
+
+import fr.titionfire.ffsaf.data.model.CategoryModel;
+import fr.titionfire.ffsaf.data.model.MatchModel;
+import fr.titionfire.ffsaf.data.model.TreeModel;
+import fr.titionfire.ffsaf.data.repository.CategoryRepository;
+import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
+import fr.titionfire.ffsaf.data.repository.MatchRepository;
+import fr.titionfire.ffsaf.data.repository.TreeRepository;
+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.TreeNode;
+import fr.titionfire.ffsaf.ws.PermLevel;
+import fr.titionfire.ffsaf.ws.send.SSCategorie;
+import io.quarkus.hibernate.reactive.panache.Panache;
+import io.quarkus.hibernate.reactive.panache.common.WithSession;
+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 lombok.Data;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@WithSession
+@ApplicationScoped
+@RegisterForReflection
+public class RCategorie {
+ //private static final Logger LOGGER = Logger.getLogger(RCategorie.class);
+
+ @Inject
+ CategoryRepository categoryRepository;
+
+ @Inject
+ CompetitionRepository competitionRepository;
+
+ @Inject
+ MatchRepository matchRepository;
+
+ @Inject
+ TreeRepository treeRepository;
+
+ private Uni getById(long id, WebSocketConnection connection) {
+ return categoryRepository.findById(id)
+ .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");
+ }));
+ }
+
+ @WSReceiver(code = "getAllCategory", permission = PermLevel.VIEW)
+ public Uni> getAllCategory(WebSocketConnection connection, Object o) {
+ return categoryRepository.list("compet.uuid", connection.pathParam("uuid"))
+ .map(category -> category.stream().map(JustCategorie::from).toList());
+ }
+
+ @WSReceiver(code = "getFullCategory", permission = PermLevel.VIEW)
+ public Uni getFullCategory(WebSocketConnection connection, Long id) {
+ FullCategory fullCategory = new FullCategory();
+
+ return getById(id, connection)
+ .invoke(cat -> {
+ fullCategory.setId(cat.getId());
+ fullCategory.setName(cat.getName());
+ fullCategory.setLiceName(cat.getLiceName());
+ fullCategory.setType(cat.getType());
+ })
+ .call(cat -> Mutiny.fetch(cat.getMatchs())
+ .map(matchModels -> matchModels.stream().filter(o -> o.getCategory_ord() >= 0)
+ .map(MatchEntity::fromModel).toList())
+ .invoke(fullCategory::setMatches))
+ .call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
+ .map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
+ .invoke(fullCategory::setTrees))
+ .map(__ -> fullCategory);
+ }
+
+ @WSReceiver(code = "createCategory", permission = PermLevel.ADMIN)
+ public Uni createCategory(WebSocketConnection connection, JustCategorie categorie) {
+ return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
+ .chain(cm -> {
+ CategoryModel categoryModel = new CategoryModel();
+ categoryModel.setName(categorie.name);
+ categoryModel.setCompet(cm);
+ categoryModel.setMatchs(new ArrayList<>());
+ categoryModel.setTree(new ArrayList<>());
+ categoryModel.setType(categorie.type);
+ categoryModel.setLiceName(categorie.liceName);
+
+ return categoryRepository.create(categoryModel);
+ })
+ .call(cat -> SSCategorie.sendAddCategory(connection, cat))
+ .map(CategoryModel::getId);
+ }
+
+ @WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN)
+ public Uni updateCategory(WebSocketConnection connection, JustCategorie categorie) {
+ return getById(categorie.id, connection)
+ .chain(cat -> {
+ cat.setName(categorie.name);
+ cat.setLiceName(categorie.liceName);
+ cat.setType(categorie.type);
+ return Panache.withTransaction(() -> categoryRepository.persist(cat));
+ })
+ .call(cat -> {
+ Uni uni = Uni.createFrom().nullItem();
+ if ((categorie.type() & 1) == 0)
+ uni = uni.chain(__ -> matchRepository.delete("category = ?1 AND category_ord >= 0", cat));
+ if ((categorie.type() & 2) == 0) {
+ uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId()))
+ .chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat));
+ }
+ return uni;
+ })
+ .call(cat -> SSCategorie.sendCategory(connection, cat))
+ .replaceWithVoid();
+ }
+
+ private Uni getOrCreateTreeNode(Long id, Long categorieId, int level) {
+ if (id == null) {
+ TreeModel treeModel = new TreeModel();
+ treeModel.setCategory(categorieId);
+ treeModel.setLevel(level);
+
+ return Panache.withTransaction(() -> treeRepository.persistAndFlush(treeModel));
+ } else {
+ return Panache.withTransaction(
+ () -> treeRepository.find("match.id = ?1", id).firstResult()
+ .invoke(t -> t.setLevel(level))
+ .chain(t -> treeRepository.persist(t)));
+ }
+ }
+
+ private Uni updateNode(TreeNode current, TreeModel currentTreeModel, CategoryModel category) {
+ return Uni.createFrom().item(currentTreeModel)
+ .chain(t -> {
+ if (current.getData() != null)
+ return Uni.createFrom().item(t);
+
+ MatchModel matchModel = new MatchModel();
+ matchModel.setCategory(category);
+ matchModel.setCategory_ord(-42);
+ matchModel.setEnd(false);
+
+ return matchRepository.create(matchModel).onItem()
+ .invoke(t::setMatch)
+ .chain(__ -> treeRepository.persistAndFlush(t));
+ })
+ .call(t -> {
+ if (current.getLeft() == null)
+ return Uni.createFrom().item(t);
+
+ return getOrCreateTreeNode(current.getLeft().getData(), category.getId(), 0)
+ .invoke(t::setLeft)
+ .call(treeModel -> updateNode(current.getLeft(), treeModel, category));
+ })
+ .call(t -> {
+ if (current.getRight() == null)
+ return Uni.createFrom().item(t);
+
+ return getOrCreateTreeNode(current.getRight().getData(), category.getId(), 0)
+ .invoke(t::setRight)
+ .call(treeModel -> updateNode(current.getRight(), treeModel, category));
+ })
+ .chain(t -> treeRepository.persist(t))
+ .replaceWithVoid();
+ }
+
+
+ @WSReceiver(code = "updateTrees", permission = PermLevel.ADMIN)
+ public Uni updateTrees(WebSocketConnection connection, TreeUpdate data) {
+ return getById(data.categoryId, connection)
+ .call(cat -> treeRepository.update("level = -1, left = NULL, right = NULL WHERE category = ?1",
+ cat.getId()))
+ .call(cat -> {
+ Uni> uni = Uni.createFrom().voidItem();
+
+ for (int i = 0; i < data.trees().size(); i++) {
+ TreeNode current = data.trees().get(i);
+
+ int finalI = i;
+ uni = uni.chain(() -> getOrCreateTreeNode(current.getData(), cat.getId(), finalI + 1)
+ .call(treeModel -> updateNode(current, treeModel, cat)));
+
+ }
+ Uni> finalUni = uni;
+ return Panache.withTransaction(() -> finalUni);
+ })
+ .call(cat -> treeRepository.list("category = ?1 AND level = -1", cat.getId())
+ .map(l -> l.stream().map(o -> o.getMatch().getId()).toList())
+ .call(__ -> treeRepository.delete("category = ?1 AND level = -1", cat.getId()))
+ .call(ids -> matchRepository.delete("id IN ?1", ids)))
+ .call(__ -> treeRepository.flush())
+ .call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
+ .map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
+ .chain(trees -> SSCategorie.sendTreeCategory(connection, trees)))
+ .replaceWithVoid();
+ }
+
+ @RegisterForReflection
+ public record JustCategorie(long id, String name, int type, String liceName) {
+ public static JustCategorie from(CategoryModel m) {
+ return new JustCategorie(m.getId(), m.getName(), m.getType(), m.getLiceName());
+ }
+ }
+
+ @RegisterForReflection
+ public record TreeUpdate(long categoryId, List> trees) {
+ }
+
+ @Data
+ @RegisterForReflection
+ public static class FullCategory {
+ long id;
+ String name;
+ int type;
+ String liceName;
+ List trees = null;
+ List matches;
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java
new file mode 100644
index 0000000..c184f3d
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RMatch.java
@@ -0,0 +1,346 @@
+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) {
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java
new file mode 100644
index 0000000..c1f7f7c
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RRegister.java
@@ -0,0 +1,37 @@
+package fr.titionfire.ffsaf.ws.recv;
+
+import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
+import fr.titionfire.ffsaf.domain.entity.CombEntity;
+import fr.titionfire.ffsaf.ws.PermLevel;
+import io.quarkus.hibernate.reactive.panache.common.WithSession;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import io.quarkus.websockets.next.WebSocketConnection;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@WithSession
+@ApplicationScoped
+@RegisterForReflection
+public class RRegister {
+
+ @Inject
+ CompetitionRepository competitionRepository;
+
+ @WSReceiver(code = "getRegister", permission = PermLevel.TABLE)
+ public Uni> getRegister(WebSocketConnection connection, Object o) {
+ return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
+ .call(cm -> Mutiny.fetch(cm.getInsc()))
+ .call(cm -> Mutiny.fetch(cm.getGuests()))
+ .map(cm -> {
+ ArrayList combEntities = new ArrayList<>();
+ combEntities.addAll(cm.getInsc().stream().map(CombEntity::fromModel).toList());
+ combEntities.addAll(cm.getGuests().stream().map(CombEntity::fromModel).toList());
+ return combEntities;
+ });
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/WSReceiver.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/WSReceiver.java
new file mode 100644
index 0000000..c901563
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/WSReceiver.java
@@ -0,0 +1,17 @@
+package fr.titionfire.ffsaf.ws.recv;
+
+import fr.titionfire.ffsaf.ws.PermLevel;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@RegisterForReflection
+@Retention(RetentionPolicy.RUNTIME)
+public @interface WSReceiver {
+
+ String code();
+
+ PermLevel permission() default PermLevel.VIEW;
+
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/JsonUni.java b/src/main/java/fr/titionfire/ffsaf/ws/send/JsonUni.java
new file mode 100644
index 0000000..abe91ac
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/send/JsonUni.java
@@ -0,0 +1,33 @@
+package fr.titionfire.ffsaf.ws.send;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import io.smallrye.mutiny.Uni;
+import io.smallrye.mutiny.unchecked.Unchecked;
+
+import java.util.function.Function;
+
+import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
+
+@RegisterForReflection
+public class JsonUni {
+
+ private final Class clazz;
+ private final Function super T, Uni>> mapper;
+
+ public JsonUni(Class clazz, Function super T, Uni>> mapper) {
+ this.clazz = clazz;
+ this.mapper = mapper;
+ }
+
+ public Uni> castAndChain(JsonNode message) throws JsonProcessingException {
+ return Uni.createFrom().item(MAPPER.treeToValue(message, clazz)).chain(mapper);
+ }
+
+ public Uni> castAndError(JsonNode message) throws JsonProcessingException {
+ return Uni.createFrom().item((T) null).invoke(Unchecked.consumer(__ -> {
+ throw new WSClientError(MAPPER.treeToValue(message, String.class));
+ })).chain(mapper);
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java b/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java
new file mode 100644
index 0000000..9197d2e
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java
@@ -0,0 +1,53 @@
+package fr.titionfire.ffsaf.ws.send;
+
+import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
+import fr.titionfire.ffsaf.data.model.RegisterModel;
+import fr.titionfire.ffsaf.domain.entity.CombEntity;
+import fr.titionfire.ffsaf.net2.MessageType;
+import fr.titionfire.ffsaf.ws.CompetitionWS;
+import fr.titionfire.ffsaf.ws.MessageOut;
+import fr.titionfire.ffsaf.ws.PermLevel;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import io.quarkus.websockets.next.OpenConnections;
+import io.quarkus.websockets.next.UserData;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.List;
+import java.util.UUID;
+
+@ApplicationScoped
+@RegisterForReflection
+public class SRegister {
+
+ @SuppressWarnings("CdiInjectionPointsInspection")
+ @Inject
+ OpenConnections connections;
+
+ public Uni sendRegister(String uuid, RegisterModel registerModel) {
+ return send(uuid, "sendRegister", CombEntity.fromModel(registerModel));
+ }
+
+ public Uni sendRegister(String uuid, CompetitionGuestModel model) {
+ return send(uuid, "sendRegister", CombEntity.fromModel(model));
+ }
+
+ public Uni sendRegisterRemove(String uuid, Long combId) {
+ return send(uuid, "sendRegisterRemove", combId);
+ }
+
+ public Uni send(String uuid, String code, Object data) {
+ List> queue = connections.findByEndpointId(CompetitionWS.class.getCanonicalName()).stream()
+ .filter(c -> c.pathParam("uuid").equals(uuid) && PermLevel.valueOf(
+ c.userData().get(UserData.TypedKey.forString("prem"))).ordinal() >= PermLevel.TABLE.ordinal())
+ .map(c -> c.sendText(
+ new MessageOut(UUID.randomUUID(), code, MessageType.NOTIFY, data)))
+ .toList();
+
+ if (queue.isEmpty())
+ return Uni.createFrom().voidItem();
+ return Uni.join().all(queue).andCollectFailures().replaceWithVoid();
+ }
+
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/SSCardboard.java b/src/main/java/fr/titionfire/ffsaf/ws/send/SSCardboard.java
new file mode 100644
index 0000000..31963b1
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/send/SSCardboard.java
@@ -0,0 +1,13 @@
+package fr.titionfire.ffsaf.ws.send;
+
+import fr.titionfire.ffsaf.domain.entity.CardboardEntity;
+import fr.titionfire.ffsaf.ws.CompetitionWS;
+import io.quarkus.websockets.next.WebSocketConnection;
+import io.smallrye.mutiny.Uni;
+
+public class SSCardboard {
+
+ public static Uni sendCardboard(WebSocketConnection connection, CardboardEntity cardboardEntity) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendCardboard", cardboardEntity);
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/SSCategorie.java b/src/main/java/fr/titionfire/ffsaf/ws/send/SSCategorie.java
new file mode 100644
index 0000000..23162b9
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/send/SSCategorie.java
@@ -0,0 +1,33 @@
+package fr.titionfire.ffsaf.ws.send;
+
+import fr.titionfire.ffsaf.data.model.CategoryModel;
+import fr.titionfire.ffsaf.domain.entity.TreeEntity;
+import fr.titionfire.ffsaf.ws.CompetitionWS;
+import fr.titionfire.ffsaf.ws.recv.RCategorie;
+import io.quarkus.websockets.next.WebSocketConnection;
+import io.smallrye.mutiny.Uni;
+
+import java.util.List;
+
+public class SSCategorie {
+
+ public static Uni sendAddCategory(WebSocketConnection connection, CategoryModel category) {
+ return SSCategorie.sendAddCategory(connection, RCategorie.JustCategorie.from(category));
+ }
+
+ public static Uni sendAddCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory", justCategorie);
+ }
+
+ public static Uni sendCategory(WebSocketConnection connection, CategoryModel category) {
+ return SSCategorie.sendCategory(connection, RCategorie.JustCategorie.from(category));
+ }
+
+ public static Uni sendCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", justCategorie);
+ }
+
+ public static Uni sendTreeCategory(WebSocketConnection connection, List treeEntities) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/SSMatch.java b/src/main/java/fr/titionfire/ffsaf/ws/send/SSMatch.java
new file mode 100644
index 0000000..b66911d
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/send/SSMatch.java
@@ -0,0 +1,32 @@
+package fr.titionfire.ffsaf.ws.send;
+
+import fr.titionfire.ffsaf.domain.entity.MatchEntity;
+import fr.titionfire.ffsaf.ws.CompetitionWS;
+import fr.titionfire.ffsaf.ws.recv.RMatch;
+import io.quarkus.websockets.next.WebSocketConnection;
+import io.smallrye.mutiny.Uni;
+
+import java.util.List;
+
+public class SSMatch {
+
+ public static Uni sendMatch(WebSocketConnection connection, MatchEntity matchEntity) {
+ return SSMatch.sendMatch(connection, List.of(matchEntity));
+ }
+
+ public static Uni sendMatch(WebSocketConnection connection, List matchEntities) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatch", matchEntities);
+ }
+
+ public static Uni sendMatchOrder(WebSocketConnection connection, RMatch.MatchOrder matchOrder) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatchOrder", matchOrder);
+ }
+
+ public static Uni sendDeleteMatch(WebSocketConnection connection, Long l) {
+ return SSMatch.sendDeleteMatch(connection, List.of(l));
+ }
+
+ public static Uni sendDeleteMatch(WebSocketConnection connection, List longs) {
+ return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDeleteMatch", longs);
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/WSClientError.java b/src/main/java/fr/titionfire/ffsaf/ws/send/WSClientError.java
new file mode 100644
index 0000000..b654569
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/ws/send/WSClientError.java
@@ -0,0 +1,13 @@
+package fr.titionfire.ffsaf.ws.send;
+
+import java.io.IOException;
+import java.io.Serial;
+
+public class WSClientError extends IOException {
+ @Serial
+ private static final long serialVersionUID = -3790479241838684450L;
+
+ public WSClientError(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 984ee85..0d7d3e6 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -45,6 +45,9 @@ siren-api.key=siren-ap
quarkus.rest-client."fr.titionfire.ffsaf.rest.client.SirenService".url=https://data.siren-api.fr/
quarkus.rest-client."fr.titionfire.ffsaf.rest.client.StateIdService".url=https://www.data-asso.fr/api/
+quarkus.websockets-next.server.auto-ping-interval=PT10s
+quarkus.websockets-next.client.connection-idle-timeout=PT30s
+
#Login
quarkus.oidc.token-state-manager.split-tokens=true
quarkus.oidc.token.refresh-expired=true
diff --git a/src/main/resources/lang/String.properties b/src/main/resources/lang/String.properties
new file mode 100644
index 0000000..7e87f97
--- /dev/null
+++ b/src/main/resources/lang/String.properties
@@ -0,0 +1,16 @@
+filtre.all=--tout--
+no.licence=Non licenci\u00E9
+
+
+# Categories
+Cat.SUPER_MINI=Super Mini
+Cat.MINI_POUSSIN=Mini Poussin
+Cat.POUSSIN= Poussin
+Cat.BENJAMIN=Benjamin
+Cat.MINIME=Minime
+Cat.CADET=Cadet
+Cat.JUNIOR=Junior
+Cat.SENIOR1=Senior 1
+Cat.SENIOR2=Senior 2
+Cat.VETERAN1=V\u00E9t\u00E9ran 1
+Cat.VETERAN2=V\u00E9t\u00E9ran 2
\ No newline at end of file
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
index 16586f2..d17ca9d 100644
--- a/src/main/webapp/index.html
+++ b/src/main/webapp/index.html
@@ -8,12 +8,8 @@
-
+
-
+