diff --git a/pom.xml b/pom.xml index 525fd91..413b042 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,11 @@ io.quarkus quarkus-swagger-ui + + + io.quarkus + quarkus-cache + diff --git a/src/main/java/fr/titionfire/ExampleResource.java b/src/main/java/fr/titionfire/ExampleResource.java index 617736e..cc432e7 100644 --- a/src/main/java/fr/titionfire/ExampleResource.java +++ b/src/main/java/fr/titionfire/ExampleResource.java @@ -2,6 +2,7 @@ package fr.titionfire; import io.quarkus.oidc.IdToken; import io.quarkus.oidc.RefreshToken; +import io.quarkus.oidc.UserInfo; import io.quarkus.security.identity.SecurityIdentity; import jakarta.inject.Inject; import jakarta.ws.rs.GET; @@ -30,6 +31,9 @@ public class ExampleResource { @IdToken JsonWebToken idToken; + @Inject + UserInfo userInfo; + /** * Injection point for the Access Token issued by the OpenID Connect Provider */ @@ -59,7 +63,8 @@ public class ExampleResource { .append("") .append("").append("").append("").toString(); diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java new file mode 100644 index 0000000..c9f6f15 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java @@ -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 insc; + + String owner; +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java new file mode 100644 index 0000000..fba895a --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java @@ -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 scores = new ArrayList<>(); +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java new file mode 100644 index 0000000..693042e --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java @@ -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 matchs; + + @OneToMany + @JoinColumn(name = "id_poule", referencedColumnName = "id") + List tree; + + Integer type; +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java new file mode 100644 index 0000000..697e49d --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java @@ -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; +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/CompetitionRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/CompetitionRepository.java new file mode 100644 index 0000000..6c277e0 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/CompetitionRepository.java @@ -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 { +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java new file mode 100644 index 0000000..ab284c4 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/MatchRepository.java @@ -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 { +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/PouleRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/PouleRepository.java new file mode 100644 index 0000000..7535bd3 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/PouleRepository.java @@ -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 { +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java new file mode 100644 index 0000000..57bb22b --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/TreeRepository.java @@ -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 { +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java new file mode 100644 index 0000000..5ec36b6 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java @@ -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 getSafcaConfig(long id) { + return cache.get(id, k -> { + CompletableFuture 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> getAllHaveAccess (String subject) { + return cacheAccess.get(subject, k -> { + CompletableFuture> 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 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 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(); + })); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java new file mode 100644 index 0000000..b0997a0 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java @@ -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 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> 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> 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 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 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 admin = new ArrayList<>(); + ArrayList table = new ArrayList<>(); + for (String username : data.getAdmin()) { + Optional 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 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())); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java index ce78f51..a87c2e5 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java @@ -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 getUser(String username) { + public Optional getUser(String username) { List 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 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(' ', '_'), diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java new file mode 100644 index 0000000..7637444 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java @@ -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 getById(Consumer 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> getAllByPoule(Consumer 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 addOrUpdate(Consumer 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 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())))); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java new file mode 100644 index 0000000..2f23ffd --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java @@ -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 getById(Consumer 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> 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 addOrUpdate(Consumer 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 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()))); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/net2/Client_Thread.java b/src/main/java/fr/titionfire/ffsaf/net2/Client_Thread.java index 67972c5..7651c33 100644 --- a/src/main/java/fr/titionfire/ffsaf/net2/Client_Thread.java +++ b/src/main/java/fr/titionfire/ffsaf/net2/Client_Thread.java @@ -40,7 +40,7 @@ public class Client_Thread extends Thread { private boolean isAuth; - private final HashMap> waitResult = new HashMap<>(); + private final HashMap> 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 consumer) { + public void sendReq(Object object, String code, JsonConsumer consumer) { UUID uuid; do { uuid = UUID.randomUUID(); diff --git a/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCompet.java b/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCompet.java new file mode 100644 index 0000000..54707fb --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCompet.java @@ -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 admin, List table) { +} diff --git a/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java b/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java new file mode 100644 index 0000000..c7aa379 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java @@ -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, SimpleCompet compet) { + for (Client_Thread client : client_Thread) { + client.sendNotify(compet, "sendConfig"); + } + } + + public static void getConfig(ArrayList client_Thread, long id_compet, + CompletableFuture 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, String userId, + CompletableFuture> 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, long id_compet) { + for (Client_Thread client : client_Thread) { + client.sendNotify(id_compet, "rmCompet"); + } + } + + +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java index 35a4492..1ebc52b 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java @@ -30,7 +30,6 @@ public class AffiliationEndpoints { AffiliationService service; @Inject - @IdToken JsonWebToken idToken; @Inject diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java index b8a8bb5..b4f5c7e 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java @@ -35,7 +35,6 @@ public class AffiliationRequestEndpoints { AffiliationService service; @Inject - @IdToken JsonWebToken idToken; @Inject diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java index d9d4127..9cb13ea 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java @@ -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 diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java index 115175f..c05a165 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java @@ -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 diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java new file mode 100644 index 0000000..e2b282f --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java @@ -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 getById(@PathParam("id") Long id) { + return service.getById(idToken, securityIdentity, id); + } + + @GET + @Path("{id}/safcaData") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni getSafcaData(@PathParam("id") Long id) { + return service.getSafcaData(idToken, securityIdentity, id); + } + + + @GET + @Path("all") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni> getAll() { + return service.getAll(idToken, securityIdentity); + } + + @GET + @Path("all/{system}") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni> getAllSystem(@PathParam("system") CompetitionSystem system) { + return service.getAllSystem(idToken, securityIdentity, system); + } + + @POST + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni 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); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java index 7a8ec62..f2bf094 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java @@ -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 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); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java index 55cf203..5eaabd9 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java @@ -29,7 +29,6 @@ public class LicenceEndpoints { LicenceService licenceService; @Inject - @IdToken JsonWebToken idToken; @Inject diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java new file mode 100644 index 0000000..3b0345b --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java @@ -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 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 getById(@PathParam("id") Long id) { + return service.getById(checkPerm, system, id); + } + + @GET + @Path("getAllByPoule/{id}") + @Produces(MediaType.APPLICATION_JSON) + public Uni> getAllByPoule(@PathParam("id") Long id) { + return service.getAllByPoule(checkPerm, system, id); + } + + @POST + @Produces(MediaType.APPLICATION_JSON) + public Uni 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); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java index 485d4d1..b65df9b 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java @@ -40,7 +40,6 @@ public class MembreAdminEndpoints { String media; @Inject - @IdToken JsonWebToken idToken; @Inject diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java index 3ae36e4..41e24c5 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java @@ -35,7 +35,6 @@ public class MembreClubEndpoints { String media; @Inject - @IdToken JsonWebToken idToken; @Inject diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java index 7d73fc3..e06a2c9 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java @@ -40,7 +40,6 @@ public class MembreEndpoints { String media; @Inject - @IdToken JsonWebToken idToken; @Inject diff --git a/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java new file mode 100644 index 0000000..63173f5 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java @@ -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 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 getById(@PathParam("id") Long id) { + return service.getById(checkPerm, system, id); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Uni> getAll() { + return service.getAll(idToken, securityIdentity, system); + } + + @POST + @Produces(MediaType.APPLICATION_JSON) + public Uni 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); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java new file mode 100644 index 0000000..eae4c16 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java @@ -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()); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java new file mode 100644 index 0000000..f7ce375 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java @@ -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 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()); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java new file mode 100644 index 0000000..f89da27 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java @@ -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()); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java new file mode 100644 index 0000000..3adaac7 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java @@ -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 admin; + private List 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<>()); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java new file mode 100644 index 0000000..cd421df --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java @@ -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); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java b/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java new file mode 100644 index 0000000..79dbeeb --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java @@ -0,0 +1,5 @@ +package fr.titionfire.ffsaf.utils; + +public enum CompetitionSystem { + SAFCA, +} diff --git a/src/main/java/fr/titionfire/ffsaf/utils/ScoreEmbeddable.java b/src/main/java/fr/titionfire/ffsaf/utils/ScoreEmbeddable.java new file mode 100644 index 0000000..9468597 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/utils/ScoreEmbeddable.java @@ -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; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2ceeada..c0346aa 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -21,7 +21,7 @@ quarkus.oidc.auth-server-url=https://auth.safca.fr/realms/safca quarkus.oidc.client-id=backend quarkus.oidc.credentials.secret=secret quarkus.oidc.tls.verification=required -quarkus.oidc.application-type=web-app +quarkus.oidc.application-type=hybrid quarkus.oidc.roles.source=accesstoken quarkus.http.limits.max-body-size=10M diff --git a/src/main/webapp/src/App.jsx b/src/main/webapp/src/App.jsx index 85662c9..bc53786 100644 --- a/src/main/webapp/src/App.jsx +++ b/src/main/webapp/src/App.jsx @@ -13,6 +13,7 @@ import 'react-toastify/dist/ReactToastify.css'; import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx"; import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx"; import {MePage} from "./pages/MePage.jsx"; +import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx"; const router = createBrowserRouter([ { @@ -47,6 +48,11 @@ const router = createBrowserRouter([ } ] }, + { + path: 'competition', + element: , + children: getCompetitionChildren() + }, { path: 'me', element: diff --git a/src/main/webapp/src/components/ClubSelect.jsx b/src/main/webapp/src/components/ClubSelect.jsx index fe23be9..9d76665 100644 --- a/src/main/webapp/src/components/ClubSelect.jsx +++ b/src/main/webapp/src/components/ClubSelect.jsx @@ -2,15 +2,15 @@ import {LoadingProvider, useLoadingSwitcher} from "../hooks/useLoading.jsx"; import {useFetch} from "../hooks/useFetch.js"; import {AxiosError} from "./AxiosError.jsx"; -export function ClubSelect({defaultValue, name, na = false}) { +export function ClubSelect({defaultValue, name, na = false, disabled = false}) { return
- +
} -function ClubSelect_({defaultValue, name, na}) { +function ClubSelect_({defaultValue, name, na, disabled}) { const setLoading = useLoadingSwitcher() const {data, error} = useFetch(`/club/no_detail`, setLoading, 1) @@ -18,7 +18,7 @@ function ClubSelect_({defaultValue, name, na}) { {data ?
- +
+
Configuration SAFCA
+
+ +
+
+ +
+ Afficher le blason du club sur les écrans +
+ +
+
+ +
+ Afficher le pays du combattant sur les écrans +
+ + Administrateur +
    + {state.map((d, index) => { + return
    + { + dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}}) + }}/> + +
    + })} +
    + +
    +
+ +
+ Table +
    + {state2.map((d, index) => { + return
    + { + dispatch2({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}}) + }}/> + +
    + })} +
    + +
    +
+
+
+
+ +
+
+
+ + : error && } + +} + +function Content({data}) { + const navigate = useNavigate(); + + const handleSubmit = (event) => { + event.preventDefault(); + + const out = {} + out['id'] = (data.id === "") ? null : data.id + out['name'] = event.target.name?.value + out['date'] = event.target.date?.value + out['system'] = event.target.system?.value + out['club'] = event.target.club?.value + out['owner'] = event.target.owner?.value + + toast.promise( + apiAxios.post(`/competition`, out), + { + pending: "Enregistrement du club en cours", + success: "Club enregistrée avec succès 🎉", + error: { + render({data}) { + return errFormater(data, "Échec de l'enregistrement du club") + } + }, + } + ).then(data => { + navigate("/competition/" + data.id) + }) + } + + return
+
+ +
{data.id ? "Edition competition" : "Création competition"}
+
+ + + +
+ Date + +
+ + {data.id !== null && } + + + +
+ +
+ +
+ +
+
+ +
+
+
+
+} \ No newline at end of file diff --git a/src/main/webapp/src/pages/competition/CompetitionList.jsx b/src/main/webapp/src/pages/competition/CompetitionList.jsx new file mode 100644 index 0000000..0f885f9 --- /dev/null +++ b/src/main/webapp/src/pages/competition/CompetitionList.jsx @@ -0,0 +1,61 @@ +import {useNavigate} from "react-router-dom"; +import {useLoadingSwitcher} from "../../hooks/useLoading.jsx"; +import {useFetch} from "../../hooks/useFetch.js"; +import {AxiosError} from "../../components/AxiosError.jsx"; +import {ThreeDots} from "react-loader-spinner"; + + +export function CompetitionList() { + const navigate = useNavigate(); + const setLoading = useLoadingSwitcher() + const {data, error} = useFetch(`/competition/all`, setLoading, 1) + + + return <> +
+
+ {data + ? + : error + ? + : + } +
+
+ +} + +function MakeCentralPanel({data, navigate}) { + + return <> +
+ +
+
+
+ {data.map(req => ())} +
+
+ +} + +function MakeRow({data, navigate}) { + return
navigate("" + data.id)}> +
+
{data.name}
+ {data.date.split('T')[0]} +
+ {data.clubName}
{data.system}
+
+} + +function Def() { + return
+
  • +
  • +
  • +
  • +
  • +
    +} \ No newline at end of file diff --git a/src/main/webapp/src/pages/competition/CompetitionRoot.jsx b/src/main/webapp/src/pages/competition/CompetitionRoot.jsx new file mode 100644 index 0000000..dadae75 --- /dev/null +++ b/src/main/webapp/src/pages/competition/CompetitionRoot.jsx @@ -0,0 +1,26 @@ +import {LoadingProvider} from "../../hooks/useLoading.jsx"; +import {Outlet} from "react-router-dom"; +import {CompetitionList} from "./CompetitionList.jsx"; +import {CompetitionEdit} from "./CompetitionEdit.jsx"; + +export function CompetitionRoot() { + return <> +

    Competition

    + + + + +} + +export function getCompetitionChildren() { + return [ + { + path: '', + element: + }, + { + path: ':id', + element: + } + ] +} \ No newline at end of file