feat: helloasso competition register

This commit is contained in:
Thibaut Valentin 2025-08-20 21:59:26 +02:00
parent 11dca5630c
commit a8a41d4e36
4 changed files with 134 additions and 8 deletions

View File

@ -8,6 +8,7 @@ import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet; import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet; import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.net2.request.SReqRegister; import fr.titionfire.ffsaf.net2.request.SReqRegister;
import fr.titionfire.ffsaf.rest.client.dto.NotificationData;
import fr.titionfire.ffsaf.rest.data.CompetitionData; import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.RegisterRequestData; import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData; import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
@ -22,13 +23,18 @@ import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName; import io.quarkus.cache.CacheName;
import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.reactive.ReactiveMailer;
import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked; import io.smallrye.mutiny.unchecked.Unchecked;
import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.core.Vertx;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import java.util.*; import java.util.*;
@ -37,6 +43,7 @@ import java.util.stream.Stream;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class CompetitionService { public class CompetitionService {
private static final Logger LOGGER = Logger.getLogger(CompetitionService.class);
@Inject @Inject
CompetitionRepository repository; CompetitionRepository repository;
@ -65,6 +72,10 @@ public class CompetitionService {
@Inject @Inject
CompetPermService permService; CompetPermService permService;
@SuppressWarnings("CdiInjectionPointsInspection")
@Inject
ReactiveMailer reactiveMailer;
@Inject @Inject
Vertx vertx; Vertx vertx;
@ -249,7 +260,7 @@ public class CompetitionService {
if ("admin".equals(source)) if ("admin".equals(source))
return permService.hasEditPerm(securityCtx, id) return permService.hasEditPerm(securityCtx, id)
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname()) .chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
.chain(combModel -> updateRegister(id, data, c, combModel, true))) .chain(combModel -> updateRegister(data, c, combModel, true)))
.chain(r -> Mutiny.fetch(r.getMembre().getLicences()) .chain(r -> Mutiny.fetch(r.getMembre().getLicences())
.map(licences -> SimpleRegisterComb.fromModel(r, licences))); .map(licences -> SimpleRegisterComb.fromModel(r, licences)));
if ("club".equals(source)) if ("club".equals(source))
@ -269,7 +280,7 @@ public class CompetitionService {
throw new DForbiddenException( throw new DForbiddenException(
"Vous n'avez pas le droit d'inscrire ce membre (par décision de l'administrateur de la compétition)"); "Vous n'avez pas le droit d'inscrire ce membre (par décision de l'administrateur de la compétition)");
})) }))
.chain(combModel -> updateRegister(id, data, c, combModel, false))) .chain(combModel -> updateRegister(data, c, combModel, false)))
.chain(r -> Mutiny.fetch(r.getMembre().getLicences()) .chain(r -> Mutiny.fetch(r.getMembre().getLicences())
.map(licences -> SimpleRegisterComb.fromModel(r, licences))); .map(licences -> SimpleRegisterComb.fromModel(r, licences)));
@ -286,13 +297,13 @@ public class CompetitionService {
throw new DForbiddenException( throw new DForbiddenException(
"Vous n'avez pas le droit de vous inscrire (par décision de l'administrateur de la compétition)"); "Vous n'avez pas le droit de vous inscrire (par décision de l'administrateur de la compétition)");
})) }))
.chain(combModel -> updateRegister(id, data, c, combModel, false))) .chain(combModel -> updateRegister(data, c, combModel, false)))
.map(r -> SimpleRegisterComb.fromModel(r, List.of())); .map(r -> SimpleRegisterComb.fromModel(r, List.of()));
} }
private Uni<RegisterModel> updateRegister(Long id, RegisterRequestData data, CompetitionModel c, private Uni<RegisterModel> updateRegister(RegisterRequestData data, CompetitionModel c,
MembreModel combModel, boolean admin) { MembreModel combModel, boolean admin) {
return registerRepository.find("competition.id = ?1 AND membre = ?2", id, combModel).firstResult() return registerRepository.find("competition = ?1 AND membre = ?2", c, combModel).firstResult()
.onFailure().recoverWithNull() .onFailure().recoverWithNull()
.map(Unchecked.function(r -> { .map(Unchecked.function(r -> {
if (r != null) { if (r != null) {
@ -480,4 +491,69 @@ public class CompetitionService {
})) }))
.call(__ -> cache.invalidate(data.getId())); .call(__ -> cache.invalidate(data.getId()));
} }
public Uni<Response> registerHelloAsso(NotificationData data) {
String organizationSlug = data.getOrganizationSlug();
String formSlug = data.getFormSlug();
RegisterRequestData req = new RegisterRequestData(null, "", "", null, 0, false);
return repository.find("data1 = ?1 AND data2 = ?2", organizationSlug, formSlug).firstResult()
.onFailure().recoverWithNull()
.chain(cm -> {
Uni<?> uni = Uni.createFrom().nullItem();
List<String> place = List.of(cm.getData3().toLowerCase().split(";"));
List<String> fail = new ArrayList<>();
for (NotificationData.Item item : data.getItems()) {
if (!place.contains(item.getName().toLowerCase()))
continue;
if (item.getCustomFields() == null || item.getCustomFields().isEmpty()) {
fail.add("%s %s - licence n°???".formatted(item.getUser().getLastName(),
item.getUser().getFirstName()));
continue;
}
Optional<Long> optional = item.getCustomFields().stream()
.filter(cf -> cf.getName().equalsIgnoreCase("Numéro de licence")).findAny().map(
NotificationData.CustomField::getAnswer).map(Long::valueOf);
if (optional.isPresent()) {
uni = uni.call(__ -> membreService.getByLicence(optional.get())
.invoke(Unchecked.consumer(m -> {
if (m == null)
throw new NotFoundException();
}))
.chain(m -> updateRegister(req, cm, m, true)))
.onFailure().recoverWithItem(throwable -> {
fail.add("%s %s - licence n°%d".formatted(item.getUser().getLastName(),
item.getUser().getFirstName(), optional.get()));
return null;
})
.replaceWithVoid();
} else {
fail.add("%s %s - licence n°???".formatted(item.getUser().getLastName(),
item.getUser().getFirstName()));
}
}
return uni.call(__ -> fail.isEmpty() ? Uni.createFrom().nullItem() :
reactiveMailer.send(
Mail.withText(cm.getData4(),
"FFSAF - Compétition - Erreur HelloAsso",
String.format(
"""
Bonjour,
Une erreur a été rencontrée lors de l'enregistrement d'une inscription à votre compétition %s pour les combattants suivants:
%s
Cordialement,
L'intranet de la FFSAF
""", cm.getName(), String.join("\r\n", fail))
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
).onFailure().invoke(e -> LOGGER.error("Fail to send email", e)));
})
.onFailure().invoke(Throwable::printStackTrace)
.map(__ -> Response.ok().build());
}
} }

View File

@ -13,13 +13,24 @@ public class WebhookService {
@Inject @Inject
CheckoutService checkoutService; CheckoutService checkoutService;
@Inject
CompetitionService competitionService;
@ConfigProperty(name = "helloasso.organizationSlug") @ConfigProperty(name = "helloasso.organizationSlug")
String organizationSlug; String organizationSlug;
public Uni<Response> helloAssoNotification(HelloassoNotification notification) { public Uni<Response> helloAssoNotification(HelloassoNotification notification) {
if (notification.getEventType().equals("Payment")){ if (notification.getEventType().equals("Payment")) {
if (notification.getData().getOrder().getOrganizationSlug().equalsIgnoreCase(organizationSlug)){ if (notification.getData().getOrder().getFormType().equals("Checkout")) {
return checkoutService.paymentStatusChange(notification.getData().getState(), notification.getMetadata()); if (notification.getData().getOrder().getOrganizationSlug().equalsIgnoreCase(organizationSlug)) {
return checkoutService.paymentStatusChange(notification.getData().getState(),
notification.getMetadata());
}
}
}else if (notification.getEventType().equals("Order")){
if (notification.getData().getFormType().equals("Event")) {
return competitionService.registerHelloAsso(notification.getData());
} }
} }

View File

@ -5,6 +5,8 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -12,11 +14,14 @@ import lombok.NoArgsConstructor;
public class NotificationData { public class NotificationData {
private Order order; private Order order;
private Integer id; private Integer id;
private String formSlug;
private String formType;
private String organizationSlug; private String organizationSlug;
private String checkoutIntentId; private String checkoutIntentId;
private String oldSlugOrganization; // Pour les changements de nom d'association private String oldSlugOrganization; // Pour les changements de nom d'association
private String newSlugOrganization; private String newSlugOrganization;
private String state; // Pour les formulaires private String state; // Pour les formulaires
private List<Item> items;
@Data @Data
@ -26,5 +31,35 @@ public class NotificationData {
public static class Order { public static class Order {
private Integer id; private Integer id;
private String organizationSlug; private String organizationSlug;
private String formSlug;
private String formType;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class Item {
private String name;
private User user;
private List<CustomField> customFields;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class User {
private String firstName;
private String lastName;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class CustomField {
private String name;
private String answer;
} }
} }

View File

@ -1,9 +1,13 @@
package fr.titionfire.ffsaf.rest.data; package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
@Data @Data
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection @RegisterForReflection
public class RegisterRequestData { public class RegisterRequestData {
private Long licence; private Long licence;