feat: move competition setting to ffsaf site

This commit is contained in:
2024-08-09 12:09:37 +02:00
parent b766525000
commit 27dd22080c
43 changed files with 1616 additions and 34 deletions

View File

@@ -0,0 +1,48 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Date;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "compet")
public class CompetitionModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "system_type")
CompetitionSystem system;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club", referencedColumnName = "id")
ClubModel club;
String name;
String uuid;
Date date;
@ManyToMany
@JoinTable(name = "register",
uniqueConstraints = @UniqueConstraint(columnNames = {"id_competition", "id_membre"}),
joinColumns = @JoinColumn(name = "id_competition"),
inverseJoinColumns = @JoinColumn(name = "id_membre"))
List<MembreModel> insc;
String owner;
}

View File

@@ -0,0 +1,53 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "match")
public class MatchModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "system_type")
CompetitionSystem system;
Long systemId;
@ManyToOne
@JoinColumn(name = "c1", referencedColumnName = "id")
MembreModel c1_id = null;
String c1_str = null;
@ManyToOne
@JoinColumn(name = "c2", referencedColumnName = "id")
MembreModel c2_id = null;
String c2_str = null;
@Column(name = "id_poule")
Long poule;
long poule_ord = 0;
@ElementCollection
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
List<ScoreEmbeddable> scores = new ArrayList<>();
}

View File

@@ -0,0 +1,45 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "poule")
public class PouleModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "system_type")
CompetitionSystem system;
Long systemId;
String name = "";
@ManyToOne
@JoinColumn(name = "id_compet", referencedColumnName = "id")
CompetitionModel compet;
@OneToMany
@JoinColumn(name = "id_poule", referencedColumnName = "id")
List<MatchModel> matchs;
@OneToMany
@JoinColumn(name = "id_poule", referencedColumnName = "id")
List<TreeModel> tree;
Integer type;
}

View File

@@ -0,0 +1,39 @@
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 = "tree")
public class TreeModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "id_poule")
Long poule;
Integer level;
@ManyToOne
@JoinColumn(name = "match_id", referencedColumnName = "id")
MatchModel match;
@ManyToOne
@JoinColumn(referencedColumnName = "id")
TreeModel left;
@ManyToOne
@JoinColumn(referencedColumnName = "id")
TreeModel right;
}

View File

@@ -0,0 +1,9 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CompetitionRepository implements PanacheRepositoryBase<CompetitionModel, Long> {
}

View File

@@ -0,0 +1,9 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.MatchModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long> {
}

View File

@@ -0,0 +1,9 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.PouleModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PouleRepository implements PanacheRepositoryBase<PouleModel, Long> {
}

View File

@@ -0,0 +1,9 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.TreeModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class TreeRepository implements PanacheRepositoryBase<TreeModel, Long> {
}

View File

@@ -0,0 +1,120 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ApplicationScoped
public class CompetPermService {
@Inject
ServerCustom serverCustom;
@Inject
@CacheName("safca-config")
Cache cache;
@Inject
@CacheName("safca-have-access")
Cache cacheAccess;
@Inject
CompetitionRepository competitionRepository;
public Uni<SimpleCompet> getSafcaConfig(long id) {
return cache.get(id, k -> {
CompletableFuture<SimpleCompet> f = new CompletableFuture<>();
SReqCompet.getConfig(serverCustom.clients, id, f);
System.out.println("get config");
try {
return f.get(1500, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
});
}
public Uni<HashMap<Long, String>> getAllHaveAccess (String subject) {
return cacheAccess.get(subject, k -> {
CompletableFuture<HashMap<Long, String>> f = new CompletableFuture<>();
SReqCompet.getAllHaveAccess(serverCustom.clients, subject, f);
System.out.println("get all have access");
try {
return f.get(1500, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
});
}
public Uni<CompetitionModel> hasViewPerm(JsonWebToken idToken, SecurityIdentity sid, long id) {
return competitionRepository.findById(id).call(o -> (
idToken.getSubject().equals(o.getOwner()) || sid.getRoles().contains("federation_admin")) ?
Uni.createFrom().nullItem()
:
o.getSystem() == CompetitionSystem.SAFCA ?
hasSafcaViewPerm(idToken, sid, id)
: Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> {
if (!GroupeUtils.isInClubGroup(o.getClub().getId(), idToken))
throw new DForbiddenException();
})
));
}
public Uni<CompetitionModel> hasEditPerm(JsonWebToken idToken, SecurityIdentity sid, long id) {
return competitionRepository.findById(id).call(o -> (
idToken.getSubject().equals(o.getOwner()) || sid.getRoles().contains("federation_admin")) ?
Uni.createFrom().nullItem()
:
o.getSystem() == CompetitionSystem.SAFCA ?
hasSafcaEditPerm(idToken, sid, id)
: Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> {
if (!GroupeUtils.isInClubGroup(o.getClub().getId(), idToken))
throw new DForbiddenException();
})
));
}
private Uni<?> hasSafcaViewPerm(JsonWebToken idToken, SecurityIdentity sid, long id) {
return sid.getRoles().contains("safca_super_admin") ?
Uni.createFrom().nullItem()
:
getSafcaConfig(id).chain(Unchecked.function(o -> {
if (!o.admin().contains(UUID.fromString(idToken.getSubject())) && !o.table()
.contains(UUID.fromString(idToken.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}));
}
private Uni<?> hasSafcaEditPerm(JsonWebToken idToken, SecurityIdentity sid, long id) {
return sid.getRoles().contains("safca_super_admin") ?
Uni.createFrom().nullItem()
:
getSafcaConfig(id).chain(Unchecked.function(o -> {
if (!o.admin().contains(UUID.fromString(idToken.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}));
}
}

View File

@@ -0,0 +1,250 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.repository.ClubRepository;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.data.repository.PouleRepository;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
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.GroupeUtils;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import io.vertx.mutiny.core.Vertx;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.*;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class CompetitionService {
@Inject
CompetitionRepository repository;
@Inject
PouleRepository pouleRepository;
@Inject
MatchRepository matchRepository;
@Inject
KeycloakService keycloakService;
@Inject
ServerCustom serverCustom;
@Inject
CompetPermService permService;
@Inject
Vertx vertx;
public Uni<CompetitionData> getById(JsonWebToken idToken, SecurityIdentity sid, Long id) {
if (id == 0) {
return Uni.createFrom()
.item(new CompetitionData(null, "", "", new Date(), CompetitionSystem.SAFCA,
null, "", ""));
}
return permService.hasViewPerm(idToken, sid, id)
.map(CompetitionData::fromModel)
.chain(data ->
vertx.getOrCreateContext().executeBlocking(() -> {
keycloakService.getUser(UUID.fromString(data.getOwner()))
.ifPresent(user -> data.setOwner(user.getUsername()));
return data;
})
);
}
public Uni<List<CompetitionData>> getAll(JsonWebToken idToken, SecurityIdentity securityIdentity) {
return repository.listAll()
.chain(o ->
permService.getAllHaveAccess(idToken.getSubject())
.chain(map -> Uni.createFrom().item(o.stream()
.filter(p -> {
if (idToken.getSubject().equals(p.getOwner()))
return true;
if (p.getSystem() == CompetitionSystem.SAFCA) {
if (map.containsKey(p.getId()))
return map.get(p.getId()).equals("admin");
return securityIdentity.getRoles().contains("federation_admin")
|| securityIdentity.getRoles().contains("safca_super_admin");
}
return securityIdentity.getRoles().contains("federation_admin");
})
.map(CompetitionData::fromModel).toList())
));
}
public Uni<List<CompetitionData>> getAllSystem(JsonWebToken idToken, SecurityIdentity securityIdentity,
CompetitionSystem system) {
if (system == CompetitionSystem.SAFCA) {
return permService.getAllHaveAccess(idToken.getSubject())
.chain(map ->
repository.list("system = ?1", system)
.map(data -> data.stream()
.filter(p -> {
if (idToken.getSubject().equals(p.getOwner()))
return true;
if (map.containsKey(p.getId()))
return map.get(p.getId()).equals("admin");
return securityIdentity.getRoles().contains("federation_admin")
|| securityIdentity.getRoles().contains("safca_super_admin");
})
.map(CompetitionData::fromModel).toList())
);
}
return repository.list("system = ?1", system)
.map(data -> data.stream()
.filter(p -> {
if (idToken.getSubject().equals(p.getOwner()))
return true;
return securityIdentity.getRoles().contains("federation_admin") ||
GroupeUtils.isInClubGroup(p.getClub().getId(), idToken);
})
.map(CompetitionData::fromModel).toList());
}
public Uni<CompetitionData> addOrUpdate(JsonWebToken idToken, SecurityIdentity sid, CompetitionData data) {
if (data.getId() == null) {
return new ClubRepository().findById(data.getClub()).invoke(Unchecked.consumer(clubModel -> {
if (!GroupeUtils.isInClubGroup(clubModel.getId(), idToken))
throw new DForbiddenException();
})) // TODO check if user can create competition
.chain(clubModel -> {
CompetitionModel model = new CompetitionModel();
model.setId(null);
model.setSystem(data.getSystem());
model.setClub(clubModel);
model.setDate(data.getDate());
model.setInsc(new ArrayList<>());
model.setUuid(UUID.randomUUID().toString());
model.setName(data.getName());
model.setOwner(idToken.getSubject());
return Panache.withTransaction(() -> repository.persist(model));
}).map(CompetitionData::fromModel)
.call(__ -> permService.cacheAccess.invalidate(idToken.getSubject()));
} else {
return permService.hasEditPerm(idToken, sid, data.getId())
.chain(model -> {
model.setDate(data.getDate());
model.setName(data.getName());
return vertx.getOrCreateContext().executeBlocking(() ->
keycloakService.getUser(data.getOwner()).map(UserRepresentation::getId).orElse(null))
.invoke(Unchecked.consumer(newOwner -> {
if (newOwner == null)
throw new DBadRequestException("User " + data.getOwner() + " not found");
if (!newOwner.equals(model.getOwner())) {
if (!sid.getRoles().contains("federation_admin")
&& !sid.getRoles().contains("safca_super_admin")
&& !idToken.getSubject().equals(model.getOwner()))
throw new DForbiddenException();
model.setOwner(newOwner);
}
}))
.chain(__ -> Panache.withTransaction(() -> repository.persist(model)));
}).map(CompetitionData::fromModel)
.call(__ -> permService.cacheAccess.invalidate(idToken.getSubject()));
}
}
public Uni<?> delete(JsonWebToken idToken, SecurityIdentity sid, Long id) {
return repository.findById(id).invoke(Unchecked.consumer(c -> {
if (!idToken.getSubject().equals(c.getOwner()) || sid.getRoles().contains("federation_admin"))
throw new DForbiddenException();
}))
.call(competitionModel -> pouleRepository.list("compet = ?1", competitionModel)
.call(pouleModels -> Uni.join()
.all(pouleModels.stream()
.map(pouleModel -> Panache.withTransaction(
() -> matchRepository.delete("poule = ?1", pouleModel.getId())))
.toList())
.andCollectFailures()))
.call(competitionModel -> Panache.withTransaction(
() -> pouleRepository.delete("compet = ?1", competitionModel)))
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())))
.invoke(o -> SReqCompet.rmCompet(serverCustom.clients, id))
.call(__ -> permService.cache.invalidate(id));
}
public Uni<SimpleCompetData> getSafcaData(JsonWebToken idToken, SecurityIdentity sid, Long id) {
return permService.getSafcaConfig(id)
.call(Unchecked.function(o -> {
if (!idToken.getSubject().equals(o.owner())
&& !sid.getRoles().contains("federation_admin")
&& !sid.getRoles().contains("safca_super_admin")
&& !o.admin().contains(UUID.fromString(idToken.getSubject()))
&& !o.table().contains(UUID.fromString(idToken.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}))
.chain(simpleCompet -> {
SimpleCompetData data = SimpleCompetData.fromModel(simpleCompet);
return vertx.getOrCreateContext().executeBlocking(() -> {
data.setAdmin(simpleCompet.admin().stream().map(uuid -> keycloakService.getUser(uuid))
.filter(Optional::isPresent)
.map(user -> user.get().getUsername())
.toList());
data.setTable(simpleCompet.table().stream().map(uuid -> keycloakService.getUser(uuid))
.filter(Optional::isPresent)
.map(user -> user.get().getUsername())
.toList());
return data;
});
});
}
public Uni<?> setSafcaData(JsonWebToken idToken, SecurityIdentity sid, SimpleCompetData data) {
return permService.hasEditPerm(idToken, sid, data.getId())
.chain(__ -> vertx.getOrCreateContext().executeBlocking(() -> {
ArrayList<UUID> admin = new ArrayList<>();
ArrayList<UUID> table = new ArrayList<>();
for (String username : data.getAdmin()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException("User " + username + " not found");
admin.add(UUID.fromString(opt.get().getId()));
}
for (String username : data.getTable()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException("User " + username + " not found");
table.add(UUID.fromString(opt.get().getId()));
}
return new SimpleCompet(data.getId(), "", data.isShow_blason(),
data.isShow_flag(), admin, table);
}))
.invoke(simpleCompet -> SReqCompet.sendUpdate(serverCustom.clients, simpleCompet))
.call(simpleCompet -> permService.getSafcaConfig(data.getId())
.call(c -> Uni.join().all(Stream.concat(
Stream.concat(
c.admin().stream().filter(uuid -> !simpleCompet.admin().contains(uuid)),
simpleCompet.admin().stream().filter(uuid -> !c.admin().contains(uuid))),
Stream.concat(
c.table().stream().filter(uuid -> !simpleCompet.table().contains(uuid)),
simpleCompet.table().stream().filter(uuid -> !c.table().contains(uuid))))
.map(uuid -> permService.cacheAccess.invalidate(uuid.toString())).toList())
.andCollectFailures()))
.call(__ -> permService.cache.invalidate(data.getId()));
}
}

View File

@@ -24,6 +24,7 @@ import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ApplicationScoped
public class KeycloakService {
@@ -253,7 +254,7 @@ public class KeycloakService {
});
}
private Optional<UserRepresentation> getUser(String username) {
public Optional<UserRepresentation> getUser(String username) {
List<UserRepresentation> users = keycloak.realm(realm).users().searchByUsername(username, true);
if (users.isEmpty())
@@ -262,6 +263,15 @@ public class KeycloakService {
return Optional.of(users.get(0));
}
public Optional<UserRepresentation> getUser(UUID userId) {
UserResource user = keycloak.realm(realm).users().get(userId.toString());
if (user == null)
return Optional.empty();
else
return Optional.of(user.toRepresentation());
}
private String makeLogin(MembreModel model) {
return Normalizer.normalize(
(model.getFname().toLowerCase() + "." + model.getLname().toLowerCase()).replace(' ', '_'),

View File

@@ -0,0 +1,97 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel;
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.PouleRepository;
import fr.titionfire.ffsaf.rest.data.MatchData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;
import java.util.function.Consumer;
@WithSession
@ApplicationScoped
public class MatchService {
@Inject
MatchRepository repository;
@Inject
PouleRepository pouleRepository;
@Inject
CombRepository combRepository;
public Uni<MatchData> getById(Consumer<ClubModel> checkPerm, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Match not found"))
.call(data -> pouleRepository.findById(data.getPoule())
.invoke(data2 -> checkPerm.accept(data2.getCompet().getClub())))
.map(MatchData::fromModel);
}
public Uni<List<MatchData>> getAllByPoule(Consumer<ClubModel> checkPerm, CompetitionSystem system, Long id) {
return pouleRepository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.invoke(data -> checkPerm.accept(data.getCompet().getClub()))
.chain(data -> repository.list("poule = ?1", data.getId())
.map(o -> o.stream().map(MatchData::fromModel).toList()));
}
public Uni<MatchData> addOrUpdate(Consumer<ClubModel> checkPerm, CompetitionSystem system, MatchData data) {
return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult()
.chain(o -> {
if (o == null) {
return pouleRepository.findById(data.getPoule())
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.invoke(data2 -> checkPerm.accept(data2.getCompet().getClub()))
.map(pouleModel -> {
MatchModel model = new MatchModel();
model.setId(null);
model.setSystem(system);
model.setSystemId(data.getId());
model.setPoule(pouleModel.getId());
return model;
});
} else {
return pouleRepository.findById(data.getPoule())
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.invoke(data2 -> checkPerm.accept(data2.getCompet().getClub()))
.map(__ -> o);
}
}
)
.chain(o -> {
o.setC1_str(data.getC1_str());
o.setC2_str(data.getC2_str());
o.setPoule_ord(data.getPoule_ord());
o.setScores(data.getScores());
return Uni.createFrom().nullItem()
.chain(() -> (data.getC1_id() == null) ?
Uni.createFrom().nullItem() : combRepository.findById(data.getC1_id()))
.invoke(o::setC1_id)
.chain(() -> (data.getC1_id() == null) ?
Uni.createFrom().nullItem() : combRepository.findById(data.getC2_id()))
.invoke(o::setC2_id)
.chain(() -> Panache.withTransaction(() -> repository.persist(o)));
})
.map(MatchData::fromModel);
}
public Uni<?> delete(Consumer<ClubModel> checkPerm, Long id) {
return repository.findById(id)
.onItem().ifNull().failWith(() -> new RuntimeException("Match not found"))
.call(data -> pouleRepository.findById(data.getPoule())
.invoke(data2 -> checkPerm.accept(data2.getCompet().getClub()))
.chain(data2 -> Panache.withTransaction(() -> repository.delete("id", data.getId()))));
}
}

View File

@@ -0,0 +1,88 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.PouleModel;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.PouleRepository;
import fr.titionfire.ffsaf.data.repository.TreeRepository;
import fr.titionfire.ffsaf.rest.data.PouleData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@WithSession
@ApplicationScoped
public class PouleService {
@Inject
PouleRepository repository;
@Inject
CompetitionRepository competRepository;
@Inject
TreeRepository treeRepository;
public Uni<PouleData> getById(Consumer<ClubModel> checkPerm, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system)
.firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.invoke(data -> checkPerm.accept(data.getCompet().getClub()))
.map(PouleData::fromModel);
}
public Uni<List<PouleData>> getAll(JsonWebToken idToken, SecurityIdentity securityIdentity,
CompetitionSystem system) {
return repository.list("system = ?1", system)
.map(data -> data.stream()
.filter(p -> securityIdentity.getRoles().contains("federation_admin") ||
GroupeUtils.isInClubGroup(p.getCompet().getClub().getId(), idToken))
.map(PouleData::fromModel).toList());
}
public Uni<PouleData> addOrUpdate(Consumer<ClubModel> checkPerm, CompetitionSystem system, PouleData data) {
return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult()
.chain(o -> {
if (o == null) {
return competRepository.findById(data.getCompet())
.onItem().ifNull().failWith(() -> new RuntimeException("Competition not found"))
.invoke(o2 -> checkPerm.accept(o2.getClub()))
.chain(competitionModel -> {
PouleModel model = new PouleModel();
model.setId(null);
model.setSystem(system);
model.setSystemId(data.getId());
model.setCompet(competitionModel);
model.setName(data.getName());
model.setMatchs(new ArrayList<>());
model.setTree(new ArrayList<>());
model.setType(data.getType());
return Panache.withTransaction(() -> repository.persist(model));
});
} else {
o.setName(data.getName());
o.setType(data.getType());
return Panache.withTransaction(() -> repository.persist(o));
}
}).map(PouleData::fromModel);
}
public Uni<?> delete(Consumer<ClubModel> checkPerm, Long id) {
return repository.findById(id)
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.invoke(data -> checkPerm.accept(data.getCompet().getClub()))
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())));
}
}

View File

@@ -40,7 +40,7 @@ public class Client_Thread extends Thread {
private boolean isAuth;
private final HashMap<UUID, JsonConsumer<Object>> waitResult = new HashMap<>();
private final HashMap<UUID, JsonConsumer<?>> waitResult = new HashMap<>();
public Client_Thread(ServerCustom serv, Socket s, PublicKey publicKey) throws IOException {
this.serv = serv;
@@ -162,7 +162,7 @@ public class Client_Thread extends Thread {
sendReq(object, type, null);
}
public void sendReq(Object object, String code, JsonConsumer<Object> consumer) {
public void sendReq(Object object, String code, JsonConsumer<?> consumer) {
UUID uuid;
do {
uuid = UUID.randomUUID();

View File

@@ -0,0 +1,10 @@
package fr.titionfire.ffsaf.net2.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import java.util.List;
import java.util.UUID;
@RegisterForReflection
public record SimpleCompet(long id, String owner, boolean show_blason, boolean show_flag, List<UUID> admin, List<UUID> table) {
}

View File

@@ -0,0 +1,40 @@
package fr.titionfire.ffsaf.net2.request;
import fr.titionfire.ffsaf.net2.Client_Thread;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.utils.JsonConsumer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
public class SReqCompet {
public static void sendUpdate(ArrayList<Client_Thread> client_Thread, SimpleCompet compet) {
for (Client_Thread client : client_Thread) {
client.sendNotify(compet, "sendConfig");
}
}
public static void getConfig(ArrayList<Client_Thread> client_Thread, long id_compet,
CompletableFuture<SimpleCompet> future) {
if (client_Thread.isEmpty()) return;
client_Thread.get(0).sendReq(id_compet, "getConfig",
new JsonConsumer<>(SimpleCompet.class, future::complete));
}
public static void getAllHaveAccess(ArrayList<Client_Thread> client_Thread, String userId,
CompletableFuture<HashMap<Long, String>> future) {
if (client_Thread.isEmpty()) return;
client_Thread.get(0).sendReq(userId, "getAllHaveAccess",
new JsonConsumer<>(HashMap.class, future::complete));
}
public static void rmCompet(ArrayList<Client_Thread> client_Thread, long id_compet) {
for (Client_Thread client : client_Thread) {
client.sendNotify(id_compet, "rmCompet");
}
}
}

View File

@@ -30,7 +30,6 @@ public class AffiliationEndpoints {
AffiliationService service;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -35,7 +35,6 @@ public class AffiliationRequestEndpoints {
AffiliationService service;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -28,7 +28,7 @@ public class AuthEndpoints {
SecurityIdentity securityIdentity;
@Inject
JsonWebToken accessToken;
JsonWebToken IdToken;
@GET
@Produces(MediaType.TEXT_PLAIN)
@@ -53,7 +53,7 @@ public class AuthEndpoints {
@APIResponse(responseCode = "401", description = "Utilisateur non authentifié")
})
public UserInfo userinfo() {
return UserInfo.makeUserInfo(accessToken, securityIdentity);
return UserInfo.makeUserInfo(IdToken, securityIdentity);
}
@GET

View File

@@ -15,7 +15,6 @@ import fr.titionfire.ffsaf.utils.Contact;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import fr.titionfire.ffsaf.utils.PageResult;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
@@ -46,7 +45,6 @@ public class ClubEndpoints {
ClubService clubService;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -0,0 +1,84 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.CompetitionService;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.List;
@Path("api/competition/")
public class CompetitionEndpoints {
@Inject
CompetitionService service;
@Inject
JsonWebToken idToken;
@Inject
SecurityIdentity securityIdentity;
@GET
@Path("{id}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<CompetitionData> getById(@PathParam("id") Long id) {
return service.getById(idToken, securityIdentity, id);
}
@GET
@Path("{id}/safcaData")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<SimpleCompetData> getSafcaData(@PathParam("id") Long id) {
return service.getSafcaData(idToken, securityIdentity, id);
}
@GET
@Path("all")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CompetitionData>> getAll() {
return service.getAll(idToken, securityIdentity);
}
@GET
@Path("all/{system}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CompetitionData>> getAllSystem(@PathParam("system") CompetitionSystem system) {
return service.getAllSystem(idToken, securityIdentity, system);
}
@POST
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<CompetitionData> addOrUpdate(CompetitionData data) {
return service.addOrUpdate(idToken, securityIdentity, data);
}
@POST
@Path("/safcaData")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> setSafcaData(SimpleCompetData data) {
return service.setSafcaData(idToken, securityIdentity, data);
}
@DELETE
@Path("{id}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> delete(@PathParam("id") Long id) {
return service.delete(idToken, securityIdentity, id);
}
}

View File

@@ -32,7 +32,7 @@ public class CompteEndpoints {
KeycloakService service;
@Inject
JsonWebToken accessToken;
JsonWebToken idToken;
@Inject
SecurityIdentity securityIdentity;
@@ -53,8 +53,9 @@ public class CompteEndpoints {
})
public Uni<KeycloakService.UserCompteState> getCompte(@PathParam("id") String id) {
return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> {
if (!securityIdentity.getRoles().contains("federation_admin") && pair.getKey().groups().stream().map(GroupRepresentation::getPath)
.noneMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, accessToken)))
if (!securityIdentity.getRoles().contains("federation_admin") && pair.getKey().groups().stream()
.map(GroupRepresentation::getPath)
.noneMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, idToken)))
throw new DForbiddenException();
return pair;
})).map(Pair::getValue);

View File

@@ -29,7 +29,6 @@ public class LicenceEndpoints {
LicenceService licenceService;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -0,0 +1,69 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.domain.service.MatchService;
import fr.titionfire.ffsaf.rest.data.MatchData;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.List;
import java.util.function.Consumer;
@Authenticated
@Path("api/match/{system}/")
public class MatchEndpoints {
@PathParam("system")
private CompetitionSystem system;
@Inject
MatchService service;
@Inject
JsonWebToken idToken;
@Inject
SecurityIdentity securityIdentity;
Consumer<ClubModel> checkPerm = Unchecked.consumer(clubModel -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(clubModel.getId(),
idToken))
throw new DForbiddenException();
});
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<MatchData> getById(@PathParam("id") Long id) {
return service.getById(checkPerm, system, id);
}
@GET
@Path("getAllByPoule/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<MatchData>> getAllByPoule(@PathParam("id") Long id) {
return service.getAllByPoule(checkPerm, system, id);
}
@POST
@Produces(MediaType.APPLICATION_JSON)
public Uni<MatchData> addOrUpdate(MatchData data) {
return service.addOrUpdate(checkPerm, system, data);
}
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> delete(@PathParam("id") Long id) {
return service.delete(checkPerm, id);
}
}

View File

@@ -40,7 +40,6 @@ public class MembreAdminEndpoints {
String media;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -35,7 +35,6 @@ public class MembreClubEndpoints {
String media;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -40,7 +40,6 @@ public class MembreEndpoints {
String media;
@Inject
@IdToken
JsonWebToken idToken;
@Inject

View File

@@ -0,0 +1,68 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.domain.service.PouleService;
import fr.titionfire.ffsaf.rest.data.PouleData;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.List;
import java.util.function.Consumer;
@Authenticated
@Path("api/poule/{system}/")
public class PouleEndpoints {
@PathParam("system")
private CompetitionSystem system;
@Inject
PouleService service;
@Inject
JsonWebToken idToken;
@Inject
SecurityIdentity securityIdentity;
Consumer<ClubModel> checkPerm = Unchecked.consumer(clubModel -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(clubModel.getId(),
idToken))
throw new DForbiddenException();
});
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<PouleData> getById(@PathParam("id") Long id) {
return service.getById(checkPerm, system, id);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<PouleData>> getAll() {
return service.getAll(idToken, securityIdentity, system);
}
@POST
@Produces(MediaType.APPLICATION_JSON)
public Uni<PouleData> addOrUpdate(PouleData data) {
return service.addOrUpdate(checkPerm, system, data);
}
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> delete(@PathParam("id") Long id) {
return service.delete(checkPerm, id);
}
}

View File

@@ -0,0 +1,31 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
@Data
@AllArgsConstructor
@RegisterForReflection
public class CompetitionData {
private Long id;
private String name;
private String uuid;
private Date date;
private CompetitionSystem system;
private Long club;
private String clubName;
private String owner;
public static CompetitionData fromModel(CompetitionModel model) {
if (model == null)
return null;
return new CompetitionData(model.getId(), model.getName(), model.getUuid(), model.getDate(), model.getSystem(),
model.getClub().getId(), model.getClub().getName(), model.getOwner());
}
}

View File

@@ -0,0 +1,31 @@
package fr.titionfire.ffsaf.rest.data;
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.List;
@Data
@AllArgsConstructor
@RegisterForReflection
public class MatchData {
private Long id;
private Long c1_id;
private String c1_str;
private Long c2_id;
private String c2_str;
private Long poule;
private long poule_ord;
private List<ScoreEmbeddable> scores;
public static MatchData fromModel(MatchModel model) {
if (model == null)
return null;
return new MatchData(model.getSystemId(), model.getC1_id().getId(), model.getC1_str(), model.getC2_id().getId(),
model.getC2_str(), model.getPoule(), model.getPoule_ord(), model.getScores());
}
}

View File

@@ -0,0 +1,23 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.PouleModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class PouleData {
private Long id;
private String name;
private Long compet;
private Integer type;
public static PouleData fromModel(PouleModel model) {
if (model == null)
return null;
return new PouleData(model.getSystemId(), model.getName(), model.getCompet().getId(), model.getType());
}
}

View File

@@ -0,0 +1,28 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@RegisterForReflection
public class SimpleCompetData {
private long id;
private boolean show_blason;
private boolean show_flag;
private List<String> admin;
private List<String> table;
public static SimpleCompetData fromModel(SimpleCompet compet) {
if (compet == null)
return null;
return new SimpleCompetData(compet.id(), compet.show_blason(), compet.show_flag(),
new ArrayList<>(), new ArrayList<>());
}
}

View File

@@ -0,0 +1,28 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.TreeModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class TreeData {
private Long id;
private Long poule;
private Integer level;
private MatchData match;
private TreeData left;
private TreeData right;
private TreeData associatedNode;
public static TreeData fromModel(TreeModel model) {
if (model == null)
return null;
return new TreeData(model.getId(), model.getPoule(), model.getLevel(), MatchData.fromModel(model.getMatch()),
fromModel(model.getLeft()),
fromModel(model.getRight()), null);
}
}

View File

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

View File

@@ -0,0 +1,21 @@
package fr.titionfire.ffsaf.utils;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.Embeddable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Embeddable
public class ScoreEmbeddable {
int n_round;
int s1;
int s2;
}