feat: move competition setting to ffsaf site
This commit is contained in:
parent
b766525000
commit
27dd22080c
5
pom.xml
5
pom.xml
@ -124,6 +124,11 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-swagger-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-cache</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@ -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("<body>")
|
||||
.append("<ul>");
|
||||
|
||||
|
||||
System.out.println(idToken);
|
||||
System.out.println(accessToken);
|
||||
Object userName = this.idToken.getClaim("preferred_username");
|
||||
|
||||
if (userName != null) {
|
||||
@ -69,25 +74,17 @@ public class ExampleResource {
|
||||
response.append("<li>username: ").append(this.idToken.toString()).append("</li>");
|
||||
}
|
||||
|
||||
Object scopes = this.accessToken.getClaim("scope");
|
||||
/*Object scopes = this.accessToken.getClaim("scope");
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>scopes: ").append(scopes.toString()).append("</li>");
|
||||
}
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>scopes: ").append(this.accessToken.toString()).append("</li>");
|
||||
}
|
||||
response.append("<li>scopes: ").append(this.accessToken.toString()).append("</li>");
|
||||
response.append("<li>scopes: ").append(this.accessToken.getClaim("user_groups").toString()).append("</li>");*/
|
||||
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>scopes: ").append(this.accessToken.getClaim("user_groups").toString()).append("</li>");
|
||||
}
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>getRoles: ").append(this.securityIdentity.getRoles()).append("</li>");
|
||||
}
|
||||
|
||||
response.append("<li>getRoles: ").append(this.securityIdentity.getRoles()).append("</li>");
|
||||
response.append("<li>refresh_token: ").append(refreshToken.getToken() != null).append("</li>");
|
||||
|
||||
return response.append("</ul>").append("</body>").append("</html>").toString();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
53
src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java
Normal file
53
src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java
Normal 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<>();
|
||||
}
|
||||
45
src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java
Normal file
45
src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java
Normal 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;
|
||||
}
|
||||
39
src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java
Normal file
39
src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java
Normal 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;
|
||||
}
|
||||
@ -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> {
|
||||
}
|
||||
@ -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> {
|
||||
}
|
||||
@ -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> {
|
||||
}
|
||||
@ -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> {
|
||||
}
|
||||
@ -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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -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()));
|
||||
}
|
||||
}
|
||||
@ -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(' ', '_'),
|
||||
|
||||
@ -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()))));
|
||||
}
|
||||
}
|
||||
@ -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())));
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -30,7 +30,6 @@ public class AffiliationEndpoints {
|
||||
AffiliationService service;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
|
||||
@ -35,7 +35,6 @@ public class AffiliationRequestEndpoints {
|
||||
AffiliationService service;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -29,7 +29,6 @@ public class LicenceEndpoints {
|
||||
LicenceService licenceService;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
|
||||
69
src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java
Normal file
69
src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,6 @@ public class MembreAdminEndpoints {
|
||||
String media;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
|
||||
@ -35,7 +35,6 @@ public class MembreClubEndpoints {
|
||||
String media;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
|
||||
@ -40,7 +40,6 @@ public class MembreEndpoints {
|
||||
String media;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
|
||||
68
src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java
Normal file
68
src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
31
src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java
Normal file
31
src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java
Normal 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());
|
||||
}
|
||||
}
|
||||
23
src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java
Normal file
23
src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@ -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<>());
|
||||
}
|
||||
}
|
||||
28
src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java
Normal file
28
src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
public enum CompetitionSystem {
|
||||
SAFCA,
|
||||
}
|
||||
21
src/main/java/fr/titionfire/ffsaf/utils/ScoreEmbeddable.java
Normal file
21
src/main/java/fr/titionfire/ffsaf/utils/ScoreEmbeddable.java
Normal 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;
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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: <CompetitionRoot/>,
|
||||
children: getCompetitionChildren()
|
||||
},
|
||||
{
|
||||
path: 'me',
|
||||
element: <MePage/>
|
||||
|
||||
@ -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 <LoadingProvider>
|
||||
<div className="input-group mb-3">
|
||||
<ClubSelect_ defaultValue={defaultValue} name={name} na={na}/>
|
||||
<ClubSelect_ defaultValue={defaultValue} name={name} na={na} disabled={disabled}/>
|
||||
</div>
|
||||
</LoadingProvider>
|
||||
}
|
||||
|
||||
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
|
||||
? <div className="input-group mb-3">
|
||||
<label className="input-group-text" id="inputGroupSelect02">Club</label>
|
||||
<select className="form-select" id="inputGroupSelect02"
|
||||
<select className="form-select" id="inputGroupSelect02" disabled={disabled}
|
||||
defaultValue={defaultValue? defaultValue : -1} name={name}>
|
||||
<option>Sélectionner...</option>
|
||||
{na && <option value={-1}>-- Non licencier --</option>}
|
||||
|
||||
270
src/main/webapp/src/pages/competition/CompetitionEdit.jsx
Normal file
270
src/main/webapp/src/pages/competition/CompetitionEdit.jsx
Normal file
@ -0,0 +1,270 @@
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {Checkbox, CheckField, OptionField, TextField} from "../../components/MemberCustomFiels.jsx";
|
||||
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
||||
import {ConfirmDialog} from "../../components/ConfirmDialog.jsx";
|
||||
import {toast} from "react-toastify";
|
||||
import {apiAxios, errFormater} from "../../utils/Tools.js";
|
||||
import {useEffect, useReducer, useState} from "react";
|
||||
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faAdd, faPen, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function CompetitionEdit() {
|
||||
const {id} = useParams()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, refresh, error} = useFetch(`/competition/${id}`, setLoading, 1)
|
||||
|
||||
const handleRm = () => {
|
||||
toast.promise(
|
||||
apiAxios.delete(`/competition/${id}`),
|
||||
{
|
||||
pending: "Suppression de la competition en cours...",
|
||||
success: "Competition supprimé avec succès 🎉",
|
||||
error: {
|
||||
render({data}) {
|
||||
return errFormater(data, "Échec de la suppression de la competition")
|
||||
}
|
||||
},
|
||||
}
|
||||
).then(_ => {
|
||||
navigate("/competition")
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/competition")}>
|
||||
« retour
|
||||
</button>
|
||||
<div>
|
||||
{data
|
||||
? <div className="">
|
||||
<Content data={data} refresh={refresh}/>
|
||||
|
||||
{data.id !== null && <ContentSAFCA data2={data}/>}
|
||||
|
||||
{data.id !== null && <>
|
||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||
<button className="btn btn-danger btn-sm" data-bs-toggle="modal"
|
||||
data-bs-target="#confirm-delete">Supprimer la competition
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmDialog title="Supprimer la competition"
|
||||
message="Êtes-vous sûr de vouloir supprimer cette competition est tout les resultat associer?"
|
||||
onConfirm={handleRm}/>
|
||||
</>}
|
||||
</div>
|
||||
: error && <AxiosError error={error}/>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function ContentSAFCA({data2}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/competition/${data2.id}/safcaData`, setLoading, 1)
|
||||
|
||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||
const [state2, dispatch2] = useReducer(SimpleReducer, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (data === null)
|
||||
return
|
||||
if (data.admin !== null){
|
||||
let index = 0
|
||||
for (const d of data.admin) {
|
||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
||||
index++
|
||||
}
|
||||
}
|
||||
if (data.table !== null){
|
||||
let index = 0
|
||||
for (const d of data.table) {
|
||||
dispatch2({type: 'UPDATE_OR_ADD', payload: {id: index, data: d}})
|
||||
index++
|
||||
}
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const out = {}
|
||||
out['id'] = (data2.id === "") ? null : data2.id
|
||||
out['show_blason'] = event.target.show_blason.checked
|
||||
out['show_flag'] = event.target.show_flag.checked
|
||||
out['admin'] = state.map(d => d.data)
|
||||
out['table'] = state2.map(d => d.data)
|
||||
|
||||
toast.promise(
|
||||
apiAxios.post(`/competition/safcaData`, out),
|
||||
{
|
||||
pending: "Enregistrement des paramètres en cours",
|
||||
success: "Paramètres enregistrée avec succès 🎉",
|
||||
error: {
|
||||
render({data}) {
|
||||
return errFormater(data, "Échec de l'enregistrement des paramètres")
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return <>
|
||||
{data ?
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input name="id" value={data2.id || ""} readOnly hidden/>
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Configuration SAFCA</div>
|
||||
<div className="card-body text-center">
|
||||
|
||||
<div className="input-group mb-1">
|
||||
<div className="input-group-text">
|
||||
<input className="form-check-input mt-0" type="checkbox" aria-label="Afficher le blason du club sur les écrans"
|
||||
defaultChecked={data.show_blason} name="show_blason"/>
|
||||
</div>
|
||||
<span className="input-group-text">Afficher le blason du club sur les écrans</span>
|
||||
</div>
|
||||
|
||||
<div className="input-group mb-3">
|
||||
<div className="input-group-text">
|
||||
<input className="form-check-input mt-0" type="checkbox" aria-label="Afficher le pays du combattant sur les écrans"
|
||||
defaultChecked={data.show_flag} name="show_flag"/>
|
||||
</div>
|
||||
<span className="input-group-text">Afficher le pays du combattant sur les écrans</span>
|
||||
</div>
|
||||
|
||||
<span className="input-group-text">Administrateur</span>
|
||||
<ul className="list-group form-control">
|
||||
{state.map((d, index) => {
|
||||
return <div key={index} className="input-group">
|
||||
<input type="text" className="form-control" value={d.data} required
|
||||
onChange={(e) => {
|
||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}})
|
||||
}}/>
|
||||
<button className="btn btn-danger"
|
||||
onClick={() => dispatch({type: 'REMOVE', payload: d.id})}>
|
||||
<FontAwesomeIcon icon={faTrashCan}/></button>
|
||||
</div>
|
||||
})}
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button className="btn btn-success" onClick={e => {
|
||||
e.preventDefault();
|
||||
|
||||
let maxId = 0;
|
||||
state.forEach((d) => {
|
||||
if (d.id > maxId)
|
||||
maxId = d.id;
|
||||
})
|
||||
|
||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: maxId + 1, data: ""}})
|
||||
}}><FontAwesomeIcon icon={faAdd}/></button>
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<div className="mb-3"></div>
|
||||
<span className="input-group-text">Table</span>
|
||||
<ul className="list-group form-control">
|
||||
{state2.map((d, index) => {
|
||||
return <div key={index} className="input-group">
|
||||
<input type="text" className="form-control" value={d.data} required
|
||||
onChange={(e) => {
|
||||
dispatch2({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: e.target.value}})
|
||||
}}/>
|
||||
<button className="btn btn-danger"
|
||||
onClick={() => dispatch2({type: 'REMOVE', payload: d.id})}>
|
||||
<FontAwesomeIcon icon={faTrashCan}/></button>
|
||||
</div>
|
||||
})}
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button className="btn btn-success" onClick={e => {
|
||||
e.preventDefault();
|
||||
|
||||
let maxId = 0;
|
||||
state2.forEach((d) => {
|
||||
if (d.id > maxId)
|
||||
maxId = d.id;
|
||||
})
|
||||
|
||||
dispatch2({type: 'UPDATE_OR_ADD', payload: {id: maxId + 1, data: ""}})
|
||||
}}><FontAwesomeIcon icon={faAdd}/></button>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="row mb-3">
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
: error && <AxiosError error={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 <form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
<input name="id" value={data.id || ""} readOnly hidden/>
|
||||
<div className="card-header">{data.id ? "Edition competition" : "Création competition"}</div>
|
||||
<div className="card-body text-center">
|
||||
<TextField name="uuid" text="UUID" value={data.uuid} disabled={true}/>
|
||||
<TextField name="name" text="Nom" value={data.name}/>
|
||||
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="birth_date">Date</span>
|
||||
<input type="date" className="form-control" placeholder="jj/mm/aaaa" aria-label="date"
|
||||
name="date" aria-describedby="date" defaultValue={data.date ? data.date.split('T')[0] : ''} required/>
|
||||
</div>
|
||||
|
||||
{data.id !== null && <TextField name="owner" text="Propriétaire" value={data.owner}/>}
|
||||
|
||||
<OptionField name="system" text="System" value={data.system} values={{SAFCA: 'SAFCA'}} disabled={data.id !== null}/>
|
||||
|
||||
<div className="row">
|
||||
<ClubSelect defaultValue={data.club} name="club" na={false} disabled={data.id !== null}/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="row mb-3">
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
61
src/main/webapp/src/pages/competition/CompetitionList.jsx
Normal file
61
src/main/webapp/src/pages/competition/CompetitionList.jsx
Normal file
@ -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 <>
|
||||
<div>
|
||||
<div className="row">
|
||||
{data
|
||||
? <MakeCentralPanel data={data} navigate={navigate}/>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function MakeCentralPanel({data, navigate}) {
|
||||
|
||||
return <>
|
||||
<div className="col mb-2" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||
<button type="button" className="btn btn-primary" onClick={() => navigate("/competition/0")}>Nouvelle competition</button>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="list-group">
|
||||
{data.map(req => (<MakeRow key={req.id} data={req} navigate={navigate}/>))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function MakeRow({data, navigate}) {
|
||||
return <div className="list-group-item d-flex justify-content-between align-items-start list-group-item-action"
|
||||
onClick={() => navigate("" + data.id)}>
|
||||
<div className="ms-2 col-auto">
|
||||
<div className="fw-bold">{data.name}</div>
|
||||
<small>{data.date.split('T')[0]}</small>
|
||||
</div>
|
||||
<small style={{textAlign: 'right'}}>{data.clubName}<br/>{data.system}</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="list-group">
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
</div>
|
||||
}
|
||||
26
src/main/webapp/src/pages/competition/CompetitionRoot.jsx
Normal file
26
src/main/webapp/src/pages/competition/CompetitionRoot.jsx
Normal file
@ -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 <>
|
||||
<h1>Competition</h1>
|
||||
<LoadingProvider>
|
||||
<Outlet/>
|
||||
</LoadingProvider>
|
||||
</>
|
||||
}
|
||||
|
||||
export function getCompetitionChildren() {
|
||||
return [
|
||||
{
|
||||
path: '',
|
||||
element: <CompetitionList/>
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
element: <CompetitionEdit/>
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user