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 8834b92..c3e2d7c 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java @@ -67,8 +67,15 @@ public class CompetitionModel { @Column(name = "table_") List table = new ArrayList<>(); + @Column(columnDefinition = "TEXT") String data1; + @Column(columnDefinition = "TEXT") String data2; + @Column(columnDefinition = "TEXT") String data3; + @Column(columnDefinition = "TEXT") String data4; + + @Column(columnDefinition = "TEXT") + String config; } 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 9b000b3..332d93f 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java @@ -98,7 +98,13 @@ public class CompetitionService { Cache cacheNoneAccess; public Uni getById(SecurityCtx securityCtx, Long id) { - return permService.hasViewPerm(securityCtx, id).map(CompetitionData::fromModelLight); + return permService.hasViewPerm(securityCtx, id).map(cm -> { + CompetitionData out = CompetitionData.fromModelLight(cm); + if (cm.getAdmin() != null) { + out.setCanEditRegisters(cm.getAdmin().stream().anyMatch(u -> u.equals(securityCtx.getSubject()))); + } + return out; + }); } public Uni getByIdAdmin(SecurityCtx securityCtx, Long id) { @@ -106,7 +112,8 @@ public class CompetitionService { return Uni.createFrom() .item(new CompetitionData(null, "", "", "", "", new Date(), new Date(), CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true, - null, "", "", null, true, "", "", "", "")); + null, "", "", null, true, true, + "", "", "", "", "{}")); } return permService.hasAdminViewPerm(securityCtx, id) .chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc()) @@ -129,6 +136,14 @@ public class CompetitionService { out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList()); out.forEach(competition -> competition.setCanEdit(true)); })) + .chain(ids -> repository.list("id NOT IN ?1 AND ?2 IN admin", ids, securityCtx.getSubject()) + .map(cm -> { + out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList()); + out.forEach(competition -> competition.setCanEditRegisters(true)); + List ids2 = new ArrayList<>(ids); + ids2.addAll(cm.stream().map(CompetitionModel::getId).toList()); + return ids2; + })) .call(ids -> repository.list("id NOT IN ?1 AND (publicVisible = TRUE OR registerMode IN ?2)", ids, securityCtx.isClubAdmin() ? List.of(RegisterMode.FREE, RegisterMode.HELLOASSO, @@ -165,8 +180,9 @@ public class CompetitionService { public Uni> getAllSystemTable(SecurityCtx securityCtx, CompetitionSystem system) { return repository.list("system = ?1", system) - .chain(l -> Uni.join().all(l.stream().map(cm -> permService.hasTablePerm(securityCtx, cm)).toList()) - .andCollectFailures()) + .chain(l -> Uni.join().all(l.stream().map(cm -> + permService.hasTablePerm(securityCtx, cm).onFailure().recoverWithNull() + ).toList()).andCollectFailures()) .map(l -> l.stream().filter(Objects::nonNull).map(CompetitionData::fromModel).toList()); } @@ -513,6 +529,64 @@ public class CompetitionService { .call(__ -> cache.invalidate(id)); } + public Uni getInternalData(SecurityCtx securityCtx, Long id) { + return permService.hasEditPerm(securityCtx, id) + .invoke(Unchecked.consumer(cm -> { + if (cm.getSystem() != CompetitionSystem.INTERNAL) + throw new DBadRequestException("Competition is not INTERNAL"); + })) + .chain(competitionModel -> { + SimpleCompetData data = SimpleCompetData.fromModel(competitionModel); + return vertx.getOrCreateContext().executeBlocking(() -> { + if (competitionModel.getAdmin() != null) + data.setAdmin( + competitionModel.getAdmin().stream().map(uuid -> keycloakService.getUserById(uuid)) + .filter(Optional::isPresent) + .map(user -> user.get().getUsername()) + .toList()); + if (competitionModel.getTable() != null) + data.setTable( + competitionModel.getTable().stream().map(uuid -> keycloakService.getUserById(uuid)) + .filter(Optional::isPresent) + .map(user -> user.get().getUsername()) + .toList()); + return data; + }); + }); + } + + public Uni setInternalData(SecurityCtx securityCtx, SimpleCompetData data) { + return permService.hasEditPerm(securityCtx, data.getId()) + .invoke(Unchecked.consumer(cm -> { + if (cm.getSystem() != CompetitionSystem.INTERNAL) + throw new DBadRequestException("Competition is not INTERNAL"); + })) + .chain(cm -> vertx.getOrCreateContext().executeBlocking(() -> { + ArrayList admin = new ArrayList<>(); + ArrayList table = new ArrayList<>(); + for (String username : data.getAdmin()) { + Optional opt = keycloakService.getUser(username); + if (opt.isEmpty()) + throw new DBadRequestException("User " + username + " not found"); + admin.add(opt.get().getId()); + } + for (String username : data.getTable()) { + Optional opt = keycloakService.getUser(username); + if (opt.isEmpty()) + throw new DBadRequestException("User " + username + " not found"); + table.add(opt.get().getId()); + } + + cm.setAdmin(admin); + cm.setTable(table); + cm.setConfig(data.getConfigForInternal()); + + return cm; + })) + .chain(cm -> Panache.withTransaction(() -> repository.persist(cm))) + .replaceWithVoid(); + } + public Uni getSafcaData(SecurityCtx securityCtx, Long id) { return permService.getSafcaConfig(id) .call(Unchecked.function(o -> { diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java index cf23435..63cdfd5 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/KeycloakService.java @@ -377,7 +377,11 @@ public class KeycloakService { public Optional getUser(UUID userId) { - UserResource user = keycloak.realm(realm).users().get(userId.toString()); + return getUserById(userId.toString()); + } + + public Optional getUserById(String userId) { + UserResource user = keycloak.realm(realm).users().get(userId); if (user == null) return Optional.empty(); else diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java index 82fccc0..77cabc5 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java @@ -71,6 +71,14 @@ public class CompetitionEndpoints { return service.getSafcaData(securityCtx, id); } + @GET + @Path("{id}/internalData") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni getInternalData(@PathParam("id") Long id) { + return service.getInternalData(securityCtx, id); + } + @GET @Path("all") @@ -95,6 +103,14 @@ public class CompetitionEndpoints { return service.setSafcaData(securityCtx, data); } + @POST + @Path("/internalData") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni setInternalData(SimpleCompetData data) { + return service.setInternalData(securityCtx, data); + } + @DELETE @Path("{id}") @Authenticated 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 8b33c2c..15cf538 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java @@ -35,10 +35,12 @@ public class CompetitionData { private String owner; private List registers; private boolean canEdit; + private boolean canEditRegisters; private String data1; private String data2; private String data3; private String data4; + private String config; public static CompetitionData fromModel(CompetitionModel model) { if (model == null) @@ -47,8 +49,8 @@ public class CompetitionData { 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()); + model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false, false, + model.getData1(), model.getData2(), model.getData3(), model.getData4(), model.getConfig()); } public static CompetitionData fromModelLight(CompetitionModel model) { @@ -58,14 +60,15 @@ public class CompetitionData { 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, - "", "", "", ""); + null, model.getClub().getName(), "", null, false, false, + "", "", "", "", "{}"); if (model.getRegisterMode() == RegisterMode.HELLOASSO) { out.setData1(model.getData1()); out.setData2(model.getData2()); } + return out; } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java index 3adaac7..10706c7 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleCompetData.java @@ -1,5 +1,9 @@ package fr.titionfire.ffsaf.rest.data; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.titionfire.ffsaf.data.model.CompetitionModel; import fr.titionfire.ffsaf.net2.data.SimpleCompet; import io.quarkus.runtime.annotations.RegisterForReflection; import lombok.AllArgsConstructor; @@ -25,4 +29,41 @@ public class SimpleCompetData { return new SimpleCompetData(compet.id(), compet.show_blason(), compet.show_flag(), new ArrayList<>(), new ArrayList<>()); } + + public static SimpleCompetData fromModel(CompetitionModel competitionModel) { + if (competitionModel == null) + return null; + + boolean show_blason = true; + boolean show_flag = false; + + if (competitionModel.getConfig() != null) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(competitionModel.getConfig()); + + if (rootNode.has("show_blason")) + show_blason = rootNode.get("show_blason").asBoolean(); + if (rootNode.has("show_flag")) + show_flag = rootNode.get("show_flag").asBoolean(); + + } catch (JsonProcessingException ignored) { + } + } + + return new SimpleCompetData(competitionModel.getId(), show_blason, show_flag, + new ArrayList<>(), new ArrayList<>()); + } + + public String getConfigForInternal(){ + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.createObjectNode() + .put("show_blason", this.show_blason) + .put("show_flag", this.show_flag); + try { + return objectMapper.writeValueAsString(rootNode); + } catch (JsonProcessingException e) { + return "{}"; + } + } } diff --git a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java index edd991b..c22e5ef 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java @@ -108,8 +108,8 @@ public class CompetitionWS { .call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> PermLevel.ADMIN) .onFailure() .recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> PermLevel.TABLE)) - .onFailure() - .recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW)) + //.onFailure() + //.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW)) .invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem.toString())) .invoke(prem -> LOGGER.infof("Connection permission: %s", prem)) .onFailure().transform(t -> new ForbiddenException())) diff --git a/src/main/webapp/src/pages/competition/CompetitionEdit.jsx b/src/main/webapp/src/pages/competition/CompetitionEdit.jsx index f08a153..f7eeedb 100644 --- a/src/main/webapp/src/pages/competition/CompetitionEdit.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionEdit.jsx @@ -51,9 +51,11 @@ export function CompetitionEdit() { {data.id !== null && } + onClick={_ => navigate(`/competition/${data.id}/register?type=${data.registerMode}`)}>Voir/Modifier + les participants} - {data.id !== null && data.system === "SAFCA" && } + {data.id !== null && (data.system === "SAFCA" || data.system === "INTERNAL") && + } {data.id !== null && <>
@@ -72,9 +74,12 @@ export function CompetitionEdit() { } -function ContentSAFCA({data2}) { +function ContentSAFCAAndInternal({data2, type = "SAFCA"}) { + const getDataPath = type === "SAFCA" ? `/competition/${data2.id}/safcaData` : `/competition/${data2.id}/internalData` + const setDataPath = type === "SAFCA" ? "/competition/safcaData" : "/competition/internalData" + const setLoading = useLoadingSwitcher() - const {data, error} = useFetch(`/competition/${data2.id}/safcaData`, setLoading, 1) + const {data, error} = useFetch(getDataPath, setLoading, 1) const [state, dispatch] = useReducer(SimpleReducer, []) const [state2, dispatch2] = useReducer(SimpleReducer, []) @@ -109,7 +114,7 @@ function ContentSAFCA({data2}) { out['table'] = state2.map(d => d.data) toast.promise( - apiAxios.post(`/competition/safcaData`, out), + apiAxios.post(setDataPath, out), { pending: "Enregistrement des paramètres en cours", success: "Paramètres enregistrée avec succès 🎉", @@ -402,7 +407,8 @@ function Content({data}) {
  • 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 > + 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.
  • diff --git a/src/main/webapp/src/pages/competition/CompetitionView.jsx b/src/main/webapp/src/pages/competition/CompetitionView.jsx index c179f73..259c3db 100644 --- a/src/main/webapp/src/pages/competition/CompetitionView.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionView.jsx @@ -79,6 +79,13 @@ function MakeContent({data}) { href={`https://www.helloasso.com/associations/${data.data1}/evenements/${data.data2}`} target="_blank" rel="noopener noreferrer">{`https://www.helloasso.com/associations/${data.data1}/evenements/${data.data2}`}

    } + {data.canEditRegisters && +
    + +
    + }
    } diff --git a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx index 8454f11..1fd1290 100644 --- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx @@ -342,7 +342,7 @@ function CategoryHeader({cat, setCatId}) { }, [cats]); useEffect(() => { - if (cats && cats.length > 0 && !cat || (cats && !cats.find(c => c.id === cat.id))) { + if (cats && cats.length > 0 && (!cat || (cats && !cats.find(c => c.id === cat.id)))) { setCatId(cats.sort((a, b) => a.name.localeCompare(b.name))[0].id); } else if (cats && cats.length === 0) { setModal({});