diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/AffiliationRequestModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/AffiliationRequestModel.java new file mode 100644 index 0000000..fc09800 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/model/AffiliationRequestModel.java @@ -0,0 +1,43 @@ +package fr.titionfire.ffsaf.data.model; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.persistence.*; +import lombok.*; + + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@RegisterForReflection + +@Entity +@Table(name = "affiliation_request") +public class AffiliationRequestModel { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + String name; + String siren; + String RNA; + String address; + + String president_lname; + String president_fname; + String president_email; + int president_lincence; + + String tresorier_lname; + String tresorier_fname; + String tresorier_email; + int tresorier_lincence; + + String secretaire_lname; + String secretaire_fname; + String secretaire_email; + int secretaire_lincence; + + int saison; +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/AffiliationRequestRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/AffiliationRequestRepository.java new file mode 100644 index 0000000..bbe55b0 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/AffiliationRequestRepository.java @@ -0,0 +1,9 @@ +package fr.titionfire.ffsaf.data.repository; + +import fr.titionfire.ffsaf.data.model.AffiliationRequestModel; +import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class AffiliationRequestRepository implements PanacheRepositoryBase { +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java new file mode 100644 index 0000000..071b9a8 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java @@ -0,0 +1,60 @@ +package fr.titionfire.ffsaf.domain.service; + +import fr.titionfire.ffsaf.data.model.AffiliationRequestModel; +import fr.titionfire.ffsaf.data.repository.AffiliationRequestRepository; +import fr.titionfire.ffsaf.data.repository.CombRepository; +import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; +import fr.titionfire.ffsaf.utils.Utils; +import io.quarkus.hibernate.reactive.panache.Panache; +import io.quarkus.hibernate.reactive.panache.common.WithSession; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@WithSession +@ApplicationScoped +public class AffiliationService { + + @Inject + CombRepository combRepository; + + @Inject + AffiliationRequestRepository repository; + + @ConfigProperty(name = "upload_dir") + String media; + + public Uni save(AffiliationRequestForm form) { + AffiliationRequestModel affModel = form.toModel(); + affModel.setSaison(Utils.getSaison()); + + return Uni.createFrom().item(affModel) + .call(model -> ((model.getPresident_lincence() != 0) ? combRepository.find("licence", + model.getPresident_lincence()).count().invoke(count -> { + if (count == 0) { + throw new IllegalArgumentException("Licence président inconnue"); + } + }) : Uni.createFrom().nullItem()) + ) + .call(model -> ((model.getTresorier_lincence() != 0) ? combRepository.find("licence", + model.getTresorier_lincence()).count().invoke(count -> { + if (count == 0) { + throw new IllegalArgumentException("Licence trésorier inconnue"); + } + }) : Uni.createFrom().nullItem()) + ) + .call(model -> ((model.getSecretaire_lincence() != 0) ? combRepository.find("licence", + model.getSecretaire_lincence()).count().invoke(count -> { + if (count == 0) { + throw new IllegalArgumentException("Licence secrétaire inconnue"); + } + }) : Uni.createFrom().nullItem()) + ).chain(model -> Panache.withTransaction(() -> repository.persist(model))) + .call(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getLogo(), media, + "aff_request/logo"))) + .call(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getStatus(), media, + "aff_request/status"))) + .map(__ -> "Ok"); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java new file mode 100644 index 0000000..dc2bbbc --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java @@ -0,0 +1,29 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; +import io.smallrye.mutiny.Uni; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; + +@Path("api/affiliation") +public class AffiliationEndpoints { + + + + @POST + @Path("save") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Uni saveAffRequest(AffiliationRequestForm form) { + System.out.println(form); + return Uni.createFrom().item("OK"); + } + /*@POST + @Path("affiliation") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Uni saveAffRequest(AffiliationRequestForm form) { + System.out.println(form); + return service.save(form); + }*/ +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java index d254a1b..6bfa442 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java @@ -1,28 +1,53 @@ package fr.titionfire.ffsaf.rest; +import fr.titionfire.ffsaf.domain.service.AffiliationService; import fr.titionfire.ffsaf.rest.client.SirenService; import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot; +import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; import io.smallrye.mutiny.Uni; +import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; +import jodd.net.MimeTypes; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; +import java.io.*; +import java.net.URLConnection; +import java.nio.file.Files; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + @Path("api/asso") public class AssoEndpoints { @RestClient SirenService sirenService; + @Inject + AffiliationService service; + + @ConfigProperty(name = "upload_dir") + String media; + @GET @Path("siren/{siren}") @Produces(MediaType.APPLICATION_JSON) public Uni getInfoSiren(@PathParam("siren") String siren) { return sirenService.get_unite(siren).onFailure().transform(throwable -> { - if (throwable instanceof WebApplicationException exception){ + if (throwable instanceof WebApplicationException exception) { if (exception.getResponse().getStatus() == 400) return new BadRequestException("Not found"); } return throwable; }); } + + @POST + @Path("affiliation") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Uni saveAffRequest(AffiliationRequestForm form) { + return service.save(form); + } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java index c29d9bd..1c69154 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java @@ -8,6 +8,7 @@ import fr.titionfire.ffsaf.rest.from.FullMemberForm; import fr.titionfire.ffsaf.utils.GroupeUtils; import fr.titionfire.ffsaf.utils.PageResult; import fr.titionfire.ffsaf.utils.Pair; +import fr.titionfire.ffsaf.utils.Utils; import io.quarkus.oidc.IdToken; import io.quarkus.security.Authenticated; import io.quarkus.security.identity.SecurityIdentity; @@ -58,8 +59,10 @@ public class CombEndpoints { @Path("/find/admin") @RolesAllowed({"federation_admin"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getFindAdmin(@QueryParam("limit") Integer limit, @QueryParam("page") Integer page, - @QueryParam("search") String search, @QueryParam("club") String club) { + public Uni> getFindAdmin(@QueryParam("limit") Integer limit, + @QueryParam("page") Integer page, + @QueryParam("search") String search, + @QueryParam("club") String club) { if (limit == null) limit = 50; if (page == null || page < 1) @@ -71,7 +74,8 @@ public class CombEndpoints { @Path("/find/club") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getFindClub(@QueryParam("limit") Integer limit, @QueryParam("page") Integer page, + public Uni> getFindClub(@QueryParam("limit") Integer limit, + @QueryParam("page") Integer page, @QueryParam("search") String search) { if (limit == null) limit = 50; @@ -99,7 +103,8 @@ public class CombEndpoints { if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out); })).chain(() -> { if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(replacePhoto(id, input.getPhoto_data())).invoke(Unchecked.consumer(out -> { + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )).invoke(Unchecked.consumer(out -> { if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out); })); else @@ -117,7 +122,8 @@ public class CombEndpoints { if (id == null) throw new InternalError("Fail to creat member data"); })).call(id -> { if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(replacePhoto(id, input.getPhoto_data())); + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )); else return Uni.createFrom().nullItem(); }); @@ -134,7 +140,8 @@ public class CombEndpoints { if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out); })).chain(() -> { if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(replacePhoto(id, input.getPhoto_data())).invoke(Unchecked.consumer(out -> { + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )).invoke(Unchecked.consumer(out -> { if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out); })); else @@ -153,38 +160,13 @@ public class CombEndpoints { if (id == null) throw new InternalError("Fail to creat member data"); })).call(id -> { if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(replacePhoto(id, input.getPhoto_data())); + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )); else return Uni.createFrom().nullItem(); }); } - private Future replacePhoto(long id, byte[] input) { - return CompletableFuture.supplyAsync(() -> { - try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input))) { - String mimeType = URLConnection.guessContentTypeFromStream(is); - String[] detectedExtensions = MimeTypes.findExtensionsByMimeTypes(mimeType, false); - if (detectedExtensions.length == 0) - throw new IOException("Fail to detect file extension for MIME type " + mimeType); - - FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id)); - File[] files = new File(media, "ppMembre").listFiles(filter); - if (files != null) { - for (File file : files) { - //noinspection ResultOfMethodCallIgnored - file.delete(); - } - } - - String extension = "." + detectedExtensions[0]; - Files.write(new File(media, "ppMembre/" + id + extension).toPath(), input); - return "OK"; - } catch (IOException e) { - return e.getMessage(); - } - }); - } - @GET @Path("{id}/photo") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java b/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java index 16d34fa..04f002d 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/from/AffiliationRequestForm.java @@ -1,5 +1,6 @@ package fr.titionfire.ffsaf.rest.from; +import fr.titionfire.ffsaf.data.model.AffiliationRequestModel; import jakarta.ws.rs.FormParam; import jakarta.ws.rs.core.MediaType; import lombok.Getter; @@ -28,4 +29,59 @@ public class AffiliationRequestForm { @FormParam("logo") @PartType(MediaType.APPLICATION_OCTET_STREAM) private byte[] logo = new byte[0]; + + @FormParam("president-nom") + private String president_lname = null; + @FormParam("president-prenom") + private String president_fname = null; + @FormParam("president-mail") + private String president_email = null; + @FormParam("president-licence") + private String president_lincence = null; + + @FormParam("tresorier-nom") + private String tresorier_lname = null; + @FormParam("tresorier-prenom") + private String tresorier_fname = null; + @FormParam("tresorier-mail") + private String tresorier_email = null; + @FormParam("tresorier-licence") + private String tresorier_lincence = null; + + @FormParam("secretaire-nom") + private String secretaire_lname = null; + @FormParam("secretaire-prenom") + private String secretaire_fname = null; + @FormParam("secretaire-mail") + private String secretaire_email = null; + @FormParam("secretaire-licence") + private String secretaire_lincence = null; + + public AffiliationRequestModel toModel() { + AffiliationRequestModel model = new AffiliationRequestModel(); + model.setName(this.getName()); + model.setSiren(this.getSiren()); + model.setRNA(this.getRna()); + model.setAddress(this.getAdresse()); + + model.setPresident_lname(this.getPresident_lname()); + model.setPresident_fname(this.getPresident_fname()); + model.setPresident_email(this.getPresident_email()); + model.setPresident_lincence((this.getPresident_lincence() == null || this.getPresident_lincence().isBlank()) + ? 0 : Integer.parseInt(this.getPresident_lincence())); + + model.setTresorier_lname(this.getTresorier_lname()); + model.setTresorier_fname(this.getTresorier_fname()); + model.setTresorier_email(this.getTresorier_email()); + model.setTresorier_lincence((this.getPresident_lincence() == null || this.getPresident_lincence().isBlank()) + ? 0 : Integer.parseInt(this.getTresorier_lincence())); + + model.setSecretaire_lname(this.getSecretaire_lname()); + model.setSecretaire_fname(this.getSecretaire_fname()); + model.setSecretaire_email(this.getSecretaire_email()); + model.setSecretaire_lincence((this.getPresident_lincence() == null || this.getPresident_lincence().isBlank()) + ? 0 : Integer.parseInt(this.getSecretaire_lincence())); + + return model; + } } diff --git a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java index 1a33bb2..88c4925 100644 --- a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java +++ b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java @@ -1,7 +1,14 @@ package fr.titionfire.ffsaf.utils; +import jodd.net.MimeTypes; + +import java.io.*; +import java.net.URLConnection; +import java.nio.file.Files; import java.util.Calendar; import java.util.Date; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; public class Utils { @@ -19,4 +26,35 @@ public class Utils { return calendar.get(Calendar.YEAR) - 1; } } + + public static Future replacePhoto(long id, byte[] input, String media, String dir) { + return CompletableFuture.supplyAsync(() -> { + try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input))) { + String mimeType = URLConnection.guessContentTypeFromStream(is); + String[] detectedExtensions = MimeTypes.findExtensionsByMimeTypes(mimeType, false); + if (detectedExtensions.length == 0) + throw new IOException("Fail to detect file extension for MIME type " + mimeType); + + File dirFile = new File(media, dir); + if (!dirFile.exists()) + if (dirFile.mkdirs()) + throw new IOException("Fail to create directory " + dir); + + FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id)); + File[] files = dirFile.listFiles(filter); + if (files != null) { + for (File file : files) { + //noinspection ResultOfMethodCallIgnored + file.delete(); + } + } + + String extension = "." + detectedExtensions[0]; + Files.write(new File(dirFile, id + extension).toPath(), input); + return "OK"; + } catch (IOException e) { + return e.getMessage(); + } + }); + } } diff --git a/src/main/webapp/src/pages/DemandeAff.jsx b/src/main/webapp/src/pages/DemandeAff.jsx index 585bae7..b8f92b7 100644 --- a/src/main/webapp/src/pages/DemandeAff.jsx +++ b/src/main/webapp/src/pages/DemandeAff.jsx @@ -31,28 +31,35 @@ export function DemandeAff() { event.preventDefault() const formData = new FormData(event.target) toast.promise( - apiAxios.post(`asso/affiliation`, formData), + apiAxios.post(`asso/affiliation`, formData, { headers: {'Accept': '*/*'}}), { pending: "Enregistrement de la demande d'affiliation en cours", success: "Demande d'affiliation enregistrée avec succès 🎉", error: "Échec de la demande d'affiliation 😕" } ).then(_ => { - navigate("/affiliation/ok") + // navigate("/affiliation/ok") }) } return

Demande d'affiliation

-

L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de l’année suivante.

+

L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de l’année + suivante.

Pour s’affilier, une association sportive doit réunir les conditions suivantes :
  • Avoir son siège social en France ou Principauté de Monaco
  • Être constituée conformément au chapitre 1er du titre II du livre 1er du Code du Sport
  • Poursuivre un objet social entrant dans la définition de l’article 1 des statuts de la Fédération
  • -
  • Disposer de statuts compatibles avec les principes d’organisation et de fonctionnement de la Fédération
  • -
  • Assurer en son sein la liberté d’opinion et le respect des droits de la défense, et s’interdire toute discrimination
  • -
  • Respecter les règles d’encadrement, d’hygiène et de sécurité établies par les règlements de la Fédération
  • +
  • Disposer de statuts compatibles avec les principes d’organisation et de fonctionnement de la + Fédération +
  • +
  • Assurer en son sein la liberté d’opinion et le respect des droits de la défense, et s’interdire toute + discrimination +
  • +
  • Respecter les règles d’encadrement, d’hygiène et de sécurité établies par les règlements de la + Fédération +
@@ -68,16 +75,20 @@ export function DemandeAff() {
-

Après validation de votre demande, vous recevrez un login et mot de passe provisoire pour accéder à votre espace FFSAF

+

Après validation de votre demande, vous recevrez un login et mot de passe provisoire pour + accéder à votre espace FFSAF

Notez que pour finaliser votre affiliation, il vous faudra :
    -
  • Disposer d’au moins trois membres licenciés, dont le président, le trésorier et le secrétaire
  • +
  • Disposer d’au moins trois membres licenciés, dont le président, le trésorier et le + secrétaire +
  • S'être acquitté des cotisations prévues par les règlements fédéraux
- +
@@ -113,7 +124,8 @@ function AssoInfo() { return <>
Nom de l'association* -
@@ -121,24 +133,29 @@ function AssoInfo() { N° SIREN* setSiren(e.target.value)}/> - +
Dénomination -
RNA - setRna(e.target.value)}/>
Adresse* - setAdresse(e.target.value)}/>
@@ -149,7 +166,8 @@ function AssoInfo() {
- +
; } @@ -159,19 +177,29 @@ function MembreInfo({role}) {
- +
- - + +
- - + + +
+
+
+
OU
+
+ +
@@ -181,7 +209,8 @@ export function DemandeAffOk() { return (

Demande d'affiliation envoyée avec succès

-

Une fois votre demande validée, vous recevrez un login et mot de passe provisoire pour accéder à votre espace FFSAF

+

Une fois votre demande validée, vous recevrez un login et mot de passe provisoire pour accéder à votre + espace FFSAF

); } \ No newline at end of file