diff --git a/pom.xml b/pom.xml index 059225b..525fd91 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,15 @@ jmimemagic 0.1.3 + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-swagger-ui + diff --git a/src/main/java/fr/titionfire/BlackPage.java b/src/main/java/fr/titionfire/BlackPage.java deleted file mode 100644 index a77d835..0000000 --- a/src/main/java/fr/titionfire/BlackPage.java +++ /dev/null @@ -1,18 +0,0 @@ -package fr.titionfire; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; - -@Path("/api") -public class BlackPage { - - @GET - @Produces(MediaType.TEXT_PLAIN) - public Response get() { - return Response.noContent().build(); - } - -} diff --git a/src/main/java/fr/titionfire/PingPage.java b/src/main/java/fr/titionfire/PingPage.java new file mode 100644 index 0000000..2acd198 --- /dev/null +++ b/src/main/java/fr/titionfire/PingPage.java @@ -0,0 +1,27 @@ +package fr.titionfire; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +@Tag(name = "Ping API", description = "API pour tester la connectivité") +@Path("/api") +public class PingPage { + + @Operation(summary = "Renvoie un message de réussite", description = "Cette méthode renvoie un message de réussite si la connexion est établie avec succès.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite") + }) + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response get() { + return Response.ok().build(); + } + +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java index 09f864e..3926f5c 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/AffiliationService.java @@ -233,7 +233,7 @@ public class AffiliationService { .call(m -> (m.getUserId() == null) ? keycloakService.initCompte(m.getId()) : keycloakService.setClubGroupMembre(m, club)) .call(m -> Panache.withTransaction(() -> licenceRepository.persist( - new LicenceModel(null, m, saison, false, true)))); + new LicenceModel(null, m, saison, null, true)))); } public Uni accept(AffiliationRequestSaveForm form) { @@ -336,6 +336,7 @@ public class AffiliationService { public Uni setAffiliation(long id, int saison) { return clubRepository.findById(id) .onItem().ifNull().failWith(new DNotFoundException("Club non trouvé")) + .call(model -> Mutiny.fetch(model.getAffiliations())) .invoke(Unchecked.consumer(club -> { if (club.getAffiliations().stream().anyMatch(affiliation -> affiliation.getSaison() == saison)) { throw new DBadRequestException("Affiliation déjà existante"); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java index 6e6448d..35a4492 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationEndpoints.java @@ -2,13 +2,8 @@ package fr.titionfire.ffsaf.rest; import fr.titionfire.ffsaf.domain.service.AffiliationService; import fr.titionfire.ffsaf.rest.data.SimpleAffiliation; -import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation; -import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliationResume; import fr.titionfire.ffsaf.rest.exception.DForbiddenException; -import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; -import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm; import fr.titionfire.ffsaf.utils.GroupeUtils; -import fr.titionfire.ffsaf.utils.Utils; import io.quarkus.oidc.IdToken; import io.quarkus.security.identity.SecurityIdentity; import io.smallrye.mutiny.Uni; @@ -17,14 +12,17 @@ import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; -import java.net.URISyntaxException; import java.util.List; import java.util.function.Consumer; +@Tag(name = "Affiliation API", description = "API pour gérer les affiliations") @Path("api/affiliation") public class AffiliationEndpoints { @@ -38,85 +36,20 @@ public class AffiliationEndpoints { @Inject SecurityIdentity securityIdentity; - @ConfigProperty(name = "upload_dir") - String media; - Consumer checkPerm = Unchecked.consumer(id -> { if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(id, idToken)) throw new DForbiddenException(); }); - @GET - @Path("/request") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.APPLICATION_JSON) - public Uni> getAllAffRequest() { - return service.getAllReq().map(o -> o.stream().map(SimpleReqAffiliationResume::fromModel).toList()); - } - - @POST - @Path("/request") - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni saveAffRequest(AffiliationRequestForm form) { - return service.save(form); - } - - @GET - @Path("/request/{id}") - @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.APPLICATION_JSON) - public Uni getAffRequest(@PathParam("id") long id) { - return service.getRequest(id).invoke(Unchecked.consumer(o -> { - if (o.getClub() == null && !securityIdentity.getRoles().contains("federation_admin")) - throw new DForbiddenException(); - })).invoke(o -> checkPerm.accept(o.getClub())); - } - - @DELETE - @Path("/request/{id}") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.APPLICATION_JSON) - public Uni getDelAffRequest(@PathParam("id") long id) { - return service.getRequest(id).invoke(Unchecked.consumer(o -> { - if (o.getClub() == null && !securityIdentity.getRoles().contains("federation_admin")) - throw new DForbiddenException(); - })).invoke(o -> checkPerm.accept(o.getClub())) - .chain(o -> service.deleteReqAffiliation(id)); - } - - @PUT - @Path("/request/save") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni saveAdminAffRequest(AffiliationRequestSaveForm form) { - return service.saveAdmin(form); - } - - @PUT - @Path("/request/edit") - @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni saveEditAffRequest(AffiliationRequestForm form) { - return service.saveEdit(form); - } - - @PUT - @Path("/request/apply") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni acceptAffRequest(AffiliationRequestSaveForm form) { - return service.accept(form); - } - - @GET @Path("/current") @RolesAllowed({"federation_admin"}) @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les affiliations pour la saison en cours", description = "Cette méthode renvoie les affiliations pour la saison en cours. Seuls les administrateurs de la fédération peuvent accéder à cette méthode.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) public Uni> getCurrentSaisonAffiliationAdmin() { return service.getCurrentSaisonAffiliation(); } @@ -125,15 +58,30 @@ public class AffiliationEndpoints { @Path("{id}") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getAffiliation(@PathParam("id") long id) { + @Operation(summary = "Renvoie les affiliations pour un club", description = "Cette méthode renvoie les affiliations pour un club donné. Seuls les administrateurs de la fédération et les présidents, secrétaires et responsables intranet du club peuvent accéder à cette méthode.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Club non trouvé") + }) + public Uni> getAffiliation( + @Parameter(description = "L'identifiant du club") @PathParam("id") long id) { return Uni.createFrom().item(id).invoke(checkPerm).chain(__ -> service.getAffiliation(id)); } - @PUT + @POST @Path("{id}") @RolesAllowed("federation_admin") @Produces(MediaType.APPLICATION_JSON) - public Uni setAffiliation(@PathParam("id") long id, @QueryParam("saison") int saison) { + @Operation(summary = "Ajoute une affiliation pour un club", description = "Cette méthode ajoute une affiliation pour un club et une saison donné. Seuls les administrateurs de la fédération peuvent accéder à cette méthode.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Club non trouvé") + }) + public Uni setAffiliation( + @Parameter(description = "L'identifiant du club") @PathParam("id") long id, + @Parameter(description = "La saison à pour la quelle ajoute l'affiliation") @QueryParam("saison") int saison) { return service.setAffiliation(id, saison); } @@ -141,22 +89,13 @@ public class AffiliationEndpoints { @Path("{id}") @RolesAllowed("federation_admin") @Produces(MediaType.TEXT_PLAIN) - public Uni deleteAffiliation(@PathParam("id") long id) { + @Operation(summary = "Supprime une affiliation", description = "Cette méthode supprime l'affiliation {id}. Seuls les administrateurs de la fédération peuvent accéder à cette méthode.") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) + public Uni deleteAffiliation( + @Parameter(description = "L'identifiant de l'affiliation") @PathParam("id") long id) { return service.deleteAffiliation(id); } - - @GET - @Path("/request/{id}/logo") - @RolesAllowed({"federation_admin"}) - public Uni getLogo(@PathParam("id") long id) throws URISyntaxException { - return Utils.getMediaFile(id, media, "aff_request/logo", Uni.createFrom().nullItem()); - } - - @GET - @Path("/request/{id}/status") - @RolesAllowed({"federation_admin"}) - public Uni getStatus(@PathParam("id") long id) throws URISyntaxException { - return Utils.getMediaFile(id, media, "aff_request/status", "affiliation_request_" + id + ".pdf", - Uni.createFrom().nullItem()); - } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java new file mode 100644 index 0000000..b8a8bb5 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/AffiliationRequestEndpoints.java @@ -0,0 +1,198 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.domain.service.AffiliationService; +import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation; +import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliationResume; +import fr.titionfire.ffsaf.rest.exception.DForbiddenException; +import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; +import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm; +import fr.titionfire.ffsaf.utils.GroupeUtils; +import fr.titionfire.ffsaf.utils.Utils; +import io.quarkus.oidc.IdToken; +import io.quarkus.security.identity.SecurityIdentity; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.unchecked.Unchecked; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + +import java.net.URISyntaxException; +import java.util.List; +import java.util.function.Consumer; + +@Path("api/affiliation/request") +public class AffiliationRequestEndpoints { + + @Inject + AffiliationService service; + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + SecurityIdentity securityIdentity; + + @ConfigProperty(name = "upload_dir") + String media; + + Consumer checkPerm = Unchecked.consumer(id -> { + if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(id, idToken)) + throw new DForbiddenException(); + }); + + @GET + @Path("") + @RolesAllowed({"federation_admin"}) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie toutes les demandes d'affiliation", description = "Cette méthode renvoie toutes les " + + "demandes d'affiliation sous forme de résumés.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) + public Uni> getAllAffRequest() { + return service.getAllReq().map(o -> o.stream().map(SimpleReqAffiliationResume::fromModel).toList()); + } + + @POST + @Path("") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Enregistre une nouvelle demande d'affiliation", description = "Cette méthode enregistre une " + + "nouvelle demande d'affiliation à partir des données soumises dans le formulaire. Ne nécessite pas d'authentification.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) + public Uni saveAffRequest(AffiliationRequestForm form) { + return service.save(form); + } + + @GET + @Path("/{id}") + @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie une demande d'affiliation", description = "Cette méthode renvoie une demande d'affiliation " + + "pour l'identifiant spécifié.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Demande d'affiliation non trouvée") + }) + public Uni getAffRequest( + @Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) { + return service.getRequest(id).invoke(Unchecked.consumer(o -> { + if (o.getClub() == null && !securityIdentity.getRoles().contains("federation_admin")) + throw new DForbiddenException(); + })).invoke(o -> checkPerm.accept(o.getClub())); + } + + @DELETE + @Path("/{id}") + @RolesAllowed({"federation_admin"}) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " + + "d'affiliation pour l'identifiant spécifié.") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Demande d'affiliation non trouvée") + }) + public Uni getDelAffRequest( + @Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) { + return service.getRequest(id).invoke(Unchecked.consumer(o -> { + if (o.getClub() == null && !securityIdentity.getRoles().contains("federation_admin")) + throw new DForbiddenException(); + })).invoke(o -> checkPerm.accept(o.getClub())) + .chain(o -> service.deleteReqAffiliation(id)); + } + + @PUT + @Path("/save") + @RolesAllowed({"federation_admin"}) + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Enregistre une demande d'affiliation en tant qu'admin", description = "Cette méthode " + + "enregistre une demande d'affiliation en tant qu'admin à partir des données soumises dans le formulaire.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) + public Uni saveAdminAffRequest(AffiliationRequestSaveForm form) { + return service.saveAdmin(form); + } + + @PUT + @Path("/edit") + @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Modifie une demande d'affiliation", description = "Cette méthode modifie une demande " + + "d'affiliation à partir des données soumises dans le formulaire.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) + public Uni saveEditAffRequest(AffiliationRequestForm form) { + return service.saveEdit(form); + } + + @PUT + @Path("/apply") + @RolesAllowed({"federation_admin"}) + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Accepte une demande d'affiliation", description = "Cette méthode accepte une demande " + + "d'affiliation à partir des données soumises dans le formulaire.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "400", description = "Données invalides"), + @APIResponse(responseCode = "403", description = "Accès refusé") + }) + public Uni acceptAffRequest(AffiliationRequestSaveForm form) { + return service.accept(form); + } + + @GET + @Path("/{id}/logo") + @RolesAllowed({"federation_admin"}) + @Operation(summary = "Renvoie le logo d'une demande d'affiliation", description = "Cette méthode renvoie le logo" + + " d'une demande d'affiliation pour l'identifiant spécifié.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Logo non trouvé") + }) + public Uni getLogo( + @Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) throws URISyntaxException { + return Utils.getMediaFile(id, media, "aff_request/logo", Uni.createFrom().nullItem()); + } + + @GET + @Path("/{id}/status") + @RolesAllowed({"federation_admin"}) + @Operation(summary = "Renvoie le statut d'une demande d'affiliation", description = "Cette méthode renvoie le statut" + + " d'une demande d'affiliation pour l'identifiant spécifié.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Statut non trouvé") + }) + public Uni getStatus( + @Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) throws URISyntaxException { + return Utils.getMediaFile(id, media, "aff_request/status", "affiliation_request_" + id + ".pdf", + Uni.createFrom().nullItem()); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java index d739ca7..7fec841 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AssoEndpoints.java @@ -6,8 +6,14 @@ import fr.titionfire.ffsaf.rest.exception.DNotFoundException; import io.smallrye.mutiny.Uni; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.eclipse.microprofile.rest.client.inject.RestClient; +@Tag(name = "Association", description = "Récupération des informations d'une association depuis la base de données Française") @Path("api/asso") public class AssoEndpoints { @@ -17,7 +23,14 @@ public class AssoEndpoints { @GET @Path("siren/{siren}") @Produces(MediaType.APPLICATION_JSON) - public Uni getInfoSiren(@PathParam("siren") String siren) { + @Operation(summary = "Renvoie les informations d'une association à partir de son numéro SIREN", + description = "Cette méthode renvoie les informations d'une association à partir de son numéro SIREN.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "404", description = "Numéro SIREN introuvable") + }) + public Uni getInfoSiren( + @Parameter(description = "Le numéro SIREN de l'association à récupérer") @PathParam("siren") String siren) { return sirenService.get_unite(siren).onFailure().transform(throwable -> { if (throwable instanceof WebApplicationException exception) { if (exception.getResponse().getStatus() == 400) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java index 3371743..ee26e80 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java @@ -11,6 +11,9 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import java.net.URI; import java.net.URISyntaxException; @@ -29,6 +32,12 @@ public class AuthEndpoints { @GET @Produces(MediaType.TEXT_PLAIN) + @Operation(summary = "Vérifie si l'utilisateur est authentifié", description = "Cette méthode renvoie true si " + + "l'utilisateur est authentifié et false sinon.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite") + }) + public Boolean auth() { return !securityIdentity.isAnonymous(); } @@ -37,6 +46,12 @@ public class AuthEndpoints { @Path("/userinfo") @Authenticated @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les informations de l'utilisateur authentifié", description = "Cette méthode renvoie les" + + " informations de l'utilisateur authentifié sous forme d'objet JSON.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Réussite"), + @APIResponse(responseCode = "401", description = "Utilisateur non authentifié") + }) public UserInfo userinfo() { return UserInfo.makeUserInfo(accessToken, securityIdentity); } @@ -45,6 +60,11 @@ public class AuthEndpoints { @Path("/login") @Authenticated @Produces(MediaType.TEXT_PLAIN) + @Operation(summary = "Redirige l'utilisateur vers la page de connexion", description = "Cette méthode redirige " + + "l'utilisateur vers la page de connexion.") + @APIResponses(value = { + @APIResponse(responseCode = "307", description = "Redirection temporaire") + }) public Response login() throws URISyntaxException { return Response.temporaryRedirect(new URI(redirect)).build(); } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java index ef778ed..cdfa9f8 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java @@ -27,12 +27,18 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.jwt.JsonWebToken; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; import java.util.function.Consumer; +@Tag(name = "Club", description = "Gestion des clubs") @Path("api/club") public class ClubEndpoints { @@ -63,6 +69,12 @@ public class ClubEndpoints { @Path("/no_detail") @Authenticated @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie la liste de tous les clubs sans détails", description = "Renvoie la liste de tous les " + + "clubs sans les détails des membres et des affiliations") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste de tous les clubs sans détails"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni> getAll() { return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList()); } @@ -71,6 +83,8 @@ public class ClubEndpoints { @Path("/contact_type") @Authenticated @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les types de contacts pour les clubs", description = "Renvoie la liste des types de " + + "contacts possibles pour les clubs") public Uni> getConcatType() { return Uni.createFrom().item(Contact.toSite()); } @@ -79,10 +93,17 @@ public class ClubEndpoints { @Path("/find") @RolesAllowed({"federation_admin"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getFindAdmin(@QueryParam("limit") Integer limit, - @QueryParam("page") Integer page, - @QueryParam("search") String search, - @QueryParam("country") String country) { + @Operation(summary = "Recherche des clubs en fonction de critères de recherche", description = "Recherche des clubs " + + "en fonction de critères de recherche tels que le nom, le pays, etc.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des clubs correspondant aux critères de recherche"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni> getFindAdmin( + @Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit, + @Parameter(description = "Page à consulter") @QueryParam("page") Integer page, + @Parameter(description = "Text à rechercher") @QueryParam("search") String search, + @Parameter(description = "Pays à filter") @QueryParam("country") String country) { if (limit == null) limit = 50; if (page == null || page < 1) @@ -95,7 +116,16 @@ public class ClubEndpoints { @Path("{id}") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) - public Uni getById(@PathParam("id") long id) { + @Operation(summary = "Renvoie les détails d'un club en fonction de son identifiant", description = "Renvoie les " + + "détails d'un club en fonction de son identifiant, y compris les informations sur les membres et les affiliations") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les détails du club"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le club n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni getById( + @Parameter(description = "Identifiant de club") @PathParam("id") long id) { return clubService.getById(id).onItem().invoke(checkPerm).map(SimpleClub::fromModel).invoke(m -> { m.setContactMap(Contact.toSite()); }); @@ -106,7 +136,17 @@ public class ClubEndpoints { @RolesAllowed({"federation_admin"}) @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni setAdminClub(@PathParam("id") long id, FullClubForm input) { + @Operation(summary = "Met à jour les informations d'un club en fonction de son identifiant", description = "Met à " + + "jour les informations d'un club en fonction de son identifiant, y compris les informations sur les membres" + + " et les affiliations") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le club a été mis à jour avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le club n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni setAdminClub( + @Parameter(description = "Identifiant de club") @PathParam("id") long id, FullClubForm input) { return clubService.update(id, input) .invoke(Unchecked.consumer(out -> { if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out); @@ -135,6 +175,13 @@ public class ClubEndpoints { @RolesAllowed({"federation_admin"}) @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Ajoute un nouveau club", description = "Ajoute un nouveau club avec les informations fournies" + + " dans le formulaire") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le club a été ajouté avec succès"), + @APIResponse(responseCode = "400", description = "Les données envoyées sont invalides"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni addAdminClub(FullClubForm input) { return clubService.add(input) .invoke(Unchecked.consumer(id -> { @@ -158,7 +205,16 @@ public class ClubEndpoints { @Path("{id}") @RolesAllowed({"federation_admin"}) @Produces(MediaType.TEXT_PLAIN) - public Uni deleteAdminClub(@PathParam("id") long id) { + @Operation(summary = "Supprime un club en fonction de son identifiant", description = "Supprime un club en fonction" + + " de son identifiant, ainsi que toutes les informations associées") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Le club a été supprimé avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le club n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni deleteAdminClub( + @Parameter(description = "Identifiant de club") @PathParam("id") long id) { return clubService.delete(id); } @@ -166,6 +222,14 @@ public class ClubEndpoints { @Path("/me") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les informations du club de l'utilisateur connecté", description = "Renvoie les " + + "informations du club de l'utilisateur connecté") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les informations du club de l'utilisateur connecté"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni getOfUser() { return clubService.getOfUser(idToken).map(SimpleClub::fromModel) .invoke(m -> m.setContactMap(Contact.toSite())); @@ -176,6 +240,14 @@ public class ClubEndpoints { @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Met à jour les informations du club de l'utilisateur connecté", description = "Met à jour les" + + " informations du club de l'utilisateur connecté") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les informations du club de l'utilisateur connecté ont été mises à jour avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni setClubOfUser(PartClubForm form) { return clubService.updateOfUser(idToken, form); } @@ -184,6 +256,7 @@ public class ClubEndpoints { @Path("/renew/{id}") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) + @Operation(hidden = true) public Uni getOfUser(@PathParam("id") long id, @QueryParam("m1") long m1_id, @QueryParam("m2") long m2_id, @QueryParam("m3") long m3_id) { return Uni.createFrom().item(id).invoke(checkPerm2) @@ -194,14 +267,31 @@ public class ClubEndpoints { @Path("/desk/{id}") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) - public Uni> getClubDesk(@PathParam("id") long id) { + @Operation(summary = "Renvoie la liste des membres du bureau du club", description = "Renvoie la liste des membres " + + "du bureau du club spécifié") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des membres du bureau du club"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le club n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni> getClubDesk( + @Parameter(description = "Identifiant de club") @PathParam("id") long id) { return clubService.getClubDesk(checkPerm, id); } @GET @Path("{clubId}/logo") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) - public Uni getLogo(@PathParam("clubId") String clubId) { + @Operation(summary = "Renvoie le logo du club", description = "Renvoie le logo du club spécifié") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le logo du club"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le club n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni getLogo( + @Parameter(description = "Identifiant long (clubId) de club") @PathParam("clubId") String clubId) { return clubService.getByClubId(clubId).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> { try { return Utils.getMediaFile((clubModel != null) ? clubModel.getId() : -1, media, "ppClub", @@ -215,7 +305,15 @@ public class ClubEndpoints { @GET @Path("{id}/status") @RolesAllowed({"federation_admin", "club_president", "club_secretaire"}) - public Uni getStatus(@PathParam("id") long id) { + @Operation(summary = "Renvoie le statut du club", description = "Renvoie le statut du club spécifié") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le statut du club"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le club n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni getStatus( + @Parameter(description = "Identifiant de club") @PathParam("id") long id) { return clubService.getById(id).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> { try { return Utils.getMediaFile(clubModel.getId(), media, "clubStatus", diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 92aa416..2ceeada 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,7 +17,6 @@ quarkus.quartz.start-mode=forced %dev.quarkus.log.min-level=ALL %dev.quarkus.log.category."fr.titionfire.ffsaf".level=ALL - quarkus.oidc.auth-server-url=https://auth.safca.fr/realms/safca quarkus.oidc.client-id=backend quarkus.oidc.credentials.secret=secret diff --git a/src/main/webapp/src/pages/admin/club/AffiliationCard.jsx b/src/main/webapp/src/pages/admin/club/AffiliationCard.jsx index 96290e7..0d7e7f7 100644 --- a/src/main/webapp/src/pages/admin/club/AffiliationCard.jsx +++ b/src/main/webapp/src/pages/admin/club/AffiliationCard.jsx @@ -67,7 +67,7 @@ function sendAffiliation(event, dispatch) { const formData = new FormData(event.target); toast.promise( - apiAxios.put(`/affiliation/${formData.get('club')}?saison=${formData.get('saison')}`), + apiAxios.post(`/affiliation/${formData.get('club')}?saison=${formData.get('saison')}`), { pending: "Enregistrement de l'affiliation en cours", success: "Affiliation enregistrée avec succès 🎉", diff --git a/src/main/webapp/vite.config.js b/src/main/webapp/vite.config.js index a9c4187..c532ed0 100644 --- a/src/main/webapp/vite.config.js +++ b/src/main/webapp/vite.config.js @@ -17,6 +17,10 @@ export default ({mode}) => { target: process.env.VITE_API_URL, changeOrigin: true, }, + "/q": { + target: process.env.VITE_API_URL, + changeOrigin: true, + }, }, }, });