diff --git a/src/main/java/fr/titionfire/ffsaf/data/id/RegisterId.java b/src/main/java/fr/titionfire/ffsaf/data/id/RegisterId.java index 29b4327..0afb07a 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/id/RegisterId.java +++ b/src/main/java/fr/titionfire/ffsaf/data/id/RegisterId.java @@ -1,20 +1,17 @@ package fr.titionfire.ffsaf.data.id; -import fr.titionfire.ffsaf.data.model.CompetitionModel; -import fr.titionfire.ffsaf.data.model.MembreModel; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import lombok.Data; +import jakarta.persistence.Embeddable; +import lombok.*; import java.io.Serializable; -@Data +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@Embeddable public class RegisterId implements Serializable { - @ManyToOne - @JoinColumn(name = "id_competition") - private CompetitionModel competition; - - @ManyToOne - @JoinColumn(name = "id_membre") - private MembreModel membre; + private Long competitionId; + private Long membreId; } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java similarity index 82% rename from src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java rename to src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java index f48bafb..d32dac0 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/PouleModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java @@ -17,8 +17,8 @@ import java.util.List; @RegisterForReflection @Entity -@Table(name = "poule") -public class PouleModel { +@Table(name = "category") +public class CategoryModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; @@ -34,11 +34,11 @@ public class PouleModel { CompetitionModel compet; @OneToMany(fetch = FetchType.LAZY) - @JoinColumn(name = "id_poule", referencedColumnName = "id") + @JoinColumn(name = "id_category", referencedColumnName = "id") List matchs; @OneToMany(fetch = FetchType.LAZY) - @JoinColumn(name = "id_poule", referencedColumnName = "id") + @JoinColumn(name = "id_category", referencedColumnName = "id") List tree; Integer type; diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java index adcbfd6..c5701d7 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java @@ -1,6 +1,7 @@ package fr.titionfire.ffsaf.data.model; import fr.titionfire.ffsaf.utils.CompetitionSystem; +import fr.titionfire.ffsaf.utils.RegisterMode; import io.quarkus.runtime.annotations.RegisterForReflection; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -21,6 +22,7 @@ import java.util.List; @Table(name = "compet") public class CompetitionModel { @Id + @Access(AccessType.PROPERTY) @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; @@ -36,9 +38,26 @@ public class CompetitionModel { String uuid; Date date; + Date todate; + + @Column(columnDefinition = "TEXT") + String description; + String adresse; + + Date startRegister; + Date endRegister; + + RegisterMode registerMode; + + boolean publicVisible; @OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL) List insc; String owner; + + String data1; + String data2; + String data3; + String data4; } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java index ece2466..a20694e 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/MatchModel.java @@ -41,10 +41,10 @@ public class MatchModel { String c2_str = null; @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "id_poule", referencedColumnName = "id") - PouleModel poule = null; + @JoinColumn(name = "id_category", referencedColumnName = "id") + CategoryModel category = null; - long poule_ord = 0; + long category_ord = 0; boolean isEnd = true; @@ -52,5 +52,5 @@ public class MatchModel { @CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match")) List scores = new ArrayList<>(); - char groupe = 'A'; + char poule = 'A'; } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java index 2824563..57407c2 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java @@ -17,14 +17,17 @@ import lombok.Setter; @Entity @Table(name = "register") -@IdClass(RegisterId.class) public class RegisterModel { - @Id + + @EmbeddedId + RegisterId id; + + @MapsId("competitionId") @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_competition") CompetitionModel competition; - @Id + @MapsId("membreId") @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "id_membre") MembreModel membre; @@ -37,4 +40,14 @@ public class RegisterModel { @JoinColumn(name = "club") ClubModel club = null; + public RegisterModel(CompetitionModel competition, MembreModel membre, Integer weight, int overCategory, + Categorie categorie, ClubModel club) { + this.id = new RegisterId(competition.getId(), membre.getId()); + this.competition = competition; + this.membre = membre; + this.weight = weight; + this.overCategory = overCategory; + this.categorie = categorie; + this.club = club; + } } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java index c4a701b..092b950 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/TreeModel.java @@ -20,8 +20,8 @@ public class TreeModel { @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; - @Column(name = "id_poule") - Long poule; + @Column(name = "id_category") + Long category; Integer level; diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/PouleRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java similarity index 57% rename from src/main/java/fr/titionfire/ffsaf/data/repository/PouleRepository.java rename to src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java index 7535bd3..8271b3b 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/repository/PouleRepository.java +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/CategoryRepository.java @@ -1,9 +1,9 @@ package fr.titionfire.ffsaf.data.repository; -import fr.titionfire.ffsaf.data.model.PouleModel; +import fr.titionfire.ffsaf.data.model.CategoryModel; import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped -public class PouleRepository implements PanacheRepositoryBase { +public class CategoryRepository implements PanacheRepositoryBase { } diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/RegisterRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/RegisterRepository.java index 8fe5ce9..01260f3 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/repository/RegisterRepository.java +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/RegisterRepository.java @@ -1,9 +1,11 @@ package fr.titionfire.ffsaf.data.repository; +import fr.titionfire.ffsaf.data.id.RegisterId; import fr.titionfire.ffsaf.data.model.RegisterModel; -import io.quarkus.hibernate.reactive.panache.PanacheRepository; +import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped -public class RegisterRepository implements PanacheRepository { +public class RegisterRepository implements PanacheRepositoryBase { + } diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java similarity index 81% rename from src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java rename to src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java index bcbaacc..19cbee0 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/PouleService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CategoryService.java @@ -2,11 +2,11 @@ package fr.titionfire.ffsaf.domain.service; import fr.titionfire.ffsaf.data.model.MatchModel; import fr.titionfire.ffsaf.data.model.MembreModel; -import fr.titionfire.ffsaf.data.model.PouleModel; +import fr.titionfire.ffsaf.data.model.CategoryModel; import fr.titionfire.ffsaf.data.model.TreeModel; import fr.titionfire.ffsaf.data.repository.*; -import fr.titionfire.ffsaf.rest.data.PouleData; -import fr.titionfire.ffsaf.rest.data.PouleFullData; +import fr.titionfire.ffsaf.rest.data.CategoryData; +import fr.titionfire.ffsaf.rest.data.CategoryFullData; import fr.titionfire.ffsaf.rest.data.TreeData; import fr.titionfire.ffsaf.utils.CompetitionSystem; import fr.titionfire.ffsaf.utils.SecurityCtx; @@ -25,10 +25,10 @@ import java.util.stream.Stream; @WithSession @ApplicationScoped -public class PouleService { +public class CategoryService { @Inject - PouleRepository repository; + CategoryRepository repository; @Inject CompetitionRepository competRepository; @@ -45,35 +45,21 @@ public class PouleService { @Inject CompetPermService permService; - public Uni getById(SecurityCtx securityCtx, CompetitionSystem system, Long id) { + public Uni getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system) .firstResult() .onItem().ifNull().failWith(() -> new RuntimeException("Poule not found")) - .call(data -> permService.hasViewPerm(securityCtx, data.getCompet())) - .map(PouleData::fromModel); + .call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet())) + .map(CategoryData::fromModel); } - public Uni> getAll(SecurityCtx securityCtx, CompetitionSystem system) { - return repository.list("system = ?1", system) - .chain(o -> - permService.getAllHaveAccess(securityCtx.getSubject()) - .chain(map -> Uni.createFrom().item(o.stream() - .filter(p -> { - if (securityCtx.getSubject().equals(p.getCompet().getOwner())) - return true; - if (p.getSystem() == CompetitionSystem.SAFCA) { - if (map.containsKey(p.getCompet().getId())) - return map.get(p.getId()).equals("admin"); - return securityCtx.roleHas("federation_admin") - || securityCtx.roleHas("safca_super_admin"); - } - return securityCtx.roleHas("federation_admin"); - }) - .map(PouleData::fromModel).toList()) - )); + public Uni> getAllAdmin(SecurityCtx securityCtx, CompetitionSystem system) { + return permService.getAllHaveAdminAccess(securityCtx) + .chain(ids -> repository.list("system = ?1 AND compet.id IN ?2", system, ids)) + .map(pouleModels -> pouleModels.stream().map(CategoryData::fromModel).toList()); } - public Uni addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, PouleData data) { + public Uni addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, CategoryData data) { return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult() .chain(o -> { if (o == null) { @@ -81,7 +67,7 @@ public class PouleService { .onItem().ifNull().failWith(() -> new RuntimeException("Competition not found")) .call(o2 -> permService.hasEditPerm(securityCtx, o2)) .chain(competitionModel -> { - PouleModel model = new PouleModel(); + CategoryModel model = new CategoryModel(); model.setId(null); model.setSystem(system); @@ -99,7 +85,7 @@ public class PouleService { o.setType(data.getType()); return Panache.withTransaction(() -> repository.persist(o)); } - }).map(PouleData::fromModel); + }).map(CategoryData::fromModel); } private MatchModel findMatch(List matchModelList, Long id) { @@ -128,7 +114,7 @@ public class PouleService { } } - private Uni persisteTree(TreeData data, List node, PouleModel poule, + private Uni persisteTree(TreeData data, List node, CategoryModel poule, List matchModelList) { TreeModel mm = findNode(node, data.getMatch()); if (mm == null) { @@ -136,7 +122,7 @@ public class PouleService { mm.setId(null); } mm.setLevel(data.getLevel()); - mm.setPoule(poule.getId()); + mm.setCategory(poule.getId()); mm.setMatch(findMatch(matchModelList, data.getMatch())); return Uni.createFrom().item(mm) @@ -147,7 +133,7 @@ public class PouleService { .chain(o -> Panache.withTransaction(() -> treeRepository.persist(o))); } - public Uni syncPoule(SecurityCtx securityCtx, CompetitionSystem system, PouleFullData data) { + public Uni syncCategory(SecurityCtx securityCtx, CompetitionSystem system, CategoryFullData data) { return repository.find("systemId = ?1 AND system = ?2", data.getId(), system) .firstResult() .onItem().ifNotNull().call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet())) @@ -156,7 +142,7 @@ public class PouleService { .onItem().ifNull().failWith(() -> new RuntimeException("Compet not found")) .call(o -> permService.hasEditPerm(securityCtx, o)) .map(o -> { - PouleModel model = new PouleModel(); + CategoryModel model = new CategoryModel(); model.setId(null); model.setSystem(system); model.setSystemId(data.getId()); @@ -172,10 +158,10 @@ public class PouleService { o.setType(data.getType()); WorkData workData = new WorkData(); - workData.poule = o; + workData.category = o; return workData; }) - .call(o -> Panache.withTransaction(() -> repository.persist(o.poule))) + .call(o -> Panache.withTransaction(() -> repository.persist(o.category))) .call(o -> (data.getMatches() == null || data.getMatches().isEmpty()) ? Uni.createFrom().nullItem() : Uni.createFrom() .item(data.getMatches().stream().flatMap(m -> Stream.of(m.getC1_id(), m.getC2_id()) @@ -187,7 +173,7 @@ public class PouleService { ) .invoke(in -> { ArrayList node = new ArrayList<>(); - for (TreeModel treeModel : in.poule.getTree()) + for (TreeModel treeModel : in.category.getTree()) flatTreeChild(treeModel, node); ArrayList new_node = new ArrayList<>(); @@ -204,7 +190,7 @@ public class PouleService { n.setLeft(null); }); - in.toRmMatch = in.poule.getMatchs().stream() + in.toRmMatch = in.category.getMatchs().stream() .filter(m -> data.getMatches().stream().noneMatch(m2 -> m2.getId().equals(m.getSystemId()))) .map(MatchModel::getId).toList(); }) @@ -219,21 +205,21 @@ public class PouleService { .call(in -> data.getMatches().isEmpty() ? Uni.createFrom().nullItem() : Uni.join().all( data.getMatches().stream().map(m -> { - MatchModel mm = findMatch(in.poule.getMatchs(), m.getId()); + MatchModel mm = findMatch(in.category.getMatchs(), m.getId()); if (mm == null) { mm = new MatchModel(); mm.setId(null); mm.setSystem(system); mm.setSystemId(m.getId()); } - mm.setPoule(in.poule); - mm.setPoule_ord(m.getPoule_ord()); + mm.setCategory(in.category); + mm.setCategory_ord(m.getCategory_ord()); mm.setC1_str(m.getC1_str()); mm.setC2_str(m.getC2_str()); mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null)); mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null)); mm.setEnd(m.isEnd()); - mm.setGroupe(m.getGroupe()); + mm.setPoule(m.getPoule()); mm.getScores().clear(); mm.getScores().addAll(m.getScores()); @@ -244,13 +230,13 @@ public class PouleService { .andCollectFailures()) .call(in -> data.getTrees().isEmpty() ? Uni.createFrom().nullItem() : Uni.join().all(data.getTrees().stream() - .map(m -> persisteTree(m, in.poule.getTree(), in.poule, in.match)).toList()) + .map(m -> persisteTree(m, in.category.getTree(), in.category, in.match)).toList()) .andCollectFailures()) .map(__ -> "OK"); } private static class WorkData { - PouleModel poule; + CategoryModel category; HashMap membres = new HashMap<>(); List match = new ArrayList<>(); List toRmMatch; diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java index 3064021..5e3ba1e 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java @@ -2,11 +2,13 @@ package fr.titionfire.ffsaf.domain.service; import fr.titionfire.ffsaf.data.model.CompetitionModel; import fr.titionfire.ffsaf.data.repository.CompetitionRepository; +import fr.titionfire.ffsaf.data.repository.RegisterRepository; 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.RegisterMode; import fr.titionfire.ffsaf.utils.SecurityCtx; import io.quarkus.cache.Cache; import io.quarkus.cache.CacheName; @@ -15,7 +17,10 @@ import io.smallrye.mutiny.unchecked.Unchecked; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -28,6 +33,9 @@ public class CompetPermService { @Inject ServerCustom serverCustom; + @Inject + CompetitionRepository competitionRepository; + @Inject @CacheName("safca-config") Cache cache; @@ -37,13 +45,16 @@ public class CompetPermService { Cache cacheAccess; @Inject - CompetitionRepository competitionRepository; + @CacheName("have-access") + Cache cacheNoneAccess; + @Inject + RegisterRepository registerRepository; + 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) { @@ -52,61 +63,172 @@ public class CompetPermService { }); } - 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> getAllHaveAdminAccess(SecurityCtx securityCtx) { + ArrayList out = new ArrayList<>(); + + Uni> safca = cacheAccess.getAsync(securityCtx.getSubject(), + k -> competitionRepository.list("system = ?1", CompetitionSystem.SAFCA) + .chain(competitionModels -> { + CompletableFuture> f = new CompletableFuture<>(); + SReqCompet.getAllHaveAccess(serverCustom.clients, securityCtx.getSubject(), f); + return Uni.createFrom().future(f, Duration.ofMillis(1500)) + .map(map_ -> { + HashMap map = new HashMap<>(); + map_.forEach((key, value) -> map.put(Long.parseLong(key), value)); + + for (CompetitionModel model : competitionModels) { + if (model.getOwner().equals(securityCtx.getSubject())) + map.putIfAbsent(model.getId(), "owner"); + else if (securityCtx.roleHas("federation_admin") + || securityCtx.roleHas("safca_super_admin")) + map.putIfAbsent(model.getId(), "admin"); + } + return map; + }); + })) + .onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject())); + + Uni> none = cacheNoneAccess.getAsync(securityCtx.getSubject(), + k -> competitionRepository.list("system = ?1", CompetitionSystem.NONE) + .map(competitionModels -> { + HashMap map = new HashMap<>(); + for (CompetitionModel model : competitionModels) { + if (model.getOwner().equals(securityCtx.getSubject())) + map.putIfAbsent(model.getId(), "owner"); + else if (securityCtx.roleHas("federation_admin")) + map.putIfAbsent(model.getId(), "admin"); + else if (securityCtx.isInClubGroup(model.getClub().getId()) && (securityCtx.roleHas( + "club_president") + || securityCtx.roleHas("club_respo_intra") || securityCtx.roleHas( + "club_secretaire") + || securityCtx.roleHas("club_tresorier"))) + map.putIfAbsent(model.getId(), "admin"); + } + return map; + })); + + return safca.invoke(map -> + map.forEach((k, v) -> { + if (v.equals("owner") || v.equals("admin")) + out.add(k); + }) + ) + .call(__ -> none.invoke(map -> + map.forEach((k, v) -> { + if (v.equals("owner") || v.equals("admin")) + out.add(k); + }) + )) + .map(__ -> out.stream().distinct().toList()); } + + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm + */ public Uni hasViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) { return hasViewPerm(securityCtx, Uni.createFrom().item(competitionModel)); } + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm + */ public Uni hasViewPerm(SecurityCtx securityCtx, long id) { return hasViewPerm(securityCtx, competitionRepository.findById(id)); } - private Uni hasViewPerm(SecurityCtx securityCtx, Uni in) { - return in.call(o -> ( - securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) ? - Uni.createFrom().nullItem() - : - o.getSystem() == CompetitionSystem.SAFCA ? - hasSafcaViewPerm(securityCtx, o.getId()) - : Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> { - if (!securityCtx.isInClubGroup(o.getClub().getId())) - throw new DForbiddenException(); - }) - )); + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm + */ + public Uni hasViewPerm(SecurityCtx securityCtx, Uni in) { + return in.call(cm -> (cm.isPublicVisible() || cm.getRegisterMode() == RegisterMode.FREE + || cm.getRegisterMode() == RegisterMode.HELLOASSO + || (cm.getRegisterMode() == RegisterMode.CLUB_ADMIN && securityCtx.isClubAdmin())) ? + Uni.createFrom().nullItem() : + hasAdminViewPerm(securityCtx, cm).onFailure() + .recoverWithUni(__ -> + registerRepository.count("membre.userId = ?1 AND competition = ?2", + securityCtx.getSubject(), cm).map(Unchecked.function(c -> { + if (c == 0) + throw new DForbiddenException(); + return cm; + })) + )); } + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm + */ + public Uni hasAdminViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) { + return hasAdminViewPerm(securityCtx, Uni.createFrom().item(competitionModel)); + } + + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm + */ + public Uni hasAdminViewPerm(SecurityCtx securityCtx, long id) { + return hasAdminViewPerm(securityCtx, competitionRepository.findById(id)); + } + + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm + */ + public Uni hasAdminViewPerm(SecurityCtx securityCtx, Uni in) { + return in.call(Unchecked.function(o -> { + if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) + return Uni.createFrom().nullItem(); + + if (o.getSystem() == CompetitionSystem.SAFCA) + return hasSafcaViewPerm(securityCtx, o.getId()); + + if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here + throw new DForbiddenException(); + + if (o.getSystem() == CompetitionSystem.NONE) + if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra") + || securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier")) + return Uni.createFrom().nullItem(); + + throw new DForbiddenException(); + }) + ); + } + + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm + */ public Uni hasEditPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) { return hasEditPerm(securityCtx, Uni.createFrom().item(competitionModel)); } + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm + */ public Uni hasEditPerm(SecurityCtx securityCtx, long id) { return hasEditPerm(securityCtx, competitionRepository.findById(id)); } + /** + * @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm + */ public Uni hasEditPerm(SecurityCtx securityCtx, Uni in) { - return in.call(o -> ( - securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) ? - Uni.createFrom().nullItem() - : - o.getSystem() == CompetitionSystem.SAFCA ? - hasSafcaEditPerm(securityCtx, o.getId()) - : Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> { - if (!securityCtx.isInClubGroup(o.getClub().getId())) - throw new DForbiddenException(); - }) - )); + return in.call(Unchecked.function(o -> { + if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) + return Uni.createFrom().nullItem(); + + if (o.getSystem() == CompetitionSystem.SAFCA) + return hasSafcaEditPerm(securityCtx, o.getId()); + + if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here + throw new DForbiddenException(); + + if (o.getSystem() == CompetitionSystem.NONE) + if (securityCtx.isClubAdmin()) + return Uni.createFrom().nullItem(); + + throw new DForbiddenException(); + }) + ); } private Uni hasSafcaViewPerm(SecurityCtx securityCtx, long id) { diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java index 87130e7..f1f6751 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java @@ -15,6 +15,7 @@ import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb; import fr.titionfire.ffsaf.rest.exception.DBadRequestException; import fr.titionfire.ffsaf.rest.exception.DForbiddenException; import fr.titionfire.ffsaf.utils.CompetitionSystem; +import fr.titionfire.ffsaf.utils.RegisterMode; import fr.titionfire.ffsaf.utils.SecurityCtx; import fr.titionfire.ffsaf.utils.Utils; import io.quarkus.cache.Cache; @@ -41,11 +42,14 @@ public class CompetitionService { CompetitionRepository repository; @Inject - PouleRepository pouleRepository; + CategoryRepository categoryRepository; @Inject MatchRepository matchRepository; + @Inject + RegisterRepository registerRepository; + @Inject KeycloakService keycloakService; @@ -68,16 +72,23 @@ public class CompetitionService { @Inject @CacheName("safca-have-access") Cache cacheAccess; + @Inject - RegisterRepository registerRepository; + @CacheName("have-access") + Cache cacheNoneAccess; public Uni getById(SecurityCtx securityCtx, Long id) { + return permService.hasViewPerm(securityCtx, id).map(CompetitionData::fromModelLight); + } + + public Uni getByIdAdmin(SecurityCtx securityCtx, Long id) { if (id == 0) { return Uni.createFrom() - .item(new CompetitionData(null, "", "", new Date(), CompetitionSystem.SAFCA, - null, "", "", null)); + .item(new CompetitionData(null, "", "", "", "", new Date(), new Date(), + CompetitionSystem.NONE, RegisterMode.FREE, new Date(), new Date(), true, + null, "", "", null, true, "", "", "", "")); } - return permService.hasViewPerm(securityCtx, id) + return permService.hasAdminViewPerm(securityCtx, id) .chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc()) .map(insc -> CompetitionData.fromModel(competitionModel).addInsc(insc))) .chain(data -> @@ -90,60 +101,52 @@ public class CompetitionService { } public Uni> getAll(SecurityCtx securityCtx) { - return repository.listAll() - .chain(o -> - permService.getAllHaveAccess(securityCtx.getSubject()) - .chain(map -> Uni.createFrom().item(o.stream() - .filter(p -> { - if (securityCtx.getSubject().equals(p.getOwner())) - return true; - if (p.getSystem() == CompetitionSystem.SAFCA) { - if (map.containsKey(p.getId())) - return map.get(p.getId()).equals("admin"); - return securityCtx.roleHas("federation_admin") - || securityCtx.roleHas("safca_super_admin"); + List out = new ArrayList<>(); + return permService.getAllHaveAdminAccess(securityCtx) + .call(ids -> repository.list("id IN ?1", ids) + .invoke(cm -> { + out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList()); + out.forEach(competition -> competition.setCanEdit(true)); + })) + .call(ids -> + repository.list("id NOT IN ?1 AND (publicVisible = TRUE OR registerMode IN ?2)", ids, + securityCtx.isClubAdmin() ? List.of(RegisterMode.FREE, RegisterMode.HELLOASSO, + RegisterMode.CLUB_ADMIN) : List.of(RegisterMode.FREE, RegisterMode.HELLOASSO)) + .invoke(cm -> out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList())) + .call(cm -> registerRepository.list( + "membre.userId = ?1 AND competition.id NOT IN ?2 AND competition NOT IN ?3", + securityCtx.getSubject(), ids, cm) + .chain(registerModels -> { + Uni uni = Uni.createFrom().nullItem(); + for (RegisterModel registerModel : registerModels) { + uni = uni.call(__ -> Mutiny.fetch(registerModel.getCompetition()) + .invoke(cm2 -> out.add(CompetitionData.fromModelLight(cm2)))); } - return securityCtx.roleHas("federation_admin"); + return uni; }) - .map(CompetitionData::fromModel).toList()) - )); + )) + .map(__ -> out); } - public Uni> getAllSystem(SecurityCtx securityCtx, - CompetitionSystem system) { - if (system == CompetitionSystem.SAFCA) { - return permService.getAllHaveAccess(securityCtx.getSubject()) - .chain(map -> - repository.list("system = ?1", system) - .map(data -> data.stream() - .filter(p -> { - if (securityCtx.getSubject().equals(p.getOwner())) - return true; - if (map.containsKey(p.getId())) - return map.get(p.getId()).equals("admin"); - return securityCtx.roleHas("federation_admin") - || securityCtx.roleHas("safca_super_admin"); - }) - .map(CompetitionData::fromModel).toList()) - ); - } + public Uni> getAllAdmin(SecurityCtx securityCtx) { + return permService.getAllHaveAdminAccess(securityCtx) + .chain(ids -> repository.list("id IN ?1", ids)) + .map(pouleModels -> pouleModels.stream().map(CompetitionData::fromModel).toList()); + } - return repository.list("system = ?1", system) - .map(data -> data.stream() - .filter(p -> { - if (securityCtx.getSubject().equals(p.getOwner())) - return true; - return securityCtx.roleHas("federation_admin") || - securityCtx.isInClubGroup(p.getClub().getId()); - }) - .map(CompetitionData::fromModel).toList()); + public Uni> getAllSystemAdmin(SecurityCtx securityCtx, + CompetitionSystem system) { + return permService.getAllHaveAdminAccess(securityCtx) + .chain(ids -> repository.list("system = ?1 AND id IN ?2", system, ids)) + .map(pouleModels -> pouleModels.stream().map(CompetitionData::fromModel).toList()); } public Uni addOrUpdate(SecurityCtx securityCtx, CompetitionData data) { if (data.getId() == null) { return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult() .invoke(Unchecked.consumer(combModel -> { - if (!securityCtx.getRoles().contains("create_compet") && !securityCtx.getRoles().contains("federation_admin")) + if (!securityCtx.getRoles().contains("create_compet") && !securityCtx.getRoles() + .contains("federation_admin")) throw new DForbiddenException("Vous ne pouvez pas créer de compétition"); })) .map(MembreModel::getClub) @@ -153,22 +156,24 @@ public class CompetitionService { 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(securityCtx.getSubject()); + copyData(data, model); + return Panache.withTransaction(() -> repository.persist(model)); }).map(CompetitionData::fromModel) - .call(__ -> cacheAccess.invalidate(securityCtx.getSubject())); + .call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate( + securityCtx.getSubject()) : Uni.createFrom().nullItem()) + .call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate( + securityCtx.getSubject()) : Uni.createFrom().nullItem()); } else { return permService.hasEditPerm(securityCtx, data.getId()) .chain(model -> { - model.setDate(data.getDate()); - model.setName(data.getName()); + copyData(data, model); - return vertx.getOrCreateContext().executeBlocking(() -> + return vertx.getOrCreateContext().executeBlocking(() -> // Update owner keycloakService.getUser(data.getOwner()).map(UserRepresentation::getId).orElse(null)) .invoke(Unchecked.consumer(newOwner -> { if (newOwner == null) @@ -183,10 +188,29 @@ public class CompetitionService { })) .chain(__ -> Panache.withTransaction(() -> repository.persist(model))); }).map(CompetitionData::fromModel) - .call(__ -> cacheAccess.invalidate(securityCtx.getSubject())); + .call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate( + securityCtx.getSubject()) : Uni.createFrom().nullItem()) + .call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate( + securityCtx.getSubject()) : Uni.createFrom().nullItem()); } } + private void copyData(CompetitionData data, CompetitionModel model) { + model.setName(data.getName()); + model.setAdresse(data.getAdresse()); + model.setDescription(data.getDescription()); + model.setDate(data.getDate()); + model.setTodate(data.getDate()); + model.setPublicVisible(data.isPublicVisible()); + model.setStartRegister(data.getStartRegister()); + model.setEndRegister(data.getEndRegister()); + model.setRegisterMode(data.getRegisterMode()); + model.setData1(data.getData1()); + model.setData2(data.getData2()); + model.setData3(data.getData3()); + model.setData4(data.getData4()); + } + public Uni> getRegister(SecurityCtx securityCtx, Long id) { return permService.hasEditPerm(securityCtx, id) .chain(c -> Mutiny.fetch(c.getInsc())) @@ -218,7 +242,7 @@ public class CompetitionService { r.setClub(combModel.getClub()); } } else { - r = new RegisterModel(c ,combModel, data.getWeight(), data.getOverCategory(), + r = new RegisterModel(c, combModel, data.getWeight(), data.getOverCategory(), (combModel.getBirth_date() == null) ? combModel.getCategorie() : Utils.getCategoryFormBirthDate(combModel.getBirth_date(), c.getDate()), @@ -248,7 +272,8 @@ public class CompetitionService { } else { if (fname == null || lname == null) return Uni.createFrom().failure(new DBadRequestException("Nom et prénom requis")); - return combRepository.find("unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2)", lname, + return combRepository.find("unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2)", + lname, fname).firstResult() .invoke(Unchecked.consumer(combModel -> { if (combModel == null) @@ -261,11 +286,11 @@ public class CompetitionService { return permService.hasEditPerm(securityCtx, id) .chain(c -> registerRepository.delete("competition = ?1 AND membre.id = ?2", c, combId) .invoke(Unchecked.consumer(l -> { - if (l != 0){ + if (l != 0) { if (c.getSystem() == CompetitionSystem.SAFCA) { SReqRegister.sendRmIfNeed(serverCustom.clients, combId, id); } - }else{ + } else { throw new DBadRequestException("Combattant non inscrit"); } })) @@ -277,7 +302,7 @@ public class CompetitionService { if (!(securityCtx.getSubject().equals(c.getOwner()) || securityCtx.roleHas("federation_admin"))) throw new DForbiddenException(); })) - .call(competitionModel -> pouleRepository.list("compet = ?1", competitionModel) + .call(competitionModel -> categoryRepository.list("compet = ?1", competitionModel) .call(pouleModels -> pouleModels.isEmpty() ? Uni.createFrom().nullItem() : Uni.join().all(pouleModels.stream() .map(pouleModel -> Panache.withTransaction( @@ -285,7 +310,7 @@ public class CompetitionService { .toList()) .andCollectFailures())) .call(competitionModel -> Panache.withTransaction( - () -> pouleRepository.delete("compet = ?1", competitionModel))) + () -> categoryRepository.delete("compet = ?1", competitionModel))) .chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId()))) .invoke(o -> SReqCompet.rmCompet(serverCustom.clients, id)) .call(__ -> cache.invalidate(id)); diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java index d7e8b2a..279d43d 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MatchService.java @@ -3,7 +3,7 @@ package fr.titionfire.ffsaf.domain.service; 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.data.repository.CategoryRepository; import fr.titionfire.ffsaf.rest.data.MatchData; import fr.titionfire.ffsaf.rest.exception.DNotFoundException; import fr.titionfire.ffsaf.utils.CompetitionSystem; @@ -25,7 +25,7 @@ public class MatchService { MatchRepository repository; @Inject - PouleRepository pouleRepository; + CategoryRepository categoryRepository; @Inject CombRepository combRepository; @@ -33,17 +33,17 @@ public class MatchService { @Inject CompetPermService permService; - public Uni getById(SecurityCtx securityCtx, CompetitionSystem system, Long id) { + public Uni getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new DNotFoundException("Match not found")) - .call(data -> permService.hasViewPerm(securityCtx, data.getPoule().getCompet())) + .call(data -> permService.hasAdminViewPerm(securityCtx, data.getCategory().getCompet())) .map(MatchData::fromModel); } - public Uni> getAllByPoule(SecurityCtx securityCtx, CompetitionSystem system, Long id) { - return pouleRepository.find("systemId = ?1 AND system = ?2", id, system).firstResult() + public Uni> getAllByPouleAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) { + return categoryRepository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found")) - .call(data -> permService.hasViewPerm(securityCtx, data.getCompet())) + .call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet())) .chain(data -> repository.list("poule = ?1", data.getId()) .map(o -> o.stream().map(MatchData::fromModel).toList())); } @@ -52,21 +52,21 @@ public class MatchService { return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult() .chain(o -> { if (o == null) { - return pouleRepository.find("systemId = ?1 AND system = ?2", data.getPoule(), system) + return categoryRepository.find("systemId = ?1 AND system = ?2", data.getCategory(), system) .firstResult() .onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found")) .call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet())) - .map(pouleModel -> { + .map(categoryModel -> { MatchModel model = new MatchModel(); model.setId(null); model.setSystem(system); model.setSystemId(data.getId()); - model.setPoule(pouleModel); + model.setCategory(categoryModel); return model; }); } else { - return pouleRepository.find("systemId = ?1 AND system = ?2", data.getPoule(), system) + return categoryRepository.find("systemId = ?1 AND system = ?2", data.getCategory(), system) .firstResult() .onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found")) .call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet())) @@ -77,7 +77,7 @@ public class MatchService { .chain(o -> { o.setC1_str(data.getC1_str()); o.setC2_str(data.getC2_str()); - o.setPoule_ord(data.getPoule_ord()); + o.setCategory_ord(data.getCategory_ord()); o.getScores().clear(); o.getScores().addAll(data.getScores()); @@ -97,7 +97,7 @@ public class MatchService { List scores) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new DNotFoundException("Match not found")) - .call(o2 -> permService.hasEditPerm(securityCtx, o2.getPoule().getCompet())) + .call(o2 -> permService.hasEditPerm(securityCtx, o2.getCategory().getCompet())) .invoke(data -> { data.getScores().clear(); data.getScores().addAll(scores); @@ -109,7 +109,7 @@ public class MatchService { public Uni delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) { return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult() .onItem().ifNull().failWith(() -> new DNotFoundException("Match not found")) - .call(o2 -> permService.hasEditPerm(securityCtx, o2.getPoule().getCompet())) + .call(o2 -> permService.hasEditPerm(securityCtx, o2.getCategory().getCompet())) .chain(data -> Panache.withTransaction(() -> repository.delete(data))); } } diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/TreeService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/TreeService.java deleted file mode 100644 index 2e10cc7..0000000 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/TreeService.java +++ /dev/null @@ -1,4 +0,0 @@ -package fr.titionfire.ffsaf.domain.service; - -public class TreeService { -} diff --git a/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java b/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java index c7aa379..18c4cce 100644 --- a/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java +++ b/src/main/java/fr/titionfire/ffsaf/net2/request/SReqCompet.java @@ -24,7 +24,7 @@ public class SReqCompet { } public static void getAllHaveAccess(ArrayList client_Thread, String userId, - CompletableFuture> future) { + CompletableFuture> future) { if (client_Thread.isEmpty()) return; client_Thread.get(0).sendReq(userId, "getAllHaveAccess", new JsonConsumer<>(HashMap.class, future::complete)); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CategoryAdminEndpoints.java similarity index 59% rename from src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java rename to src/main/java/fr/titionfire/ffsaf/rest/CategoryAdminEndpoints.java index 8bc9dc1..1ea2568 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/PouleEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CategoryAdminEndpoints.java @@ -1,8 +1,8 @@ package fr.titionfire.ffsaf.rest; -import fr.titionfire.ffsaf.domain.service.PouleService; -import fr.titionfire.ffsaf.rest.data.PouleData; -import fr.titionfire.ffsaf.rest.data.PouleFullData; +import fr.titionfire.ffsaf.domain.service.CategoryService; +import fr.titionfire.ffsaf.rest.data.CategoryData; +import fr.titionfire.ffsaf.rest.data.CategoryFullData; import fr.titionfire.ffsaf.utils.CompetitionSystem; import fr.titionfire.ffsaf.utils.SecurityCtx; import io.quarkus.security.Authenticated; @@ -14,14 +14,14 @@ import jakarta.ws.rs.core.MediaType; import java.util.List; @Authenticated -@Path("api/poule/{system}/") -public class PouleEndpoints { +@Path("api/poule/{system}/admin/") +public class CategoryAdminEndpoints { @PathParam("system") private CompetitionSystem system; @Inject - PouleService service; + CategoryService service; @Inject SecurityCtx securityCtx; @@ -30,28 +30,28 @@ public class PouleEndpoints { @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) - public Uni getById(@PathParam("id") Long id) { - return service.getById(securityCtx, system, id); + public Uni getByIdAdmin(@PathParam("id") Long id) { + return service.getByIdAdmin(securityCtx, system, id); } @GET @Produces(MediaType.APPLICATION_JSON) - public Uni> getAll() { - return service.getAll(securityCtx, system); + public Uni> getAllAdmin() { + return service.getAllAdmin(securityCtx, system); } @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public Uni addOrUpdate(PouleData data) { + public Uni addOrUpdate(CategoryData data) { return service.addOrUpdate(securityCtx, system, data); } @POST @Path("sync") @Consumes(MediaType.APPLICATION_JSON) - public Uni syncPoule(PouleFullData data) { - return service.syncPoule(securityCtx, system, data); + public Uni syncCategory(CategoryFullData data) { + return service.syncCategory(securityCtx, system, data); } @DELETE diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java new file mode 100644 index 0000000..51c0d9f --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionAdminEndpoints.java @@ -0,0 +1,50 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.domain.service.CompetitionService; +import fr.titionfire.ffsaf.rest.data.CompetitionData; +import fr.titionfire.ffsaf.utils.CompetitionSystem; +import fr.titionfire.ffsaf.utils.SecurityCtx; +import io.quarkus.security.Authenticated; +import io.smallrye.mutiny.Uni; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import java.util.List; + +@Path("api/competition/admin") +public class CompetitionAdminEndpoints { + + @Inject + CompetitionService service; + + @Inject + SecurityCtx securityCtx; + + @GET + @Path("{id}") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni getByIdAdmin(@PathParam("id") Long id) { + return service.getByIdAdmin(securityCtx, id); + } + + @GET + @Path("all") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni> getAllAdmin() { + return service.getAllAdmin(securityCtx); + } + + @GET + @Path("all/{system}") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni> getAllSystemAdmin(@PathParam("system") CompetitionSystem system) { + return service.getAllSystemAdmin(securityCtx, system); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java index b54abd2..3d2398d 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java @@ -5,7 +5,6 @@ import fr.titionfire.ffsaf.rest.data.CompetitionData; import fr.titionfire.ffsaf.rest.data.RegisterRequestData; import fr.titionfire.ffsaf.rest.data.SimpleCompetData; import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb; -import fr.titionfire.ffsaf.utils.CompetitionSystem; import fr.titionfire.ffsaf.utils.SecurityCtx; import io.quarkus.security.Authenticated; import io.smallrye.mutiny.Uni; @@ -29,8 +28,11 @@ public class CompetitionEndpoints { @Path("{id}") @Authenticated @Produces(MediaType.APPLICATION_JSON) - public Uni getById(@PathParam("id") Long id) { - return service.getById(securityCtx, id); + public Uni getById(@PathParam("id") Long id, @QueryParam("light") boolean light) { + if (light) + return service.getById(securityCtx, id); + else + return service.getByIdAdmin(securityCtx, id); } @GET @@ -76,14 +78,6 @@ public class CompetitionEndpoints { return service.getAll(securityCtx); } - @GET - @Path("all/{system}") - @Authenticated - @Produces(MediaType.APPLICATION_JSON) - public Uni> getAllSystem(@PathParam("system") CompetitionSystem system) { - return service.getAllSystem(securityCtx, system); - } - @POST @Authenticated @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MatchAdminEndpoints.java similarity index 80% rename from src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java rename to src/main/java/fr/titionfire/ffsaf/rest/MatchAdminEndpoints.java index 097dac9..6e233ec 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/MatchEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/MatchAdminEndpoints.java @@ -14,8 +14,8 @@ import jakarta.ws.rs.core.MediaType; import java.util.List; @Authenticated -@Path("api/match/{system}/") -public class MatchEndpoints { +@Path("api/match/{system}/admin") +public class MatchAdminEndpoints { @PathParam("system") private CompetitionSystem system; @@ -30,15 +30,15 @@ public class MatchEndpoints { @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) - public Uni getById(@PathParam("id") Long id) { - return service.getById(securityCtx, system, id); + public Uni getByIdAdmin(@PathParam("id") Long id) { + return service.getByIdAdmin(securityCtx, system, id); } @GET @Path("getAllByPoule/{id}") @Produces(MediaType.APPLICATION_JSON) - public Uni> getAllByPoule(@PathParam("id") Long id) { - return service.getAllByPoule(securityCtx, system, id); + public Uni> getAllByPouleAdmin(@PathParam("id") Long id) { + return service.getAllByPouleAdmin(securityCtx, system, id); } @POST diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CategoryData.java similarity index 58% rename from src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java rename to src/main/java/fr/titionfire/ffsaf/rest/data/CategoryData.java index f89da27..0632d81 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/PouleData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CategoryData.java @@ -1,6 +1,6 @@ package fr.titionfire.ffsaf.rest.data; -import fr.titionfire.ffsaf.data.model.PouleModel; +import fr.titionfire.ffsaf.data.model.CategoryModel; import io.quarkus.runtime.annotations.RegisterForReflection; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,16 +8,16 @@ import lombok.Data; @Data @AllArgsConstructor @RegisterForReflection -public class PouleData { +public class CategoryData { private Long id; private String name; private Long compet; private Integer type; - public static PouleData fromModel(PouleModel model) { + public static CategoryData fromModel(CategoryModel model) { if (model == null) return null; - return new PouleData(model.getSystemId(), model.getName(), model.getCompet().getId(), model.getType()); + return new CategoryData(model.getSystemId(), model.getName(), model.getCompet().getId(), model.getType()); } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/PouleFullData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CategoryFullData.java similarity index 96% rename from src/main/java/fr/titionfire/ffsaf/rest/data/PouleFullData.java rename to src/main/java/fr/titionfire/ffsaf/rest/data/CategoryFullData.java index b949111..bf6a370 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/PouleFullData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CategoryFullData.java @@ -7,7 +7,7 @@ import java.util.List; @Data @AllArgsConstructor -public class PouleFullData { +public class CategoryFullData { private Long id; private String name; private Long compet; diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java index 347d964..d6b07d1 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java @@ -1,9 +1,10 @@ package fr.titionfire.ffsaf.rest.data; import fr.titionfire.ffsaf.data.model.CompetitionModel; +import fr.titionfire.ffsaf.data.model.RegisterModel; import fr.titionfire.ffsaf.utils.Categorie; import fr.titionfire.ffsaf.utils.CompetitionSystem; -import fr.titionfire.ffsaf.data.model.RegisterModel; +import fr.titionfire.ffsaf.utils.RegisterMode; import io.quarkus.runtime.annotations.RegisterForReflection; import lombok.AllArgsConstructor; import lombok.Data; @@ -17,20 +18,53 @@ import java.util.List; public class CompetitionData { private Long id; private String name; + private String description; + private String adresse; private String uuid; private Date date; + private Date toDate; private CompetitionSystem system; + private RegisterMode registerMode; + private Date startRegister; + private Date endRegister; + private boolean publicVisible; private Long club; private String clubName; private String owner; private List registers; + private boolean canEdit; + private String data1; + private String data2; + private String data3; + private String data4; 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(), null); + return new CompetitionData(model.getId(), model.getName(), model.getDescription(), model.getAdresse(), + model.getUuid(), model.getDate(), model.getTodate(), model.getSystem(), + model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(), + model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false, + model.getData1(), model.getData2(), model.getData3(), model.getData4()); + } + + public static CompetitionData fromModelLight(CompetitionModel model) { + if (model == null) + return null; + + CompetitionData out = new CompetitionData(model.getId(), model.getName(), model.getDescription(), + model.getAdresse(), "", model.getDate(), model.getTodate(), null, + model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(), + null, model.getClub().getName(), "", null, false, + "","", "",""); + + if (model.getRegisterMode() == RegisterMode.HELLOASSO){ + out.setData1(model.getData1()); + out.setData2(model.getData2()); + } + + return out; } public CompetitionData addInsc(List insc) { diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java index b7c942b..143d0ad 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/MatchData.java @@ -17,10 +17,10 @@ public class MatchData { private String c1_str; private Long c2_id; private String c2_str; - private Long poule; - private long poule_ord; + private Long category; + private long category_ord; private boolean isEnd = true; - private char groupe; + private char poule; private List scores; public static MatchData fromModel(MatchModel model) { @@ -30,7 +30,7 @@ public class MatchData { return new MatchData(model.getSystemId(), (model.getC1_id() == null) ? null : model.getC1_id().getId(), model.getC1_str(), (model.getC2_id() == null) ? null : model.getC2_id().getId(), model.getC2_str(), - model.getPoule().getId(), model.getPoule_ord(), model.isEnd(), model.getGroupe(), + model.getCategory().getId(), model.getCategory_ord(), model.isEnd(), model.getPoule(), model.getScores()); } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java index b4cff47..2ab4841 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/TreeData.java @@ -10,7 +10,7 @@ import lombok.Data; @RegisterForReflection public class TreeData { private Long id; - private Long poule; + private Long category; private Integer level; private Long match; private TreeData left; @@ -20,7 +20,7 @@ public class TreeData { if (model == null) return null; - return new TreeData(model.getId(), model.getPoule(), model.getLevel(), model.getMatch().getId(), + return new TreeData(model.getId(), model.getCategory(), model.getLevel(), model.getMatch().getId(), fromModel(model.getLeft()), fromModel(model.getRight())); } } diff --git a/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java b/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java index 79dbeeb..32cb79a 100644 --- a/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java +++ b/src/main/java/fr/titionfire/ffsaf/utils/CompetitionSystem.java @@ -1,5 +1,5 @@ package fr.titionfire.ffsaf.utils; public enum CompetitionSystem { - SAFCA, + SAFCA, NONE } diff --git a/src/main/java/fr/titionfire/ffsaf/utils/RegisterMode.java b/src/main/java/fr/titionfire/ffsaf/utils/RegisterMode.java new file mode 100644 index 0000000..4f5e570 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/utils/RegisterMode.java @@ -0,0 +1,12 @@ +package fr.titionfire.ffsaf.utils; + +public enum RegisterMode { + FREE, CLUB_ADMIN, ADMIN, HELLOASSO +} +/* +HELLOASSO: +-> data1 = organizationSlug +-> data2 = formSlug +-> data3 = tarifs +-> data4 = errorEmail + */ diff --git a/src/main/java/fr/titionfire/ffsaf/utils/SecurityCtx.java b/src/main/java/fr/titionfire/ffsaf/utils/SecurityCtx.java index 348e1b3..aafe8b6 100644 --- a/src/main/java/fr/titionfire/ffsaf/utils/SecurityCtx.java +++ b/src/main/java/fr/titionfire/ffsaf/utils/SecurityCtx.java @@ -31,6 +31,11 @@ public class SecurityCtx { return securityIdentity.getRoles().contains(role); } + public boolean isClubAdmin() { + return this.roleHas("club_president") || this.roleHas("club_respo_intra") + || this.roleHas("club_secretaire") || this.roleHas("club_tresorier"); + } + public boolean isInClubGroup(long id) { if (idToken == null || idToken.getClaim("user_groups") == null) return false; diff --git a/src/main/webapp/public/img/HA-help-2.png b/src/main/webapp/public/img/HA-help-2.png new file mode 100644 index 0000000..97cdcde Binary files /dev/null and b/src/main/webapp/public/img/HA-help-2.png differ diff --git a/src/main/webapp/public/img/HA-help-3.png b/src/main/webapp/public/img/HA-help-3.png new file mode 100644 index 0000000..c587d14 Binary files /dev/null and b/src/main/webapp/public/img/HA-help-3.png differ diff --git a/src/main/webapp/public/img/HA-help-4.png b/src/main/webapp/public/img/HA-help-4.png new file mode 100644 index 0000000..5547b9c Binary files /dev/null and b/src/main/webapp/public/img/HA-help-4.png differ diff --git a/src/main/webapp/src/components/ClubSelect.jsx b/src/main/webapp/src/components/ClubSelect.jsx index 9d76665..844f781 100644 --- a/src/main/webapp/src/components/ClubSelect.jsx +++ b/src/main/webapp/src/components/ClubSelect.jsx @@ -4,9 +4,7 @@ import {AxiosError} from "./AxiosError.jsx"; export function ClubSelect({defaultValue, name, na = false, disabled = false}) { return -
- -
+
} @@ -19,7 +17,7 @@ function ClubSelect_({defaultValue, name, na, disabled}) { ?
{data.id ? "Edition competition" : "Création competition"}
- - -
- Date - -
- - {data.id !== null && } - - - - {data.id !== null && -
- +
+
+

+ +

+
+
+ + + {data.id !== null && +
+ +
+ } + {data.id !== null && } +
+
- } +
+

+ +

+
+
+ +
+ Date* + Du + + Au + +
+ + + + Si non coché, la compétition ne sera visible que par les personnes pouvant y inscrire des participants. +
+
+
+ +
+

+ +

+
+
+ +
+
+ + +
+
+ +
+ Date d'inscription + Du + + Au + +
+ +
+ +
Afin de permettre une bonne interconnexion avec HelloAsso, merci de suivre les instructions suivantes :
+
    +
  • Configurer l'url de notification : afin que nous puissions recevoir une notification à + chaque inscription, il est nécessaire de configurer l'url de notification de votre compte HelloAsso pour + qu'il redirige vers "https://intra.ffsaf.fr/api/webhook/ha". Pour ce faire, depuis la page d'accueil de + votre association sur HelloAsso, allez dans Mon compte > Paramètres > + Intégrations et API section Notification et copier-coller https://intra.ffsaf.fr/api/webhook/ha + dans le champ Mon URL de callback et enregister. +
  • +
  • Copier-coller le nom exacte des tarifs -sépare par des point-virgules- qui donneront + lieux à une inscription automatique. Tous ces tarifs doivent impérativement demander le numéro de licence + en champs obligatoire. Pour ce faire, lors de la configuration de votre billetterie à l'étape n°3, + cliquer sur + Ajouter une information, saisissez l'intituler exact suivant + Numéro de licence, dans Type de réponse souhaitée rentrer Nombre, + sélectionner les tarifs entrés plus précédemment et rendre l'information obligatoire. + ... + ...
  • +
  • Copier-coller l'url de votre billetterie dans le champs si dessous. Il devrais avoir la forme suivante: + https://www.helloasso.com/associations/<nom-asso-sur-helloasso>/evenements/<nom-billetterie>
  • +
+
+ + + + + + + Si pour une raison quelconque l'inscription automatique échoue, un email sera envoyé à cette adresse pour + vous en informer +
+
+
+
+ +
@@ -273,4 +441,4 @@ function Content({data}) {
-} \ 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 index 7f16ba1..0541147 100644 --- a/src/main/webapp/src/pages/competition/CompetitionList.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionList.jsx @@ -42,14 +42,32 @@ function MakeCentralPanel({data, navigate}) { } +const inscText = (type) => { + if (type === "FREE") { + return "Inscriptions libres" + } else if (type === "CLUB_ADMIN") { + return "Inscriptions par les responsables de club" + } else if (type === "ADMIN") { + return "Inscriptions par les administrateurs de la compétition" + } else if (type === "HELLOASSO") { + return "Inscriptions sur la billetterie HelloAsso" + } + + return "" +} + function MakeRow({data, navigate}) { - return
navigate("" + data.id)}> -
-
{data.name}
- {data.date.split('T')[0]} + return
data.canEdit ? navigate("" + data.id) : navigate("view/" + data.id)}> +
+
+
{data.name} par {data.clubName}
+ Du {new Date(data.date.split('T')[0]).toLocaleDateString()} au {new Date(data.toDate.split('T')[0]).toLocaleDateString()} +
+
+ {inscText(data.registerMode)} +
- {data.clubName}
{data.system}
} diff --git a/src/main/webapp/src/pages/competition/CompetitionRoot.jsx b/src/main/webapp/src/pages/competition/CompetitionRoot.jsx index e117154..bf02c63 100644 --- a/src/main/webapp/src/pages/competition/CompetitionRoot.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionRoot.jsx @@ -3,6 +3,7 @@ import {Outlet} from "react-router-dom"; import {CompetitionList} from "./CompetitionList.jsx"; import {CompetitionEdit} from "./CompetitionEdit.jsx"; import {CompetitionRegisterAdmin} from "./CompetitionRegisterAdmin.jsx"; +import {CompetitionView} from "./CompetitionView.jsx"; export function CompetitionRoot() { return <> @@ -23,9 +24,13 @@ export function getCompetitionChildren() { path: ':id', element: }, + { + path: 'view/:id', + element: + }, { path: ':id/register', element: } ] -} \ No newline at end of file +} diff --git a/src/main/webapp/src/pages/competition/CompetitionView.jsx b/src/main/webapp/src/pages/competition/CompetitionView.jsx new file mode 100644 index 0000000..cf2a6e2 --- /dev/null +++ b/src/main/webapp/src/pages/competition/CompetitionView.jsx @@ -0,0 +1,72 @@ +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 {useAuth} from "../../hooks/useAuth.jsx"; +import {isClubAdmin} from "../../utils/Tools.js"; + +export function CompetitionView() { + + const {id} = useParams() + const navigate = useNavigate(); + + const setLoading = useLoadingSwitcher() + const {data, error} = useFetch(`/competition/${id}?light=true`, setLoading, 1) + + return <> + +
+ {data ? <> + + + : error && + } +
+ +} + +const inscText = (type) => { + if (type === "FREE") { + return "Libres" + } else if (type === "CLUB_ADMIN") { + return "Par les responsables de club" + } else if (type === "ADMIN") { + return "Par les administrateurs de la compétition" + } else if (type === "HELLOASSO") { + return "Sur la billetterie HelloAsso" + } + + return "" +} + +function MakeContent({data}) { + const {userinfo} = useAuth() + + return
+
+

{data.name}

+
+
+

{data.description}

+

Date + : Du {new Date(data.date.split('T')[0]).toLocaleDateString()} au {new Date(data.toDate.split('T')[0]).toLocaleDateString()} +

+

Lieu : {data.adresse}

+

Organisateur : {data.clubName}

+

Type d'inscription : {inscText(data.registerMode)}

+ {(data.registerMode === "FREE" || data.registerMode === "CLUB_ADMIN") && +

Date d'inscription : Du {new Date(data.startRegister.split('+')[0]).toLocaleString()} au {new Date(data.endRegister.split('+')[0]).toLocaleString()}

+ } + {(data.registerMode === "CLUB_ADMIN" && isClubAdmin(userinfo)) || data.registerMode === "FREE" && + + } + {data.registerMode === "HELLOASSO" && +

Billetterie : {`https://www.helloasso.com/associations/${data.data1}/evenements/${data.data2}`}

+ } +
+
+} diff --git a/src/main/webapp/src/utils/Tools.js b/src/main/webapp/src/utils/Tools.js index 2d7d046..b7f2314 100644 --- a/src/main/webapp/src/utils/Tools.js +++ b/src/main/webapp/src/utils/Tools.js @@ -7,6 +7,15 @@ export const apiAxios = axios.create({ }); apiAxios.defaults.headers.post['Accept'] = 'application/json; charset=UTF-8'; +export function isInClub(userinfo, clubId) { + return userinfo?.groups.filter((g => g.startsWith("/club/"))).map(g => g.split("/")[2]).map(g => Number(g.split("-")[0])).includes(clubId) +} + +export function isClubAdmin(userinfo) { + return userinfo?.roles?.includes("club_president") || userinfo?.roles?.includes("club_respo_intra") + || userinfo?.roles?.includes("club_secretaire") || userinfo?.roles?.includes("club_tresorier"); +} + export const errFormater = (data, msg) => { if (typeof data.response.data === 'string' || data.response.data instanceof String)