Compare commits

...

50 Commits

Author SHA1 Message Date
09a51edd5f wip: public screen 2025-12-10 00:08:14 +01:00
354fbfede9 feat: add flag svg 2025-12-09 22:49:25 +01:00
ef5b707697 feat: cm table next tree match + lock scores 2025-12-07 19:45:09 +01:00
489bfeb354 feat: cm table matches & scores 2025-12-07 15:12:48 +01:00
bb901392fc feat: cm admin poule editor 2025-12-02 15:58:20 +01:00
6f61db6817 feat: cm admin poule selector 2025-11-30 21:52:05 +01:00
00701fb874 feat: cm admin comb selector 2025-11-30 19:16:32 +01:00
936392f8bd feat: add guest comb to competition register form 2025-11-30 18:38:18 +01:00
f6d4bb0fe4 wip: cm admin comb selector 2025-11-26 12:37:32 +01:00
160c7d59e3 fix: move data model to good package 2025-11-25 18:43:40 +01:00
9689201a8c feat: add anti-blink timer 2025-11-25 17:24:21 +01:00
083d72fbfa feat: update Bootstrap 2025-11-25 17:18:25 +01:00
1c7466d883 fix: categorie perm 2025-11-25 17:17:48 +01:00
c9c8c8536d feat: add categorie info in CM 2025-11-24 19:28:04 +01:00
b1bcf75e56 feat: categorie on CM 2025-11-24 15:56:15 +01:00
b78b3f005b feat: implement ws 2025-11-21 14:06:46 +01:00
a83088387b wip: implement ws base 2025-11-20 23:39:02 +01:00
22fa896ee0 feat: competition result 2025-11-19 17:53:11 +01:00
f8976deb91 feat: re-add siret/rna api 2025-11-19 16:47:14 +01:00
eb9badb4a1 fix: error on loading logo on maps 2025-11-19 16:47:14 +01:00
661fcdb16b fix: remove siret/rna api 2025-11-19 16:47:14 +01:00
b2ad633b21 feat: membre list filter history 2025-11-19 16:47:13 +01:00
1b5bf8ba6c feat: add categorie filter membre search 2025-11-19 16:47:13 +01:00
bf75d9d036 feat: make user-friendly cat name 2025-11-19 16:47:13 +01:00
9457c5749a feat: add ordering for member page 2025-11-19 16:47:13 +01:00
c7f56881cd feat: upgrade club groupe name in kc when club is rename 2025-11-19 16:47:13 +01:00
aebcd62aa9 feat: upgrade aff find to user all id (Rna, siret, siren) 2025-11-19 16:47:13 +01:00
c2eecf4906 fix: certifDate NaN all membre export 2025-11-19 16:47:13 +01:00
87b3bc12e0 fix: null licence on import 2025-11-19 16:47:13 +01:00
3d3d63e58c fix: add more log to import 2025-11-19 16:47:13 +01:00
81c115c655 fix: empty mail on import 2025-11-19 16:47:13 +01:00
a7ba1d16a4 feat: remove kc new account mail 2025-11-19 16:47:13 +01:00
645949a2f6 feat: add cache to getAssoInfo 2025-11-19 16:47:13 +01:00
beb40db1b1 feat: merge rna and siret fields 2025-11-19 16:47:13 +01:00
61a4af6ff1 fix: club delete on RegisterModel 2025-11-19 16:47:13 +01:00
d9fc68298c feat: allow empty mail 2025-11-19 16:47:13 +01:00
fccea5bf6a fix: aff renew select length 2025-11-19 16:47:13 +01:00
ee476cd0e2 feat: remove saison selection on aff req 2025-11-19 16:47:13 +01:00
b320d7db37 fix: affiliation ok login msg 2025-11-19 16:47:13 +01:00
80fef98e07 feat: add email tooltip 2025-11-19 16:47:13 +01:00
7e80703c04 feat: add re-login message 2025-11-19 16:47:10 +01:00
cc4a3e4e06 fix: null email on import 2025-11-19 16:46:46 +01:00
9e28356f2c fix: log detail 2025-11-19 16:46:46 +01:00
77d66813c7 fix: log detail 2025-11-19 16:46:46 +01:00
7625da1d4b feat: add aff req log detail
fix: membre import null email filter
2025-11-19 16:46:46 +01:00
4262845074 fix: typo 2025-11-19 16:46:46 +01:00
b84e10de44 feat: keep log 2025-11-19 16:46:46 +01:00
ac6563ac95 fix: club order 2025-11-19 16:46:46 +01:00
baf57c3464 fix: log message length 2025-11-19 16:46:46 +01:00
d1c7f37a94 feat: competition result 2025-09-03 18:56:58 +02:00
375 changed files with 7451 additions and 462 deletions

View File

@ -76,6 +76,7 @@ jobs:
key: ${{ secrets.SSH_KEY }}
script: |
cd ${{ secrets.TARGET_DIR }}
docker logs ffsaf > "log/ffsaf_logs_$(date +"%Y-%m-%d_%H-%M-%S").log" 2>&1
docker stop ffsaf
docker rm ffsaf
docker compose up --build -d ffsaf

View File

@ -133,6 +133,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mailer</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -21,8 +21,7 @@ public class AffiliationRequestModel {
Long id;
String name;
long siret;
String RNA;
String state_id;
String address;
String contact;

View File

@ -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;
}

View File

@ -42,4 +42,6 @@ public class CategoryModel {
List<TreeModel> tree;
Integer type;
String liceName = "1";
}

View File

@ -55,11 +55,8 @@ public class ClubModel implements LoggableModel {
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
String address;
@Schema(description = "RNA du club", example = "W123456789")
String RNA;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long SIRET;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
String StateId;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation;

View File

@ -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;
}
}

View File

@ -55,10 +55,18 @@ public class CompetitionModel {
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
List<RegisterModel> insc;
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
List<CompetitionGuestModel> guests = new ArrayList<>();
List<Long> banMembre = new ArrayList<>();
String owner;
List<String> admin = new ArrayList<>();
@Column(name = "table_")
List<String> table = new ArrayList<>();
String data1;
String data2;
String data3;

View File

@ -30,8 +30,10 @@ public class LogModel {
Long target_id;
@Column(columnDefinition = "TEXT")
String target_name;
@Column(columnDefinition = "TEXT")
String message;
public enum ActionType {

View File

@ -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,41 @@ public class MatchModel {
boolean isEnd = true;
Date date = null;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
List<ScoreEmbeddable> scores = new ArrayList<>();
char poule = 'A';
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "match", referencedColumnName = "id")
List<CardboardModel> cardboard = new ArrayList<>();
public String getC1Name() {
if (c1_id == null)
return c1_guest.fname + " " + c1_guest.lname;
return c1_id.fname + " " + c1_id.lname;
}
public String getC2Name() {
if (c2_id == null)
return c2_guest.fname + " " + c2_guest.lname;
return c2_id.fname + " " + c2_id.lname;
}
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;
}
}

View File

@ -82,4 +82,22 @@ public class MembreModel implements LoggableModel {
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Membre;
}
@Override
public String toString() {
return "MembreModel{" +
"id=" + id +
", userId='" + userId + '\'' +
", lname='" + lname + '\'' +
", fname='" + fname + '\'' +
", categorie=" + categorie +
", genre=" + genre +
", licence=" + licence +
", country='" + country + '\'' +
", birth_date=" + birth_date +
", email='" + email + '\'' +
", role=" + role +
", grade_arbitrage=" + grade_arbitrage +
'}';
}
}

View File

@ -8,6 +8,8 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Getter
@Setter
@ -38,6 +40,7 @@ public class RegisterModel {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club")
@OnDelete(action = OnDeleteAction.SET_NULL)
ClubModel club = null;
@Column(nullable = false, columnDefinition = "boolean default false")
@ -53,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;
}
}

View File

@ -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<TreeModel> flat() {
List<TreeModel> out = new ArrayList<>();
this.flat(out);
return out;
}
private void flat(List<TreeModel> out) {
out.add(this);
if (this.right != null)
this.right.flat(out);
if (this.left != null)
this.left.flat(out);
}
}

View File

@ -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<CategoryModel, Long> {
public Uni<CategoryModel> create(CategoryModel categoryModel) {
categoryModel.setSystem(CompetitionSystem.INTERNAL);
return Panache.withTransaction(() -> this.persist(categoryModel)
.invoke(categoryModel1 -> categoryModel1.setSystemId(categoryModel1.getId())))
.chain(this::persist);
}
}

View File

@ -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<CompetitionGuestModel, Long> {
}

View File

@ -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<MatchModel, Long> {
public Uni<MatchModel> create(MatchModel matchModel) {
matchModel.setSystem(CompetitionSystem.INTERNAL);
return Panache.withTransaction(() -> this.persistAndFlush(matchModel)
.invoke(matchModel1 -> matchModel1.setSystemId(matchModel1.getId())))
.chain(this::persist);
}
public Uni<Void> create(List<MatchModel> 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);
}
}

View File

@ -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<TreeModel, Long> {
@WithTransaction
public Uni<Boolean> deleteTree(TreeModel entity) {
Uni<Boolean> 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()));
}
}

View File

@ -0,0 +1,23 @@
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().getId(), model.getMatch().getId(), model.getCompet().getId(),
model.getRed(), model.getYellow());
}
}

View File

@ -22,8 +22,7 @@ public class ClubEntity {
private String training_location;
private String training_day_time;
private String contact_intern;
private String RNA;
private Long SIRET;
private String StateId;
private Long no_affiliation;
private boolean international;
@ -41,8 +40,7 @@ public class ClubEntity {
.training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern())
.RNA(model.getRNA())
.SIRET(model.getSIRET())
.StateId(model.getStateId())
.no_affiliation(model.getNo_affiliation())
.international(model.isInternational())
.build();

View File

@ -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;
Long club;
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().getId(),
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().getId(),
registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(),
model.getCountry(), registerModel.getOverCategory(), registerModel.getWeight());
}
}

View File

@ -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<ScoreEmbeddable> scores;
private char poule;
private List<CardboardEntity> 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;
}
}

View File

@ -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());
}
}

View File

@ -2,6 +2,8 @@ package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
@ -21,15 +23,19 @@ import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class AffiliationService {
private static final Logger LOGGER = Logger.getLogger(AffiliationService.class);
@Inject
CombRepository combRepository;
@ -58,6 +64,12 @@ public class AffiliationService {
@Inject
LoggerService ls;
@RestClient
StateIdService stateIdService;
@RestClient
SirenService sirenService;
@ConfigProperty(name = "upload_dir")
String media;
@ -71,6 +83,8 @@ public class AffiliationService {
public Uni<AffiliationRequestModel> pre_save(AffiliationRequestForm form, boolean unique) {
AffiliationRequestModel affModel = form.toModel();
int currentSaison = Utils.getSaison();
List<String> out = new ArrayList<>();
out.add(affModel.getState_id());
return Uni.createFrom().item(affModel)
.invoke(Unchecked.consumer(model -> {
@ -78,14 +92,26 @@ public class AffiliationService {
throw new DBadRequestException("Saison non valid");
}
}))
.chain(() -> repositoryRequest.count("siret = ?1 and saison = ?2", affModel.getSiret(),
affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
.chain(() -> clubRepository.find("SIRET = ?1", affModel.getSiret()).firstResult().chain(club ->
.chain(() -> ((affModel.getState_id().charAt(0) == 'W') ? stateIdService.get_rna(
affModel.getState_id()) : sirenService.get_unite(affModel.getState_id())
.chain(stateIdService::getAssoDataFromUnit)).onItem().transform(o -> {
if (o.getRna() != null && !o.getRna().isBlank())
out.add(o.getRna());
if (o.getSiren() != null && !o.getSiren().isBlank())
out.add(o.getSiren());
if (o.getIdentite().getSiret_siege() != null && !o.getIdentite().getSiret_siege().isBlank())
out.add(o.getIdentite().getSiret_siege());
return out;
}).onFailure().recoverWithItem(out)
.chain(a -> repositoryRequest.count("state_id IN ?1 and saison = ?2",
out, affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
)
.chain(() -> clubRepository.find("StateId IN ?1", out).firstResult().chain(club ->
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) {
@ -122,7 +148,6 @@ public class AffiliationService {
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(origine -> {
origine.setName(model.getName());
origine.setRNA(model.getRNA());
origine.setAddress(model.getAddress());
origine.setContact(model.getContact());
origine.setM1_lname(model.getM1_lname());
@ -146,6 +171,9 @@ public class AffiliationService {
}
public Uni<String> save(AffiliationRequestForm form) {
LOGGER.debug("Affiliation Request Created");
LOGGER.debug(form.toString());
// noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher
return pre_save(form, true)
.chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model)))
@ -169,12 +197,14 @@ public class AffiliationService {
}
public Uni<?> saveAdmin(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Saved");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> {
model.setName(form.getName());
model.setSiret(form.getSiret());
model.setRNA(form.getRna());
model.setState_id(form.getState_id());
model.setAddress(form.getAddress());
model.setContact(form.getContact());
@ -259,7 +289,9 @@ public class AffiliationService {
}).call(m -> Panache.withTransaction(() -> combRepository.persist(m)));
}
})
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId()) :
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId())
.onFailure().invoke(t -> LOGGER.warnf("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull() :
keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId()))
.call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage()))
.call(userId -> keycloakService.setEmail(userId, m.getEmail())))
@ -267,19 +299,24 @@ public class AffiliationService {
.call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ?
Uni.createFrom().nullItem() :
Panache.withTransaction(() -> licenceRepository.persist(
new LicenceModel(null, m, club.getId(), saison, null, true, false)))
new LicenceModel(null, m, club.getId(), saison, null, true, false)))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(),
licenceModel))));
}
public Uni<?> accept(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Accepted");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req ->
clubRepository.find("SIRET = ?1", form.getSiret()).firstResult()
clubRepository.find("StateId = ?1", form.getState_id()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
.call(club -> setMembre(form.new Member(1), club, req.getSaison())
.call(__ -> setMembre(form.new Member(2), club, req.getSaison())
.call(club -> setMembre(form.new Member(1), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(__ -> setMembre(form.new Member(2), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(___ -> setMembre(form.new Member(3), club, req.getSaison()))))
.onItem()
.invoke(model -> Uni.createFrom()
@ -298,13 +335,13 @@ public class AffiliationService {
}
private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) {
LOGGER.debug("New Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
ClubModel club = new ClubModel();
club.setName(form.getName());
club.setCountry("FR");
club.setSIRET(form.getSiret());
club.setRNA(form.getRna());
club.setStateId(form.getState_id());
club.setAddress(form.getAddress());
club.setContact_intern(form.getContact());
club.setAffiliations(new ArrayList<>());
@ -336,17 +373,24 @@ public class AffiliationService {
}
private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) {
AtomicBoolean nameChange = new AtomicBoolean(false);
LOGGER.debug("Old Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
club.setName(form.getName());
if (!form.getName().equals(club.getName())) {
club.setName(form.getName());
nameChange.set(true);
}
club.setCountry("FR");
club.setSIRET(form.getSiret());
club.setRNA(form.getRna());
club.setStateId(form.getState_id());
club.setAddress(form.getAddress());
club.setContact_intern(form.getContact());
return Panache.withTransaction(() -> clubRepository.persist(club)
.chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison())))
.chain(() -> repositoryRequest.delete(model)));
.chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison())))
.chain(() -> repositoryRequest.delete(model)))
.call(() -> nameChange.get() ? keycloakService.updateGroupFromClub(
club) // update group in keycloak
: Uni.createFrom().nullItem());
})
.map(__ -> club);
}
@ -354,7 +398,7 @@ public class AffiliationService {
public Uni<SimpleReqAffiliation> getRequest(long id) {
return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel)
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.call(out -> clubRepository.find("SIRET = ?1", out.getSiret()).firstResult().invoke(c -> {
.call(out -> clubRepository.find("StateId = ?1", out.getStateId()).firstResult().invoke(c -> {
if (c != null) {
out.setClub(c.getId());
out.setClub_name(c.getName());
@ -367,7 +411,7 @@ public class AffiliationService {
public Uni<List<SimpleAffiliation>> getCurrentSaisonAffiliation() {
return repositoryRequest.list("saison = ?1 or saison = ?1 + 1", Utils.getSaison())
.map(models -> models.stream()
.map(model -> new SimpleAffiliation(model.getId() * -1, model.getSiret(), model.getSaison(),
.map(model -> new SimpleAffiliation(model.getId() * -1, model.getState_id(), model.getSaison(),
false)).toList())
.chain(aff -> repository.list("saison = ?1", Utils.getSaison())
.map(models -> models.stream().map(SimpleAffiliation::fromModel).toList())
@ -379,9 +423,9 @@ public class AffiliationService {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.chain(model -> repositoryRequest.list("siret = ?1", model.getSIRET())
.chain(model -> repositoryRequest.list("state_id = ?1", model.getStateId())
.map(reqs -> reqs.stream().map(req ->
new SimpleAffiliation(req.getId() * -1, model.getId(), req.getSaison(), false)))
new SimpleAffiliation(req.getId() * -1, model.getStateId(), req.getSaison(), false)))
.map(aff2 -> Stream.concat(aff2,
model.getAffiliations().stream().map(SimpleAffiliation::fromModel)).toList())
);
@ -411,9 +455,9 @@ public class AffiliationService {
return Panache.withTransaction(() -> repository.deleteById(id));
}
public Uni<?> deleteReqAffiliation(long id, String reason) {
public Uni<?> deleteReqAffiliation(long id, String reason, boolean federationAdmin) {
return repositoryRequest.findById(id)
.call(aff -> reactiveMailer.send(
.call(aff -> federationAdmin ? reactiveMailer.send(
Mail.withText(aff.getM1_email(),
"FFSAF - Votre demande d'affiliation a été rejetée.",
String.format(
@ -430,7 +474,7 @@ public class AffiliationService {
""", aff.getName(), reason)
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
.addTo(aff.getM2_email(), aff.getM3_email())
))
) : Uni.createFrom().nullItem())
.chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff)))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/logo"))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/status"));

View File

@ -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<CategoryData> 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<TreeModel> 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<Long, MembreModel> membres = new HashMap<>();
HashMap<String, CompetitionGuestModel> guest = new HashMap<>();
List<MatchModel> match = new ArrayList<>();
List<Long> toRmMatch;
List<TreeModel> 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())));
}
}

View File

@ -32,6 +32,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
@ -193,12 +194,17 @@ public class ClubService {
}
public Uni<String> update(long id, FullClubForm input) {
AtomicBoolean nameChange = new AtomicBoolean(false);
return repository.findById(id).call(m -> Mutiny.fetch(m.getContact()))
.onItem().transformToUni(Unchecked.function(m -> {
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
};
m.setName(input.getName());
if (!input.getName().equals(m.getName())) {
m.setName(input.getName());
nameChange.set(true);
}
m.setCountry(input.getCountry());
m.setInternational(input.isInternational());
@ -211,11 +217,9 @@ public class ClubService {
m.setTraining_day_time(input.getTraining_day_time());
ls.logChange("Contact interne", m.getContact_intern(), input.getContact_intern(), m);
m.setContact_intern(input.getContact_intern());
ls.logChange("N° RNA", m.getRNA(), input.getRna(), m);
m.setRNA(input.getRna());
if (input.getSiret() != null && !input.getSiret().isBlank()) {
ls.logChange("N° SIRET", m.getSIRET(), input.getSiret(), m);
m.setSIRET(Long.parseLong(input.getSiret()));
if (input.getState_id() != null && !input.getState_id().isBlank()) {
ls.logChange("N° SIRET", m.getClubId(), input.getState_id(), m);
m.setStateId(input.getState_id());
}
ls.logChange("Adresse administrative", m.getAddress(), input.getAddress(), m);
m.setAddress(input.getAddress());
@ -230,6 +234,8 @@ public class ClubService {
}
return Panache.withTransaction(() -> repository.persist(m)).call(() -> ls.append());
}))
.call(clubModel -> nameChange.get() ? keycloakService.updateGroupFromClub(clubModel) // update group in keycloak
: Uni.createFrom().nullItem())
.invoke(membreModel -> SReqClub.sendIfNeed(serverCustom.clients,
SimpleClubModel.fromModel(membreModel)))
.map(__ -> "OK");
@ -251,9 +257,8 @@ public class ClubService {
clubModel.setTraining_location(input.getTraining_location());
clubModel.setTraining_day_time(input.getTraining_day_time());
clubModel.setContact_intern(input.getContact_intern());
clubModel.setRNA(input.getRna());
if (input.getSiret() != null && !input.getSiret().isBlank())
clubModel.setSIRET(Long.parseLong(input.getSiret()));
if (input.getState_id() != null && !input.getState_id().isBlank())
clubModel.setStateId(input.getState_id());
clubModel.setAddress(input.getAddress());
try {
@ -300,9 +305,9 @@ public class ClubService {
.call(clubModel -> Mutiny.fetch(clubModel.getAffiliations()))
.invoke(clubModel -> {
data.setName(clubModel.getName());
data.setSiret(clubModel.getSIRET());
data.setRna(clubModel.getRNA());
data.setState_id(clubModel.getStateId());
data.setAddress(clubModel.getAddress());
data.setContact(clubModel.getContact_intern());
data.setSaison(
clubModel.getAffiliations().stream().max(Comparator.comparing(AffiliationModel::getSaison))
.map(AffiliationModel::getSaison).map(i -> Math.min(i + 1, Utils.getSaison() + 1))

View File

@ -89,7 +89,7 @@ public class CompetPermService {
.onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));
Uni<HashMap<Long, String>> none = cacheNoneAccess.getAsync(securityCtx.getSubject(),
k -> competitionRepository.list("system = ?1", CompetitionSystem.NONE)
k -> competitionRepository.list("system = ?1", CompetitionSystem.INTERNAL)
.map(competitionModels -> {
HashMap<Long, String> map = new HashMap<>();
for (CompetitionModel model : competitionModels) {
@ -184,7 +184,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 +219,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<CompetitionModel> 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<CompetitionModel> 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<CompetitionModel> hasTablePerm(SecurityCtx securityCtx, Uni<CompetitionModel> 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 +281,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 +298,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();
}));
}
}

View File

@ -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()))
@ -175,6 +178,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 +188,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 +212,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 +239,18 @@ public class CompetitionService {
public Uni<List<SimpleRegisterComb>> 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<List<SimpleRegisterComb>> 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 +273,44 @@ public class CompetitionService {
public Uni<SimpleRegisterComb> 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 +321,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 +330,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 +385,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<MembreModel> 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 +439,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<Void> 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();
}
@ -528,8 +584,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 +602,8 @@ public class CompetitionService {
public Uni<Response> 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()

View File

@ -85,6 +85,31 @@ public class KeycloakService {
return Uni.createFrom().item(club::getClubId);
}
public Uni<String> updateGroupFromClub(ClubModel club) {
if (club.getClubId() == null) {
return getGroupFromClub(club);
} else {
LOGGER.infof("Updating name of club group %d-%s...", club.getId(), club.getName());
return vertx.getOrCreateContext().executeBlocking(() -> {
GroupRepresentation clubGroup =
keycloak.realm(realm).groups().groups().stream().filter(g -> g.getName().equals("club"))
.findAny()
.orElseThrow(() -> new KeycloakException("Fail to fetch group %s".formatted("club")));
keycloak.realm(realm).groups().group(clubGroup.getId()).getSubGroups(0, 1000, true).stream()
.filter(g -> g.getName().startsWith(club.getId() + "-")).findAny()
.ifPresent(groupRepresentation -> {
groupRepresentation.setName(club.getId() + "-" + club.getName());
keycloak.realm(realm).groups().group(groupRepresentation.getId())
.update(groupRepresentation);
});
return club.getClubId();
}
);
}
}
public Uni<String> getUserFromMember(MembreModel membreModel) {
if (membreModel.getUserId() == null) {
return Uni.createFrom()
@ -199,16 +224,16 @@ public class KeycloakService {
public Uni<String> initCompte(long id) {
return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> {
if (membreModel.getUserId() != null)
throw new KeycloakException("User already linked to the user id=" + id);
if (membreModel.getEmail() == null)
throw new KeycloakException("User email is null");
if (membreModel.getFname() == null || membreModel.getLname() == null)
throw new KeycloakException("User name is null");
})).chain(membreModel -> creatUser(membreModel).chain(user -> {
LOGGER.infof("Set user id %s to membre %s", user.getId(), membreModel.getId());
return membreService.setUserId(membreModel.getId(), user.getId()).map(__ -> user.getId());
}));
if (membreModel.getUserId() != null)
throw new KeycloakException("User already linked to the user id=" + id);
if (membreModel.getEmail() == null)
throw new KeycloakException("User email is null");
if (membreModel.getFname() == null || membreModel.getLname() == null)
throw new KeycloakException("User name is null");
})).chain(membreModel -> creatUser(membreModel).chain(user -> {
LOGGER.infof("Set user id %s to membre %s", user.getId(), membreModel.getId());
return membreService.setUserId(membreModel.getId(), user.getId()).map(__ -> user.getId());
}));
}
private Uni<UserRepresentation> creatUser(MembreModel membreModel) {
@ -231,9 +256,6 @@ public class KeycloakService {
user.setEmail(membreModel.getEmail());
user.setEnabled(true);
user.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
try (Response response = keycloak.realm(realm).users().create(user)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo()
.equals(Response.Status.CONFLICT))
@ -245,13 +267,6 @@ public class KeycloakService {
return getUser(login).orElseThrow(
() -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin)));
})
.call(user -> enabled_email ?
vertx.getOrCreateContext().executeBlocking(() -> {
keycloak.realm(realm).users().get(user.getId())
.executeActionsEmail(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
return null;
}) : Uni.createFrom().nullItem())
.invoke(user -> membreModel.setUserId(user.getId()))
.call(user -> updateRole(user.getId(), List.of("safca_user"), List.of()))
.call(user -> enabled_email ? reactiveMailer.send(
@ -261,14 +276,14 @@ public class KeycloakService {
"""
Bonjour,
Suite à votre première inscription % la Fédération Française de Soft Armored Fighting (FFSAF), votre compte pour accéder à l'intranet a été créé.
Ce compte vous permettra de consulter vos informations, de vous inscrire aux compétitions et de consulter vos résultats.
Vous allez recevoir dans les prochaines minutes un email vous demandant de vérifier votre email et de définir un mot de passe.
Suite à votre première inscription % la Fédération Française de Soft Armored Fighting (FFSAF), votre compte intranet a été créé.
Ce compte vous permettra de consulter vos informations et, dans un futur proche, de vous inscrire aux compétitions ainsi que d'en consulter les résultats.
L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr
Votre nom d'utilisateur est : %s
Pour définir votre mot de passe, rendez-vous sur l'intranet > "Connexion" > "Mot de passe oublié ?"
Si vous n'avez pas demandé cette inscription, veuillez contacter le support à l'adresse support@ffsaf.fr.
(Pas de panique, nous ne vous enverrons pas de message autre que ce concernant votre compte)

View File

@ -18,6 +18,7 @@ import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.List;
import java.util.function.Consumer;
@ -26,6 +27,7 @@ import java.util.function.Function;
@WithSession
@ApplicationScoped
public class LicenceService {
private static final Logger LOGGER = Logger.getLogger(LicenceService.class);
@Inject
LicenceRepository repository;
@ -125,7 +127,9 @@ public class LicenceService {
.chain(() -> combRepository.persist(membreModel))
: Uni.createFrom().nullItem())
.call(__ -> (membreModel.getUserId() == null) ?
keycloakService.initCompte(membreModel.getId())
keycloakService.initCompte(membreModel.getId()).onFailure()
.invoke(t -> LOGGER.infof("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull()
: Uni.createFrom().nullItem());
}

View File

@ -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<MatchData> 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);

View File

@ -13,6 +13,7 @@ import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.hibernate.reactive.panache.Panache;
@ -102,15 +103,47 @@ public class MembreService {
return baseUni;
}
private Sort getSort(String order) {
Sort sort;
if (order == null || order.isBlank()) {
sort = Sort.ascending("fname", "lname");
} else {
sort = Sort.empty();
for (String e : order.split(",")) {
String[] split = e.split(" ");
if (split.length == 2) {
sort = sort.and(split[0],
split[1].equals("n") ? Sort.Direction.Ascending : Sort.Direction.Descending);
} else {
return null;
}
}
}
return sort;
}
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club,
int licenceRequest, int payState) {
int licenceRequest, int payState, String order, String categorie) {
if (search == null)
search = "";
search = "%" + search.replaceAll(" ", "% %") + "%";
String categorieFilter;
if (categorie == null || categorie.isBlank())
categorieFilter = " True";
else
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
String finalSearch = search;
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
Sort sort = getSort(order);
if (sort == null)
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return baseUni
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
@ -120,18 +153,18 @@ public class MembreService {
if (club == null || club.isBlank()) {
query = repository.find(
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, ids)
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, ids)
.page(Page.ofSize(limit));
} else {
if (club.equals("null")) {
query = repository.find(
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, ids).page(Page.ofSize(limit));
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, ids).page(Page.ofSize(limit));
} else {
query = repository.find(
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, club + "%", ids)
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, club, ids)
.page(Page.ofSize(limit));
}
}
@ -140,7 +173,7 @@ public class MembreService {
}
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
String subject) {
String order, String categorie, String subject) {
if (search == null)
search = "";
search = "%" + search.replaceAll(" ", "% %") + "%";
@ -149,6 +182,16 @@ public class MembreService {
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
String categorieFilter;
if (categorie == null || categorie.isBlank())
categorieFilter = " True";
else
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
Sort sort = getSort(order);
if (sort == null)
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return baseUni
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
@ -157,8 +200,8 @@ public class MembreService {
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
PanacheQuery<MembreModel> query = repository.find(
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, membreModel.getClub(), ids)
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, membreModel.getClub(), ids)
.page(Page.ofSize(limit));
return getPageResult(query, limit, page);
});
@ -197,6 +240,11 @@ public class MembreService {
return Uni.createFrom().nullItem();
AtomicReference<ClubModel> clubModel = new AtomicReference<>();
LOGGER.debugf("Membre import (size=%d)", data2.size());
for (SimpleMembreInOutData simpleMembreInOutData : data2) {
LOGGER.debugf("-> %s", simpleMembreInOutData.toString());
}
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
clubModel.set(membreModel.getClub());
@ -205,20 +253,24 @@ public class MembreService {
return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3",
data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(),
data2.stream().map(SimpleMembreInOutData::getEmail).filter(Objects::nonNull).toList());
data2.stream().map(SimpleMembreInOutData::getEmail).filter(o -> o != null && !o.isBlank())
.toList());
})
.call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get()))
if (!Objects.equals(membreModel.getClub(), clubModel.get())) {
LOGGER.info("Similar membres found: " + membreModel);
throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
}
}
Uni<Void> uniResult = Uni.createFrom().voidItem();
for (SimpleMembreInOutData dataIn : data2) {
MembreModel model = membres.stream()
.filter(m -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname()
.equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom()) ||
Objects.equals(m.getFname(), dataIn.getEmail())).findFirst()
.filter(m -> (dataIn.getLicence() != null && Objects.equals(m.getLicence(),
dataIn.getLicence())) || m.getLname().equals(dataIn.getNom()) && m.getFname()
.equals(dataIn.getPrenom()) || (dataIn.getEmail() != null && !dataIn.getEmail()
.isBlank() && Objects.equals(m.getFname(), dataIn.getEmail()))).findFirst()
.orElseGet(() -> {
MembreModel mm = new MembreModel();
mm.setClub(clubModel.get());
@ -226,16 +278,23 @@ public class MembreService {
mm.setCountry("FR");
return mm;
});
if (model.getId() != null) {
LOGGER.debugf("updating -> %s", dataIn.toString());
} else {
LOGGER.debugf("creating -> %s", dataIn.toString());
}
if (model.getEmail() != null) {
if (model.getEmail() != null && !model.getEmail().isBlank()) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
throw new DBadRequestException("Email déja utiliser");
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
}
if (StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
throw new DBadRequestException("Email déja utiliser");
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
}
}
@ -244,6 +303,7 @@ public class MembreService {
if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
}
@ -319,7 +379,7 @@ public class MembreService {
return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0)
if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utiliser");
})))
.chain(membreModel -> clubRepository.findById(membre.getClub())
@ -341,7 +401,7 @@ public class MembreService {
return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0)
if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utiliser");
})))
.invoke(Unchecked.consumer(membreModel -> {

View File

@ -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<List<Object[]>> 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<List<ResultCategoryData>> 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<Long, List<MatchModel>> 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<MatchModel> 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<MatchModel> matchModels_, ResultCategoryData out) {
List<MatchModel> matchModels = matchModels_.stream().filter(o -> o.getCategory_ord() >= 0).toList();
HashMap<Character, List<MatchModel>> 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<ResultCategoryData.PouleArrayData> matchs = matchEntities.stream()
.sorted(Comparator.comparing(MatchModel::getCategory_ord))
.map(ResultCategoryData.PouleArrayData::fromModel)
.toList();
List<ResultCategoryData.RankArray> 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<ResultCategoryData.TreeData> 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<TreeModel> treeModels, ResultCategoryData out) {
ArrayList<TreeNode<ResultCategoryData.TreeData>> trees = new ArrayList<>();
treeModels.stream().filter(t -> t.getLevel() != 0).forEach(treeModel -> {
TreeNode<ResultCategoryData.TreeData> root = new TreeNode<>();
convertTree(treeModel, root);
trees.add(root);
});
out.setTrees(trees);
}
public Uni<CombsArrayData> 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<RegisterModel> registers = pair.getKey();
List<MatchModel> matchModels = pair.getValue();
CombsArrayData.CombsArrayDataBuilder builder = CombsArrayData.builder();
List<CombsArrayData.CombsData> 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<RegisterModel> 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<CombsData> 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<ClubArrayData> 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<RegisterModel> registers = pair.getKey();
List<MatchModel> matchModels = pair.getValue();
builder.nb_insc(registers.size());
AtomicInteger tt_win = new AtomicInteger(0);
AtomicInteger tt_match = new AtomicInteger(0);
List<ClubArrayData.CombData> 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<CombData> 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<RegisterModel> 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<RegisterModel> 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");
}));
}
}

View File

@ -92,7 +92,7 @@ public class AffiliationRequestEndpoints {
@DELETE
@Path("/{id}")
@RolesAllowed({"federation_admin"})
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " +
"d'affiliation pour l'identifiant spécifié.")
@ -107,7 +107,7 @@ public class AffiliationRequestEndpoints {
if (o.getClub() == null && !securityCtx.roleHas("federation_admin"))
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()))
.chain(o -> service.deleteReqAffiliation(id, reason));
.chain(o -> service.deleteReqAffiliation(id, reason, securityCtx.roleHas("federation_admin")));
}
@PUT

View File

@ -1,7 +1,8 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*;
@ -12,18 +13,24 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("api/asso")
public class AssoEndpoints {
@RestClient
StateIdService stateIdService;
@RestClient
SirenService sirenService;
@GET
@Path("siren/{siren}")
@Path("state_id/{stateId}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<UniteLegaleRoot> getInfoSiren(@PathParam("siren") String siren) {
return sirenService.get_unite(siren).onFailure().transform(throwable -> {
public Uni<AssoData> getAssoInfo(@PathParam("stateId") String stateId) {
return ((stateId.charAt(0) == 'W') ? stateIdService.get_rna(stateId) : sirenService.get_unite(
stateId).chain(stateIdService::getAssoDataFromUnit)).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 404)
return new DNotFoundException("Service momentanément indisponible");
if (exception.getResponse().getStatus() == 400)
return new DNotFoundException("Siret introuvable");
return new DNotFoundException("Asso introuvable");
}
return throwable;
});

View File

@ -29,6 +29,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
@ -69,7 +70,8 @@ public class ClubEndpoints {
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleClubModel>> getAll() {
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).sorted(
Comparator.comparing(SimpleClubModel::getName)).toList());
}
@GET

View File

@ -58,13 +58,15 @@ public class MembreAdminEndpoints {
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Club à filter") @QueryParam("club") String club,
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment) {
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
@Parameter(description = "État de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "État du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
@Parameter(description = "Ordre") @QueryParam("order") String order) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment);
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment, order, categorie);
}
@GET

View File

@ -50,13 +50,15 @@ public class MembreClubEndpoints {
@Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit,
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment) {
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
@Parameter(description = "Ordre") @QueryParam("order") String order) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.search(limit, page - 1, search, licenceRequest, payment, securityCtx.getSubject());
return membreService.search(limit, page - 1, search, licenceRequest, payment, order, categorie, securityCtx.getSubject());
}
@GET

View File

@ -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<List<Object[]>> getList() {
return resultService.getList(securityCtx);
}
@GET
@Path("{uuid}")
public Uni<List<ResultCategoryData>> getCategory(@PathParam("uuid") String uuid) {
return resultService.getCategory(uuid, securityCtx);
}
@GET
@Path("{uuid}/club")
public Uni<ResultService.ClubArrayData> getClub(@PathParam("uuid") String uuid) {
return resultService.getClubArray(uuid, securityCtx);
}
@GET
@Path("{uuid}/comb")
public Uni<ResultService.CombsArrayData> getComb(@PathParam("uuid") String uuid) {
return resultService.getAllCombArray(uuid, securityCtx);
}
}

View File

@ -1,6 +1,7 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.quarkus.cache.CacheResult;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@ -15,5 +16,6 @@ public interface SirenService {
@GET
@Path("/v3/unites_legales/{SIREN}")
@CacheResult(cacheName = "AssoData_siren")
Uni<UniteLegaleRoot> get_unite(@PathParam("SIREN") String siren);
}
}

View File

@ -0,0 +1,48 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.quarkus.cache.CacheResult;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
public interface StateIdService {
@GET
@Path("/associations/{rna}")
@CacheResult(cacheName = "AssoData_rna")
Uni<AssoData> get_rna(@PathParam("rna") String rna);
default Uni<AssoData> getAssoDataFromUnit(UniteLegaleRoot u) {
AssoData assoData = new AssoData();
assoData.setSiren(u.getUnite_legale().getSiren());
assoData.setRna(u.getUnite_legale().getIdentifiant_association());
AssoData.Identite identite = new AssoData.Identite();
identite.setNom(u.getUnite_legale().getDenomination());
identite.setSiret_siege(u.getUnite_legale().getEtablissement_siege().getSiret());
assoData.setIdentite(identite);
AssoData.Address address = new AssoData.Address();
StringBuilder voie = new StringBuilder();
if (u.getUnite_legale().getEtablissement_siege().getNumero_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getNumero_voie()).append(' ');
if (u.getUnite_legale().getEtablissement_siege().getType_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getType_voie()).append(' ');
if (u.getUnite_legale().getEtablissement_siege().getLibelle_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getLibelle_voie()).append(' ');
address.setVoie(voie.toString().trim());
address.setComplement(u.getUnite_legale().getEtablissement_siege().getComplement_adresse());
address.setCode_postal(u.getUnite_legale().getEtablissement_siege().getCode_postal());
address.setCommune(
new AssoData.Commune(u.getUnite_legale().getEtablissement_siege().getLibelle_commune()));
assoData.setCoordonnees(new AssoData.Coordonnee(address));
return Uni.createFrom().item(assoData);
}
}

View File

@ -0,0 +1,48 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@RegisterForReflection
public class AssoData {
String siren;
String rna;
Identite identite;
Coordonnee coordonnees;
@Data
@RegisterForReflection
public static class Identite {
String nom;
String siret_siege;
}
@Data
@RegisterForReflection
@NoArgsConstructor
@AllArgsConstructor
public static class Coordonnee {
Address adresse_gestion;
}
@Data
@RegisterForReflection
public static class Address {
String voie;
String complement;
String code_postal;
String pays;
Commune commune;
}
@Data
@RegisterForReflection
@NoArgsConstructor
@AllArgsConstructor
public static class Commune {
String nom;
}
}

View File

@ -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<RegisterModel> 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<RegisterModel> insc, List<CompetitionGuestModel> 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;
}
}

View File

@ -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());
}

View File

@ -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;
}

View File

@ -17,9 +17,9 @@ import java.util.List;
@RegisterForReflection
public class RenewAffData {
String name;
Long siret;
String rna;
String state_id;
String address;
String contact;
int saison;
List<RenewMember> members;

View File

@ -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<Character, List<PouleArrayData>> matchs = new HashMap<>();
HashMap<Character, List<RankArray>> rankArray = new HashMap<>();
ArrayList<TreeNode<TreeData>> 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<Integer[]> 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<ScoreEmbeddable> scores,
boolean end) {
public static TreeData from(MatchModel match) {
return new TreeData(match.getId(), match.getC1Name(), match.getC2Name(), match.getScores(), match.isEnd());
}
}
}

View File

@ -14,8 +14,8 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
public class SimpleAffiliation {
@Schema(description = "L'identifiant de l'affiliation.", example = "1")
private Long id;
@Schema(description = "L'identifiant du club associé à l'affiliation.", example = "123")
private Long club;
@Schema(description = "L'identifiant du club associé à l'affiliation si id > 0 sinon n° SIRET ou RNA du club.", example = "123")
private String club;
@Schema(description = "La saison de l'affiliation.", example = "2022")
private int saison;
@Schema(description = "Indique si l'affiliation est validée ou non.", example = "true")
@ -27,7 +27,7 @@ public class SimpleAffiliation {
return new SimpleAffiliationBuilder()
.id(model.getId())
.club(model.getClub().getId())
.club(String.valueOf(model.getClub().getId()))
.saison(model.getSaison())
.validate(true)
.build();

View File

@ -36,10 +36,8 @@ public class SimpleClub {
private String contact_intern;
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
private String address;
@Schema(description = "RNA du club", example = "W123456789")
private String RNA;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
private Long SIRET;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
private String state_id;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
private Long no_affiliation;
@Schema(description = "Club international", example = "false")
@ -60,8 +58,7 @@ public class SimpleClub {
.training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern())
.RNA(model.getRNA())
.SIRET(model.getSIRET())
.state_id(model.getStateId())
.no_affiliation(model.getNo_affiliation())
.international(model.isInternational())
.address(model.getAddress())

View File

@ -20,8 +20,8 @@ public class SimpleClubList {
String name;
@Schema(description = "Pays du club", example = "FR")
String country;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long siret;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234")
String state_id;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation;
@ -29,7 +29,7 @@ public class SimpleClubList {
if (model == null)
return null;
return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getSIRET(),
return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getStateId(),
model.getNo_affiliation());
}
}

View File

@ -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<LicenceModel> 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);
}
}

View File

@ -25,10 +25,8 @@ public class SimpleReqAffiliation {
Long club_no_aff;
@Schema(description = "Nom du club demander", example = "Association sportive")
String name;
@Schema(description = "Numéro SIRET de l'association", example = "12345678901234")
long siret;
@Schema(description = "Numéro RNA de l'association", example = "W123456789")
String RNA;
@Schema(description = "Numéro SIRET ou RNA de l'association", example = "12345678901234")
String stateId;
@Schema(description = "Adresse de l'association", example = "1 rue de l'exemple, 75000 Paris")
String address;
@Schema(description = "Email de contact de l'association", example = "test@test.fr")
@ -45,8 +43,7 @@ public class SimpleReqAffiliation {
return new SimpleReqAffiliation.SimpleReqAffiliationBuilder()
.id(model.getId())
.name(model.getName())
.siret(model.getSiret())
.RNA(model.getRNA())
.stateId(model.getState_id())
.address(model.getAddress())
.saison(model.getSaison())
.contact(model.getContact())

View File

@ -16,8 +16,8 @@ public class SimpleReqAffiliationResume {
Long id;
@Schema(description = "Le nom de l'association.", example = "Association sportive")
String name;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234")
long siret;
@Schema(description = "Le numéro SIRET ou RNA de l'association.", example = "12345678901234")
String stateId;
@Schema(description = "La saison de l'affiliation.", example = "2025")
int saison;
@ -25,10 +25,10 @@ public class SimpleReqAffiliationResume {
if (model == null)
return null;
return new SimpleReqAffiliationResume.SimpleReqAffiliationResumeBuilder()
return new SimpleReqAffiliationResumeBuilder()
.id(model.getId())
.name(model.getName())
.siret(model.getSiret())
.stateId(model.getState_id())
.saison(model.getSaison())
.build();
}

View File

@ -33,8 +33,8 @@ public class UniteLegaleRoot {
public String etat_administratif;
public String identifiant_association;
public String nic_siege;
public Object nom;
public Object nom_usage;
public String nom;
public String nom_usage;
public int nombre_periodes;
public String nomenclature_activite_principale;
public Object prenom_1;
@ -67,7 +67,7 @@ public class UniteLegaleRoot {
private Object code_pays_etranger_2;
private String code_postal;
private Object code_postal_2;
private Object complement_adresse;
private String complement_adresse;
private Object complement_adresse2;
private String date_creation;
private String date_debut;

View File

@ -11,7 +11,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType;
@Getter
@ToString
@ToString(exclude = {"status", "logo"})
public class AffiliationRequestForm {
@Schema(description = "L'identifiant de l'affiliation. (null si nouvelle demande d'affiliation)")
@FormParam("id")
@ -21,13 +21,9 @@ public class AffiliationRequestForm {
@FormParam("name")
private String name = null;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234", required = true)
@FormParam("siret")
private Long siret = null;
@Schema(description = "Le numéro RNA de l'association. (peut être null)", example = "W123456789")
@FormParam("rna")
private String rna = null;
@Schema(description = "Le numéro SIRET/RNA de l'association.", example = "12345678901234", required = true)
@FormParam("state_id")
private String state_id = null;
@Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true)
@FormParam("adresse")
@ -114,8 +110,7 @@ public class AffiliationRequestForm {
public AffiliationRequestModel toModel() {
AffiliationRequestModel model = new AffiliationRequestModel();
model.setName(this.getName());
model.setSiret(this.getSiret());
model.setRNA(this.getRna());
model.setState_id(this.getState_id());
model.setAddress(this.getAdresse());
model.setSaison(this.getSaison());
model.setContact(this.getContact());

View File

@ -4,12 +4,10 @@ import fr.titionfire.ffsaf.utils.RoleAsso;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MediaType;
import lombok.Getter;
import lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType;
@Getter
@ToString
public class AffiliationRequestSaveForm {
@Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true)
@FormParam("id")
@ -19,13 +17,9 @@ public class AffiliationRequestSaveForm {
@FormParam("name")
private String name = null;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234", required = true)
@FormParam("siret")
private Long siret = null;
@Schema(description = "Le numéro RNA de l'association. (peut être null)", example = "W123456789")
@FormParam("rna")
private String rna = null;
@Schema(description = "Le numéro SIRET ou RNA de l'association.", example = "12345678901234", required = true)
@FormParam("state_id")
private String state_id = null;
@Schema(description = "L'adresse de l'association.", example = "1 rue de l'exemple, 75000 Paris", required = true)
@FormParam("address")
@ -171,4 +165,38 @@ public class AffiliationRequestSaveForm {
}
}
}
@Override
public String toString() {
return "AffiliationRequestSaveForm{" +
"id=" + id +
", name='" + name + '\'' +
", state_id=" + state_id +
", address='" + address + '\'' +
", contact='" + contact + '\'' +
", status_len=" + status.length +
", logo_len=" + logo.length +
", m1_mode=" + m1_mode +
", m1_role=" + m1_role +
", m1_lincence='" + m1_lincence + '\'' +
", m1_lname='" + m1_lname + '\'' +
", m1_fname='" + m1_fname + '\'' +
", m1_email='" + m1_email + '\'' +
", m1_email_mode=" + m1_email_mode +
", m2_mode=" + m2_mode +
", m2_role=" + m2_role +
", m2_lincence='" + m2_lincence + '\'' +
", m2_lname='" + m2_lname + '\'' +
", m2_fname='" + m2_fname + '\'' +
", m2_email='" + m2_email + '\'' +
", m2_email_mode=" + m2_email_mode +
", m3_mode=" + m3_mode +
", m3_role=" + m3_role +
", m3_lincence='" + m3_lincence + '\'' +
", m3_lname='" + m3_lname + '\'' +
", m3_fname='" + m3_fname + '\'' +
", m3_email='" + m3_email + '\'' +
", m3_email_mode=" + m3_email_mode +
'}';
}
}

View File

@ -43,13 +43,9 @@ public class FullClubForm {
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris", required = true)
private String address = null;
@FormParam("rna")
@Schema(description = "RNA du club", example = "W123456789")
private String rna = null;
@FormParam("siret")
@Schema(description = "Numéro SIRET du club", example = "12345678901234", required = true)
private String siret = null;
@FormParam("state_id")
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234", required = true)
private String state_id = null;
@FormParam("international")
@Schema(description = "Club international", example = "false", required = true)

View File

@ -1,5 +1,5 @@
package fr.titionfire.ffsaf.utils;
public enum CompetitionSystem {
SAFCA, NONE
SAFCA, INTERNAL
}

View File

@ -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<T> {
private T data;
private TreeNode<T> left;
private TreeNode<T> 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);
}
}

View File

@ -0,0 +1,206 @@
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.RCategorie;
import fr.titionfire.ffsaf.ws.recv.RMatch;
import fr.titionfire.ffsaf.ws.recv.RRegister;
import fr.titionfire.ffsaf.ws.recv.WSReceiver;
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<WebSocketConnection, HashMap<UUID, JsonUni<?>>> waitingResponse = new HashMap<>();
@Inject
RMatch rMatch;
@Inject
RCategorie rCategorie;
@Inject
RRegister rRegister;
@Inject
SecurityCtx securityCtx;
@Inject
CompetPermService competPermService;
@Inject
CompetitionRepository competitionRepository;
HashMap<Method, Object> 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);
}
@OnOpen
@WithSession
Uni<MessageOut> 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<MessageOut> 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<Method, Object> 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<Void> sendNotifyToOtherEditor(WebSocketConnection connection, String code, Object data) {
String uuid = connection.pathParam("uuid");
List<Uni<Void>> 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<Void> error(WebSocketConnection connection, ForbiddenException t) {
return connection.close(CloseReason.INTERNAL_SERVER_ERROR);
//return "forbidden: " + securityCtx.getSubject();
}
}

View File

@ -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){
}

View File

@ -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){
}

View File

@ -0,0 +1,8 @@
package fr.titionfire.ffsaf.ws;
public enum PermLevel {
NONE,
VIEW,
TABLE,
ADMIN,
}

View File

@ -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;
}

View File

@ -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<CategoryModel> 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<List<JustCategorie>> 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<FullCategory> 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<Long> 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<Void> 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<Long> 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<TreeModel> 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<Void> updateNode(TreeNode<Long> 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<Void> 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<Long> 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<TreeNode<Long>> trees) {
}
@Data
@RegisterForReflection
public static class FullCategory {
long id;
String name;
int type;
String liceName;
List<TreeEntity> trees = null;
List<MatchEntity> matches;
}
}

View File

@ -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<MatchModel> getById(long id, WebSocketConnection connection) {
return matchRepository.findById(id)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Matche non trouver");
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}));
}
private Uni<MatchModel> creatMatch(CategoryModel categoryModel, AddMatch m) {
return Uni.createFrom().item(() -> {
MatchModel matchModel = new MatchModel();
matchModel.setCategory(categoryModel);
matchModel.setCategory_ord(m.categorie_ord);
matchModel.setEnd(false);
matchModel.setPoule(m.poule);
return matchModel;
})
.call(mm -> m.c1() >= 0 ?
combRepository.findById(m.c1()).invoke(mm::setC1_id) :
competitionGuestRepository.findById(m.c1() * -1).invoke(mm::setC1_guest))
.call(mm -> m.c2() >= 0 ?
combRepository.findById(m.c2()).invoke(mm::setC2_id) :
competitionGuestRepository.findById(m.c2() * -1).invoke(mm::setC2_guest));
}
@WSReceiver(code = "addMatch", permission = PermLevel.ADMIN)
public Uni<Void> addMatch(WebSocketConnection connection, AddMatch m) {
return categoryRepository.findById(m.categorie)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}))
.chain(categoryModel -> creatMatch(categoryModel, m))
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm)))
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchComb", permission = PermLevel.ADMIN)
public Uni<Void> updateMatchComb(WebSocketConnection connection, MatchComb match) {
return getById(match.id(), connection)
.call(mm -> match.c1() != null ?
match.c1() >= 0 ?
combRepository.findById(match.c1()).invoke(model -> {
mm.setC1_id(model);
mm.setC1_guest(null);
}) :
competitionGuestRepository.findById(match.c1() * -1).invoke(model -> {
mm.setC1_id(null);
mm.setC1_guest(model);
}) :
Uni.createFrom().nullItem().invoke(__ -> {
mm.setC1_id(null);
mm.setC1_guest(null);
}))
.call(mm -> match.c2() != null ?
match.c2() >= 0 ?
combRepository.findById(match.c2()).invoke(model -> {
mm.setC2_id(model);
mm.setC2_guest(null);
}) :
competitionGuestRepository.findById(match.c2() * -1).invoke(model -> {
mm.setC2_id(null);
mm.setC2_guest(model);
}) :
Uni.createFrom().nullItem().invoke(__ -> {
mm.setC2_id(null);
mm.setC2_guest(null);
}))
.chain(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchOrder", permission = PermLevel.ADMIN)
public Uni<Void> updateMatchComb(WebSocketConnection connection, MatchOrder order) {
return getById(order.id(), connection)
.call(m -> matchRepository.update(
"category_ord = category_ord + 1 WHERE category_ord >= ?1 AND category_ord < ?2", order.pos,
m.getCategory_ord()))
.call(m -> matchRepository.update(
"category_ord = category_ord - 1 WHERE category_ord <= ?1 AND category_ord > ?2", order.pos,
m.getCategory_ord()))
.invoke(m -> m.setCategory_ord(order.pos))
.call(m -> Panache.withTransaction(() -> matchRepository.persist(m)))
.call(mm -> SSMatch.sendMatchOrder(connection, order))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchScore", permission = PermLevel.TABLE)
public Uni<Void> updateMatchScore(WebSocketConnection connection, MatchScore score) {
return getById(score.matchId(), connection)
.chain(matchModel -> {
int old_win = matchModel.win();
Optional<ScoreEmbeddable> optional = matchModel.getScores().stream()
.filter(s -> s.getN_round() == score.n_round()).findAny();
boolean b = score.s1() != -1000 || score.s2() != -1000;
if (optional.isPresent()) {
if (b) {
optional.get().setS1(score.s1());
optional.get().setS2(score.s2());
} else {
matchModel.getScores().remove(optional.get());
}
} else if (b) {
matchModel.getScores().add(new ScoreEmbeddable(score.n_round(), score.s1(), score.s2()));
}
return Panache.withTransaction(() -> matchRepository.persist(matchModel))
.call(mm -> {
if (mm.isEnd() && mm.win() != old_win && mm.getCategory_ord() == -42) {
return updateEndAndTree(mm, new ArrayList<>())
.call(l -> SSMatch.sendMatch(connection, l));
}
return Uni.createFrom().nullItem();
});
})
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@WSReceiver(code = "updateMatchEnd", permission = PermLevel.TABLE)
public Uni<Void> updateMatchEnd(WebSocketConnection connection, MatchEnd matchEnd) {
List<MatchEntity> toSend = new ArrayList<>();
return getById(matchEnd.matchId(), connection)
.chain(mm -> {
if (mm.getCategory_ord() == -42 && mm.win() == 0) { // Tournois
mm.setDate(null);
mm.setEnd(false);
} else {
mm.setDate((matchEnd.end) ? new Date() : null);
mm.setEnd(matchEnd.end);
}
return Panache.withTransaction(() -> matchRepository.persist(mm));
})
.invoke(mm -> toSend.add(MatchEntity.fromModel(mm)))
.chain(mm -> updateEndAndTree(mm, toSend))
.call(__ -> SSMatch.sendMatch(connection, toSend))
.replaceWithVoid();
}
private Uni<List<MatchEntity>> updateEndAndTree(MatchModel mm, List<MatchEntity> toSend) {
return (mm.getCategory_ord() != -42) ?
Uni.createFrom().item(toSend) :
treeRepository.list("category = ?1 AND level != 0", Sort.ascending("level"), mm.getCategory().getId())
.chain(treeModels -> {
List<TreeModel> node = treeModels.stream().flatMap(t -> t.flat().stream()).toList();
List<TreeEntity> trees = treeModels.stream().map(TreeEntity::fromModel).toList();
for (int i = 0; i < trees.size() - 1; i++) {
TreeEntity.setAssociated(trees.get(i), trees.get(i + 1));
}
TreeEntity root = trees.stream()
.filter(t -> t.getMatchNode(mm.getId()) != null)
.findFirst()
.orElseThrow();
TreeEntity currentNode = root.getMatchNode(mm.getId());
if (currentNode == null) {
LOGGER.error(
"currentNode is empty for " + mm.getId() + " in " + mm.getCategory().getId());
return Uni.createFrom().voidItem();
}
TreeEntity parent = TreeEntity.getParent(root, currentNode);
if (parent == null) {
LOGGER.error("parent is empty for " + mm.getId() + " in " + mm.getCategory().getId());
return Uni.createFrom().voidItem();
}
int w = mm.win();
MembreModel toSetWin = null;
MembreModel toSetLose = null;
CompetitionGuestModel toSetWinGuest = null;
CompetitionGuestModel toSetLoseGuest = null;
if (mm.isEnd() && w != 0) {
toSetWin = (w > 0) ? mm.getC1_id() : mm.getC2_id();
toSetLose = (w > 0) ? mm.getC2_id() : mm.getC1_id();
toSetWinGuest = (w > 0) ? mm.getC1_guest() : mm.getC2_guest();
toSetLoseGuest = (w > 0) ? mm.getC2_guest() : mm.getC1_guest();
}
MatchModel modelWin = node.stream()
.filter(n -> Objects.equals(n.getId(), parent.getId()))
.findAny()
.map(TreeModel::getMatch)
.orElseThrow();
MatchModel modelLose = (parent.getAssociatedNode() == null) ? null : node.stream()
.filter(n -> Objects.equals(n.getId(), parent.getAssociatedNode().getId()))
.findAny()
.map(TreeModel::getMatch)
.orElseThrow();
if (currentNode.equals(parent.getLeft())) {
modelWin.setC1_id(toSetWin);
modelWin.setC1_guest(toSetWinGuest);
if (modelLose != null) {
modelLose.setC1_id(toSetLose);
modelLose.setC1_guest(toSetLoseGuest);
}
} else if (currentNode.equals(parent.getRight())) {
modelWin.setC2_id(toSetWin);
modelWin.setC2_guest(toSetWinGuest);
if (modelLose != null) {
modelLose.setC2_id(toSetLose);
modelLose.setC2_guest(toSetLoseGuest);
}
}
return Panache.withTransaction(() -> matchRepository.persist(modelWin)
.invoke(mm2 -> toSend.add(MatchEntity.fromModel(mm2)))
.call(__ -> modelLose == null ? Uni.createFrom().nullItem() :
matchRepository.persist(modelLose)
.invoke(mm2 -> toSend.add(MatchEntity.fromModel(mm2)))
)
.replaceWithVoid());
})
.map(__ -> toSend);
}
@WSReceiver(code = "deleteMatch", permission = PermLevel.ADMIN)
public Uni<Void> deleteMatch(WebSocketConnection connection, Long idMatch) {
return getById(idMatch, connection)
.chain(matchModel -> Panache.withTransaction(() -> matchRepository.delete(matchModel)))
.call(__ -> SSMatch.sendDeleteMatch(connection, idMatch))
.replaceWithVoid();
}
@WSReceiver(code = "recalculateMatch", permission = PermLevel.ADMIN)
public Uni<Void> recalculateMatch(WebSocketConnection connection, RecalculateMatch data) {
ArrayList<MatchEntity> matches = new ArrayList<>();
return categoryRepository.findById(data.categorie)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}))
.call(cm -> 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<List<MatchModel>> 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<AddMatch> newMatch, HashMap<Long, Integer> matchOrderToUpdate,
HashMap<Long, Character> matchPouleToUpdate, List<Long> matchesToRemove) {
}
}

View File

@ -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.ADMIN)
public Uni<List<CombEntity>> 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<CombEntity> combEntities = new ArrayList<>();
combEntities.addAll(cm.getInsc().stream().map(CombEntity::fromModel).toList());
combEntities.addAll(cm.getGuests().stream().map(CombEntity::fromModel).toList());
return combEntities;
});
}
}

View File

@ -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;
}

View File

@ -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<T> {
private final Class<T> clazz;
private final Function<? super T, Uni<?>> mapper;
public JsonUni(Class<T> 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);
}
}

View File

@ -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<Void> sendRegister(String uuid, RegisterModel registerModel) {
return send(uuid, "sendRegister", CombEntity.fromModel(registerModel));
}
public Uni<Void> sendRegister(String uuid, CompetitionGuestModel model) {
return send(uuid, "sendRegister", CombEntity.fromModel(model));
}
public Uni<Void> sendRegisterRemove(String uuid, Long combId) {
return send(uuid, "sendRegisterRemove", combId);
}
public Uni<Void> send(String uuid, String code, Object data) {
List<Uni<Void>> 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.ADMIN.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();
}
}

View File

@ -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<Void> sendAddCategory(WebSocketConnection connection, CategoryModel category) {
return SSCategorie.sendAddCategory(connection, RCategorie.JustCategorie.from(category));
}
public static Uni<Void> sendAddCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory", justCategorie);
}
public static Uni<Void> sendCategory(WebSocketConnection connection, CategoryModel category) {
return SSCategorie.sendCategory(connection, RCategorie.JustCategorie.from(category));
}
public static Uni<Void> sendCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", justCategorie);
}
public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
}
}

View File

@ -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<Void> sendMatch(WebSocketConnection connection, MatchEntity matchEntity) {
return SSMatch.sendMatch(connection, List.of(matchEntity));
}
public static Uni<Void> sendMatch(WebSocketConnection connection, List<MatchEntity> matchEntities) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatch", matchEntities);
}
public static Uni<Void> sendMatchOrder(WebSocketConnection connection, RMatch.MatchOrder matchOrder) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatchOrder", matchOrder);
}
public static Uni<Void> sendDeleteMatch(WebSocketConnection connection, Long l) {
return SSMatch.sendDeleteMatch(connection, List.of(l));
}
public static Uni<Void> sendDeleteMatch(WebSocketConnection connection, List<Long> longs) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDeleteMatch", longs);
}
}

View File

@ -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);
}
}

View File

@ -43,6 +43,10 @@ notif.affRequest.mail=
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

View File

@ -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

View File

@ -8,12 +8,8 @@
<link href="/index.css" rel="stylesheet">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous"
/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
@ -27,11 +23,8 @@
<div class="spinner"></div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"
></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script async type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -8,6 +8,9 @@
"name": "webapp",
"version": "0.0.0",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
@ -399,6 +402,55 @@
"node": ">=6.9.0"
}
},
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@dnd-kit/sortable": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
"dependencies": {
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@dnd-kit/core": "^6.3.0",
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/utilities": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@emotion/is-prop-valid": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",

View File

@ -10,6 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",

View File

@ -38,18 +38,22 @@ async function getData() {
let icon = null;
if (d.uuid !== null) {
const img = await getMeta(`${api_url}/api/club/${d.uuid}/logo`);
let ratio = img.naturalHeight / img.naturalWidth;
try {
const img = await getMeta(`${api_url}/api/club/${d.uuid}/logo`);
let ratio = img.naturalHeight / img.naturalWidth;
icon = L.icon({
iconUrl: `${api_url}/api/club/${d.uuid}/logo`,
icon = L.icon({
iconUrl: `${api_url}/api/club/${d.uuid}/logo`,
iconSize: [50, 50 * ratio], // size of the icon
//shadowSize: [50, 64], // size of the shadow
iconAnchor: [25, 50 * ratio], // point of the icon which will correspond to marker's location
//shadowAnchor: [4, 62], // the same for the shadow
popupAnchor: [0, -50 * ratio] // point from which the popup should open relative to the iconAnchor
});
iconSize: [50, 50 * ratio], // size of the icon
//shadowSize: [50, 64], // size of the shadow
iconAnchor: [25, 50 * ratio], // point of the icon which will correspond to marker's location
//shadowAnchor: [4, 62], // the same for the shadow
popupAnchor: [0, -50 * ratio] // point from which the popup should open relative to the iconAnchor
});
}catch (e) {
console.log("Error loading image for club", d.name, e);
}
}
for (const m of d.training_location) {

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="600" viewBox="0 0 12 6"><path fill="#00843d" d="M0 0h12v6H0z"/><path fill="#fff" d="M0 2h12v4H0z"/><path d="M0 4h12v2H0z"/><path fill="#c8102e" d="M0 0h3v6H0z"/></svg>

After

Width:  |  Height:  |  Size: 228 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 217 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600" viewBox="0 0 138 92"><path fill="#fff" d="M0 0h138v92H0z"/><path d="M0 0h138l-9 46H9z"/><path fill="#fcd116" d="M69 46 39 36l15.288-2.926-13.004-8.555 15.244 3.147-8.741-12.879 12.879 8.741-3.147-15.244 8.555 13.004L69 6l2.926 15.288 8.555-13.004-3.147 15.244 12.879-8.741-8.741 12.879 15.244-3.147-13.004 8.555L99 36z"/><path fill="#0072c6" d="M9 36h120v20H9z"/><path fill="#ce1126" d="m0 0 69 92 69-92v92H0z"/></svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="600"><clipPath id="a"><path d="M0 0v150h700v150H600zm600 0H300v350H0v-50z"/></clipPath><path fill="#012169" d="M0 0h1200v600H0z"/><path stroke="#fff" stroke-width="60" d="m0 0 600 300m0-300L0 300"/><path stroke="#c8102e" stroke-width="40" d="m0 0 600 300m0-300L0 300" clip-path="url(#a)"/><path stroke="#fff" stroke-width="100" d="M300 0v350M0 150h700"/><path stroke="#c8102e" stroke-width="60" d="M300 0v350M0 150h700"/><path fill="#012169" d="M0 300h600V0h200v400H0z"/><path fill="#fff" d="M784.8 156c0 151.011 17.51 203.045 52.565 238.1C858.27 414.853 867.539 422.662 900 444c32.461-21.337 41.729-29.146 62.636-49.9 35.053-35.055 52.564-87.089 52.564-238.1-25.497 10.917-40.026 16.209-61.051 15.885-18.809-.29-38.737-9.024-54.149-15.885-15.412 6.86-35.34 15.595-54.148 15.885-21.026.324-35.555-4.968-61.052-15.885"/><g transform="matrix(3.50845 0 0 3.34488 -47.153 -59.398)"><path fill="#f90" d="M271 87c1.543 3.63 6.49 7.637 7.85 9.601-1.731 1.964-2.077 1.75-1.85 5.399 3.01-3.15 3.064-3.478 5-3 4.241 4.232.759 13.321-2.746 15.297-3.504 2.108-2.868-.073-8.12 2.569 2.408 2.059 5.198-.302 7.478.329 1.239 1.47-.589 4.149.374 6.672 2.015-.194 1.773-4.262 2.242-5.737C282.7 112.726 291.55 108.957 292 104c1.866-.876 3.731-.274 6 1-1.13-4.644-4.868-4.594-5.87-6.044-2.385-3.645-4.499-7.803-9.593-8.881-3.867-.82-3.578.246-6.056-1.444-1.543-1.202-6.231-3.474-5.481-1.631"/><circle cx="281.317" cy="91.128" r=".806" fill="#fff"/></g><g transform="matrix(-1.639 -2.95744 3.10207 -1.56258 1034.545 1220.517)"><path fill="#f90" d="M271 87c1.543 3.63 6.49 7.637 7.85 9.601-1.731 1.964-2.077 1.75-1.85 5.399 3.01-3.15 3.064-3.478 5-3 4.241 4.232.759 13.321-2.746 15.297-3.504 2.108-2.868-.073-8.12 2.569 2.408 2.059 5.198-.302 7.478.329 1.239 1.47-.589 4.149.374 6.672 2.015-.194 1.773-4.262 2.242-5.737C282.7 112.726 291.55 108.957 292 104c1.866-.876 3.731-.274 6 1-1.13-4.644-4.868-4.594-5.87-6.044-2.385-3.645-4.499-7.803-9.593-8.881-3.867-.82-3.578.246-6.056-1.444-1.543-1.202-6.231-3.474-5.481-1.631"/><circle cx="281.317" cy="91.128" r=".806" fill="#fff"/></g><g transform="matrix(-1.80698 2.86712 -3.00733 -1.72273 1682.51 -336.808)"><path fill="#f90" d="M271 87c1.543 3.63 6.49 7.637 7.85 9.601-1.731 1.964-2.077 1.75-1.85 5.399 3.01-3.15 3.064-3.478 5-3 4.241 4.232.759 13.321-2.746 15.297-3.504 2.108-2.868-.073-8.12 2.569 2.408 2.059 5.198-.302 7.478.329 1.239 1.47-.589 4.149.374 6.672 2.015-.194 1.773-4.262 2.242-5.737C282.7 112.726 291.55 108.957 292 104c1.866-.876 3.731-.274 6 1-1.13-4.644-4.868-4.594-5.87-6.044-2.385-3.645-4.499-7.803-9.593-8.881-3.867-.82-3.578.246-6.056-1.444-1.543-1.202-6.231-3.474-5.481-1.631"/><circle cx="281.317" cy="91.128" r=".806" fill="#fff"/></g><path fill="#9cf" d="M813.698 362.055c6.72 12.755 14.602 22.979 23.668 32.044C858.272 414.854 867.539 422.663 900 444c32.461-21.337 41.73-29.146 62.637-49.9 9.065-9.066 16.945-19.29 23.665-32.045z"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="600"><path fill="#F2A800" d="M0 0h1200v600H0z"/><path fill="#0033A0" d="M0 0h1200v400H0z"/><path fill="#D90012" d="M0 0h1200v200H0z"/></svg>

After

Width:  |  Height:  |  Size: 201 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600"><path d="M0 0h900v600H0z"/><path fill="#cc092f" d="M0 0h900v300H0z"/><path d="M390.858 205.797h36.644l11.112-34.537 11.41 34.537h36.64l-29.433 21.926 11.409 35.135-30.026-21.324-29.731 21.324 11.41-35.135z" fill="#ffcb00" fill-rule="evenodd"/><path d="m524.801 394.706-28.827-22.53c29.127-23.724 48.052-60.064 48.052-100.605 0-61.866-44.144-113.824-102.411-126.137l4.504-21.025c8.11 1.803 17.624 5.217 25.432 7.916l7.51-12.608c8.1 3.9 17.118 7.803 24.621 12.608l-5.703 13.518c7.51 4.808 16.575 12.251 21.616 18.112l11.277-8.809c6.008 6.604 13.218 13.507 18.323 20.721l-10.211 10.512c6.594 9.68 9.143 14.135 14.004 25.287l14.327-3.36c2.706 8.408 6.814 18.702 8.32 27.408l-13.566 5.332c1.62 6.766 3.718 18.584 3.583 28.525-.011.598-.056 1.276-.056 1.878l14.167 2.021c-.6 8.712-.897 18.625-2.704 27.333h-14.714c-2.1 9.005-4.81 20.02-8.11 28.425l12.308 7.213c-3.901 7.805-7.8 17.117-12.908 24.626l-13.514-5.703c-5.11 7.505-10.81 14.71-17.118 21.321l8.706 11.41c-2.398 2.1-4.502 4.205-6.908 6.61m-63.362-2.704 21.772 13.12c-2.03 1.857-8.112 5.006-9.724 5.644l3.56 13.666c-8.407 2.708-17.42 6.608-26.724 8.412l-5.405-13.513c-8.71 1.8-17.416 3-26.427 3l-1.807 14.114c-8.406-.598-18.617 0-27.928-2.102v-14.11c-8.411-1.506-16.519-3.606-24.324-6.312l-6.91 12.322c-7.207-3.01-16.816-6.615-24.628-11.416l5.108-13.514c-6.31-3.307-12.015-7.509-17.42-11.72l13.515-17.118c22.226 17.722 50.153 28.228 80.787 28.228 16.221 0 32.132-2.697 46.555-8.7" fill="#ffcb00" fill-rule="evenodd"/><path d="M552.192 426.811 408.462 317.43l-1.31 1.866 143.36 109.573-8.509 10.647-139.753-89.64c-37.839-23.726-36.337-42.344-28.528-62.466l6.608-16.217c3.605 21.023 25.223 36.944 47.748 54.658l126.914 97.497zm36.084 32.6a2.23 2.23 0 0 1-2.23-2.238 2.23 2.23 0 0 1 2.23-2.232c1.237 0 2.24 1 2.24 2.232a2.238 2.238 0 0 1-2.24 2.239m-8.59-6.308a2.24 2.24 0 0 1-2.237-2.235 2.236 2.236 0 1 1 2.237 2.235m-8.79-7.006c-1.232 0-2.236-1-2.236-2.231a2.236 2.236 0 0 1 4.47 0c0 1.231-1 2.231-2.234 2.231m29.554 12.4-43.065-33.365-12.514 16.127 37.41 26.235c3.005 2.1 3.3 11.718 14.407 13.222 4.513.596 7.508-2.71 7.508-2.71 4.84-5.892 3.521-14.003-3.746-19.51" fill="#ffcb00" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
<svg width="900" height="600" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path fill="#009edb" d="M0 0h900v600H0Z"/><path d="M435.583 51.758c-.47-.08-.957.322-1.474 1.318-2.452 4.904-3.45 8.81-4.142 11.8-.655 2.901-5.053 2.911-6.892 3.524-1.839.613-2.169 6.417-3.983 4.062-5.75-7.433-9.504-7.74-14.408-7.126-2.913 1.456 4.522 5.287 2.453 9.807-14.711 0-17.163 4.904-19.615 19.615-4.597-4.444-8.888 6.283-22.067 9.807.23 3.142 1.532 11.265 0 14.713-.172.86 2.806 2.67 1.852 6.28-.95 3.61-8.054 4.652-9.236 9.034-1.184 4.382 3.018 3.291 2.153 6.47-.866 3.178-4.728 3.345-2.66 14.15-.46 1.991-4.37 5.747-4.37 5.747s-2.827 3.17 1.39 7.594c-6.943 5.393-6.483 17.347-6.292 21.829-7.048-1.84-12.26 2.45-12.26 2.45l2.45 9.81c-19.614 7.354-26.587 1.913-29.497 5.36-2.873 4.33-10.228 6.188-24.643 6.927-2.554.286-4.09 4.875-12.059 4.875-7.356-2.452-12.26-12.26-22.067-19.615l-22.067-7.357c-7.356-4.904-9.808-9.806-19.615-14.71l-17.162-7.358c-12.26-7.354-20.763-32.716-26.97-26.97 0 17.164 4.903 41.682 14.71 53.942 7.356 4.904 9.809 7.356 17.165 9.808 4.903 7.356 4.901 7.356 12.257 12.26-12.258 4.902-12.258 2.45-17.162 14.71 7.586 2.681 2.758 6.301 6.743 9.462 3.984 3.16 6.283 1.953 9.425 4.253 2.22-.767.994-3.908.994-3.908s2.836-5.133 4.905-2.45c4.215 10.265 9.808 7.355 9.808 7.355.613 2.452-1.151 8.043.267 8.966 4.405 2.834.344 9.653 1.11 10.573 4.598 1.84 5.134-7.586 6.552-7.204 1.417.386 1.878-.075 4.33-.075 0 9.806 0 9.806 4.903 19.614 0 0-5.515.076-7.393.766 2.95 7.74-12.298 15.86-12.068 19.078-.536 1.609 2.453 25.516 4.751 29.193 1.457 2.3-14.712 0-14.712 0 0 19.615 4.905 24.518 24.52 26.97 0 0-3.526-6.742-2.453-7.355 5.286-4.828 4.905 9.807 4.905 9.807 20.993 8.888-2.452 6.36 7.355 19.615l9.807-2.45c2.452 26.97 7.355 12.259 2.453 41.682h12.257v14.71c24.519-7.356 17.164 4.903 31.875 9.807l17.162 4.902 19.615 9.81v-4.905s30.19 1.762 33.178 2.758c6.59.307 2.835-7.277 12.643-7.277.65 0 11.186 7.106 14.05-2.184-.723 9.554 18.437 5.094 22.573 5.016 4.522 0 30.496 17.317 45.207 12.414 7.204-2.607 7.204-12.105 12.107-10.727 1.608 0 9.807 22.067 9.807 22.067 2.758 6.82 2.74 8.706-.562 16.435-1.846 5.353 4.205 8.039-6.325 15.161-8.08 4.823-4.008 6.722-5.999 11.721-2.604 3.465-10.656 3.441-9.41 8.555 1.531 4.903 17.24 2.836 19.843 14.33 3.18 1.648 2.876-3.87 8.71-4.035 2.915-.08 3.214 5.1 6.492 4.255 5.138-1.347 4.262-2.903 9.318-.22 3.754-14.251 7.587 11.032 31.723 2.526 4.693-.804 10.166 4.951 11.547 4.71 1.38-.24 5.181-9.312 7.13-9.582 1.95-.27 2.667 2.826 4.929 2.55 2.265-.278 8.911-9.135 11.234-9.4 2.324-.262 2.067 6.99 3.031 6.895a381.5 381.5 0 0 1 1.51-.151l12.26-2.453s-2.145 6.13-2.299 7.818c.671.247 5.67-5.556 9.382-7.891 3.715-2.331 6.753-8.398 8.865-2.389 4.77-6.308 7.738-5.559 14.027-7.634 5.73-9.67 11.707-8.905 14.16-9.519 4.902 0 6.666.153 9.807 2.453.995-2.528-4.905-4.905-4.905-4.905s5.056-14.098 16.55-16.319c.612-3.064-9.501-9.502-6.743-15.556 10.573-.307 24.136-8.044 24.52-9.807-.69-4.443-5.136-5.593-4.905-9.807 2.758.306 12.26 0 12.26 0-7.355-12.26-2.452-14.711 0-26.97l7.354 2.452c4.905-7.356 4.906-7.357 14.713-9.81 2.528-14.173-4.14-15.4-4.905-29.421 13.024 2.375 5.21-19.997 24.52-19.615-2.453-29.423 4.902-22.067 7.354-41.682-9.042 0-15.476-9.884-14.863-10.65 9.807-12.26-4.6-15.246 10.112-25.054-7.356-17.163-9.96-15.785-2.604-32.948-29.421 7.356-12.259-7.356-49.036-14.712 4.902-7.356 14.71-19.613 4.902-26.97 2.452-4.902 6.36-7.432 6.36-12.335 0-3.678-8.026-18.217-6.627-26.856-5.268-8.41-5.862-12.299-7.088-17.203-2.452-14.71 0-24.518 0-26.97-2.452-4.903-7.356-9.807-12.26-12.259-4.902-2.452-6.512.23-11.416 2.682-2.376-.23.56-3.77-3.464-6.472-4.869.822-7.197-1.43-9.599-5.826-5.164-2.327-11.936-3.351-25.23-7.894-4.946-.864-6.682-1.414-10.973-2.027-1.8-.114-3.66 4.732-5.68 6.618-2.022 1.887-4.205.813-4.205.813 0-6.36-8.966-16.166-7.892-24.671-4.023-1.264-2.72 1.877-7.326 2.653-2.834.251-7.233-9.854-10.833-9.854-2.145-.384-30.34 14.863-38.54-7.74-1.38-1.763-7.753 3.695-11.647-2.3-2.758-11.11-7.662-7.203-12.872-12.413-7.049.767-11.954 4.6-19.31 2.684-9.807-2.681-33.175-4.982-43.52-5.365-1.379-5.862-2.629-10.428-4.04-10.665zM164.095 164.099c-2.068.078-4.597-.305-3.754 2.99 1.227-2.53 3.678-2.836 3.754-2.99zm-1.417 7.644c-1.017.025-2.97.382-2.337 2.854 1.227-2.53 2.835-2.682 2.91-2.836 0 0-.234-.027-.573-.018zm15.546 36.262c.077.153 1.148.534 2.374 3.064.844-3.295-2.374-3.064-2.374-3.064zm560.834 144.504c-.716 1.611-.589 3.222.152 4.37 1.329-1.993.206-2.914-.152-4.37z" fill="#fff" stroke="#fff" stroke-width="2" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1 @@
<svg width="800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" height="500"><path fill="#74acdf" d="M0 0h800v500H0z"/><path fill="#fff" d="M0 166.67h800v166.67H0z"/><g id="c"><path id="a" stroke-width="1.112" stroke="#85340a" fill="#f6b40e" d="m396.84 251.31 28.454 61.992s.49 1.185 1.28.859c.79-.327.299-1.512.299-1.512l-23.715-63.956m-.68 24.12c-.347 9.428 5.452 14.613 4.694 23.032-.757 8.42 3.867 13.18 4.94 16.454 1.073 3.274-1.16 5.232-.198 5.698.963.466 3.07-2.12 2.383-6.775-.687-4.655-4.22-6.037-3.39-16.32.83-10.283-4.206-12.678-2.98-22.058"/><use xlink:href="#a" transform="rotate(22.5 400 250)"/><use xlink:href="#a" transform="rotate(45 400 250)"/><use xlink:href="#a" transform="rotate(67.5 400 250)"/><path id="b" fill="#85340a" d="M404.31 274.41c.453 9.054 5.587 13.063 4.579 21.314 2.213-6.525-3.124-11.583-2.82-21.22m-7.649-23.757 19.487 42.577-16.329-43.887"/><use xlink:href="#b" transform="rotate(22.5 400 250)"/><use xlink:href="#b" transform="rotate(45 400 250)"/><use xlink:href="#b" transform="rotate(67.5 400 250)"/></g><use xlink:href="#c" transform="rotate(90 400 250)"/><use xlink:href="#c" transform="rotate(180 400 250)"/><use xlink:href="#c" transform="rotate(270 400 250)"/><circle r="27.778" stroke="#85340a" cy="250" cx="400" stroke-width="1.5" fill="#f6b40e"/><path id="h" fill="#843511" d="M409.47 244.06c-1.897 0-3.713.822-4.781 2.531 2.136 1.923 6.856 2.132 10.062-.219a7.333 7.333 0 0 0-5.281-2.312zm-.031.438c1.846-.034 3.571.814 3.812 1.656-2.136 2.35-5.55 2.146-7.687.437.935-1.495 2.439-2.067 3.875-2.094z"/><use xlink:href="#d" transform="matrix(-1 0 0 1 800.25 0)"/><use xlink:href="#e" transform="matrix(-1 0 0 1 800.25 0)"/><use xlink:href="#f" transform="translate(18.862)"/><use xlink:href="#g" transform="matrix(-1 0 0 1 800.25 0)"/><path d="M395.75 253.84c-.913.167-1.563.977-1.563 1.906 0 1.062.878 1.906 1.938 1.906a1.89 1.89 0 0 0 1.563-.812c.739.556 1.764.615 2.312.625.084.002.193 0 .25 0 .548-.01 1.573-.069 2.313-.625.36.516.935.812 1.562.812 1.06 0 1.938-.844 1.938-1.906 0-.929-.65-1.74-1.563-1.906.513.18.844.676.844 1.219a1.28 1.28 0 0 1-1.281 1.281c-.68 0-1.242-.54-1.282-1.219-.208.417-1.034 1.655-2.656 1.719-1.622-.064-2.447-1.302-2.656-1.719-.04.679-.6 1.219-1.281 1.219a1.28 1.28 0 0 1-1.281-1.281c0-.542.33-1.038.843-1.219zM397.84 259.53c-2.138 0-2.983 1.937-4.906 3.219 1.068-.427 1.91-1.27 3.406-2.125 1.496-.855 2.772.187 3.625.187h.031c.853 0 2.13-1.041 3.625-.187 1.497.856 2.369 1.698 3.438 2.125-1.924-1.282-2.8-3.219-4.938-3.219-.426 0-1.271.23-2.125.656h-.031c-.853-.426-1.698-.656-2.125-.656z" fill="#85340a"/><path d="M397.12 262.06c-.844.037-1.96.207-3.563.688 3.848-.855 4.697.437 6.407.437h.03c1.71 0 2.56-1.292 6.407-.438-4.274-1.282-5.124-.437-6.406-.437h-.031c-.802 0-1.437-.312-2.844-.25z" fill="#85340a"/><path d="M393.75 262.72c-.248.003-.519.005-.813.031 4.488.428 2.331 3 7.032 3h.03c4.702 0 2.575-2.572 7.063-3-4.7-.426-3.214 2.344-7.062 2.344h-.031c-3.608 0-2.496-2.421-6.22-2.375zM403.85 269.66a3.848 3.848 0 0 0-3.846-3.846 3.848 3.848 0 0 0-3.847 3.846 3.955 3.955 0 0 1 3.847-3.04 3.952 3.952 0 0 1 3.846 3.04z" fill="#85340a"/><path id="e" fill="#85340a" d="M382.73 244.02c4.915-4.273 11.11-4.915 14.53-1.709.837 1.121 1.373 2.32 1.593 3.57.43 2.433-.33 5.062-2.236 7.756.215-.001.643.212.856.427 1.697-3.244 2.297-6.577 1.74-9.746a13.815 13.815 0 0 0-.67-2.436c-4.7-3.845-11.11-4.272-15.81 2.138z"/><path id="d" fill="#85340a" d="M390.42 242.74c2.777 0 3.419.642 4.7 1.71 1.284 1.068 1.924.854 2.137 1.068.213.215 0 .854-.426.64s-1.284-.64-2.564-1.708c-1.283-1.07-2.563-1.069-3.846-1.069-3.846 0-5.983 3.205-6.41 2.991-.426-.214 2.137-3.632 6.41-3.632z"/><use xlink:href="#h" transform="translate(-19.181)"/><circle id="f" cy="246.15" cx="390.54" r="1.923" fill="#85340a"/><path id="g" fill="#85340a" d="M385.29 247.44c3.633 2.778 7.265 2.564 9.402 1.282 2.136-1.282 2.136-1.709 1.71-1.709-.427 0-.853.427-2.564 1.281-1.71.856-4.273.856-8.546-.854z"/></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600"><path fill="#c8102e" d="M0 0h900v600H0z"/><path fill="#fff" d="M0 200h900v200H0z"/></svg>

After

Width:  |  Height:  |  Size: 154 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280" height="640" viewBox="0 0 10080 5040"><defs><clipPath id="a"><path d="M0 0h6v3H0z"/></clipPath><clipPath id="b"><path d="M0 0v1.5h6V3zm6 0H3v3H0z"/></clipPath><path id="c" d="m0-360 69.421 215.845 212.038-80.301L155.99-35.603l194.985 115.71-225.881 19.651 31.105 224.59L0 160l-156.198 164.349 31.105-224.59-225.881-19.651 194.986-115.711-125.471-188.853 212.038 80.301z"/><path id="d" d="M0-210 54.86-75.508l144.862 10.614L88.765 28.842l34.67 141.052L0 93.334l-123.435 76.56 34.67-141.052-110.957-93.736L-54.86-75.508z"/></defs><path fill="#012169" d="M0 0h10080v5040H0z"/><path d="m0 0 6 3m0-3L0 3" stroke="#fff" stroke-width=".6" clip-path="url(#a)" transform="scale(840)"/><path d="m0 0 6 3m0-3L0 3" stroke="#e4002b" stroke-width=".4" clip-path="url(#b)" transform="scale(840)"/><path d="M2520 0v2520M0 1260h5040" stroke="#fff" stroke-width="840"/><path d="M2520 0v2520M0 1260h5040" stroke="#e4002b" stroke-width="504"/><g fill="#fff"><use xlink:href="#c" transform="matrix(2.1 0 0 2.1 2520 3780)"/><use xlink:href="#c" x="7560" y="4200"/><use xlink:href="#c" x="6300" y="2205"/><use xlink:href="#c" x="7560" y="840"/><use xlink:href="#c" x="8680" y="1869"/><use xlink:href="#d" x="8064" y="2730"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600" viewBox="0 0 27 18"><path fill="#418fde" d="M0 0h27v18H0V0z"/><path fill="#ffd100" d="M0 12h27v1H0v1h27v1H0v-3z"/><path fill="#EF3340" stroke="#FFF" stroke-width=".2" stroke-miterlimit="10" d="M4.625 3.375 4 1.35l-.625 2.025L1.35 4l2.025.625L4 6.65l.625-2.025L6.65 4z"/></svg>

After

Width:  |  Height:  |  Size: 341 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="520" height="340" viewBox="0 0 52 34"><path d="M0 0h52v34H0Z" fill="#0064AD"/><path d="M0 17h52M21 0v34" stroke-width="10" stroke="#FFD300"/><path d="M0 17h52M21 0v34" stroke-width="4" stroke="#DA0E15"/></svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="600"><path fill="#509e2f" d="M0 0h1200v600H0z"/><path fill="#ef3340" d="M0 0h1200v400H0z"/><path fill="#00b5e2" d="M0 0h1200v200H0z"/><circle cx="570" cy="300" r="90" fill="#fff"/><circle cx="590" cy="300" r="75" fill="#ef3340"/><path d="m670 250 9.567 26.903 25.788-12.258-12.258 25.788L720 300l-26.903 9.567 12.258 25.788-25.788-12.258L670 350l-9.567-26.903-25.788 12.258 12.258-25.788L620 300l26.903-9.567-12.258-25.788 25.788 12.258z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 519 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800" height="400" viewBox="0 0 16 8"><path fill="#002395" d="M0 0h16v8H0z"/><path d="M4.24 0h8v8z" fill="#fecb00"/><g id="b"><path d="M2.353.525 2.8-.85 3.247.525l-1.17-.85h1.446z" fill="#fff" id="a"/><use xlink:href="#a" x="1" y="1"/><use xlink:href="#a" x="2" y="2"/></g><use xlink:href="#b" x="3" y="3"/><use xlink:href="#b" x="6" y="6"/></svg>

After

Width:  |  Height:  |  Size: 437 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1500" height="1000" viewBox="0 0 24000 16000"><path fill="#00267f" d="M0 0h24000v16000H0z"/><path fill="#ffc726" d="M8000 0h8000v16000H8000z"/><path id="a" fill="#000" d="M12000 4124c-260 709-525 1447-1092 2012 176-58 484-110 682-105v2982l-842 125c-30-3-40-50-40-114-81-926-300-1704-552-2509-18-110-337-530-91-456 30 4 359 138 307 74-448-464-1103-798-1739-897-56-14-89 14-39 79 844 1299 1550 2832 1544 4651 328 0 1123-194 1452-194v2104h415l95-5876z"/><use xlink:href="#a" transform="matrix(-1 0 0 1 24000 0)"/></svg>

After

Width:  |  Height:  |  Size: 606 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 6"><path fill="#006a4e" d="M0 0h10v6H0z"/><circle cx="4.5" cy="3" r="2" fill="#f42a41"/></svg>

After

Width:  |  Height:  |  Size: 150 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="780"><path fill="#ef3340" d="M0 0h900v780H0z"/><path fill="#fdda25" d="M0 0h600v780H0z"/><path d="M0 0h300v780H0z"/></svg>

After

Width:  |  Height:  |  Size: 182 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="900" height="600" version="1.0"><path fill="#009e49" d="M0 0h900v600H0z"/><path fill="#ef2b2d" d="M0 0h900v300H0z"/><g fill="#fcd116" transform="translate(450 300)"><g id="b"><path id="a" d="M0-100V0h50" transform="rotate(18 0 -100)"/><use xlink:href="#a" transform="scale(-1 1)"/></g><use xlink:href="#b" transform="rotate(72)"/><use xlink:href="#b" transform="rotate(144)"/><use xlink:href="#b" transform="rotate(216)"/><use xlink:href="#b" transform="rotate(288)"/></g></svg>

After

Width:  |  Height:  |  Size: 568 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="600" viewBox="0 0 5 3"><path fill="#fff" d="M0 0h5v3H0z"/><path fill="#00966E" d="M0 1h5v2H0z"/><path fill="#D62612" d="M0 2h5v1H0z"/></svg>

After

Width:  |  Height:  |  Size: 201 B

Some files were not shown because too many files have changed in this diff Show More