diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java index ee26e80..d9d4127 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java @@ -60,11 +60,7 @@ 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") - }) + @Operation(hidden = true) 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 cdfa9f8..115175f 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java @@ -257,7 +257,7 @@ public class ClubEndpoints { @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, + public Uni getRenew(@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) .chain(__ -> clubService.getRenewData(id, List.of(m1_id, m2_id, m3_id))); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java deleted file mode 100644 index 36c3492..0000000 --- a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java +++ /dev/null @@ -1,216 +0,0 @@ -package fr.titionfire.ffsaf.rest; - -import fr.titionfire.ffsaf.data.model.MembreModel; -import fr.titionfire.ffsaf.domain.service.MembreService; -import fr.titionfire.ffsaf.rest.data.MeData; -import fr.titionfire.ffsaf.rest.data.SimpleMembre; -import fr.titionfire.ffsaf.rest.exception.DForbiddenException; -import fr.titionfire.ffsaf.rest.exception.DInternalError; -import fr.titionfire.ffsaf.rest.from.ClubMemberForm; -import fr.titionfire.ffsaf.rest.from.FullMemberForm; -import fr.titionfire.ffsaf.utils.GroupeUtils; -import fr.titionfire.ffsaf.utils.PageResult; -import fr.titionfire.ffsaf.utils.Utils; -import io.quarkus.oidc.IdToken; -import io.quarkus.security.Authenticated; -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 java.net.URISyntaxException; -import java.util.List; -import java.util.function.Consumer; - -@Authenticated -@Path("api/member") -public class CombEndpoints { - - @Inject - MembreService membreService; - - @ConfigProperty(name = "upload_dir") - String media; - - @Inject - @IdToken - JsonWebToken idToken; - - @Inject - SecurityIdentity securityIdentity; - - Consumer checkPerm = Unchecked.consumer(membreModel -> { - if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup( - membreModel.getClub().getId(), idToken)) - throw new DForbiddenException(); - }); - - @GET - @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) { - if (limit == null) - limit = 50; - if (page == null || page < 1) - page = 1; - return membreService.searchAdmin(limit, page - 1, search, club); - } - - @GET - @Path("/find/similar") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.APPLICATION_JSON) - public Uni> getSimilar(@QueryParam("fname") String fname, @QueryParam("lname") String lname) { - return membreService.getSimilar(fname, lname); - } - - @GET - @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, - @QueryParam("search") String search) { - if (limit == null) - limit = 50; - if (page == null || page < 1) - page = 1; - return membreService.search(limit, page - 1, search, idToken.getSubject()); - } - - @GET - @Path("{id}") - @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.APPLICATION_JSON) - public Uni getById(@PathParam("id") long id) { - return membreService.getById(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel); - } - - @GET - @Path("/find/licence") - @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.APPLICATION_JSON) - public Uni getByLicence(@QueryParam("id") long id) { - return membreService.getByLicence(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel); - } - - @PUT - @Path("{id}") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni setAdminMembre(@PathParam("id") long id, FullMemberForm input) { - return membreService.update(id, input) - .invoke(Unchecked.consumer(out -> { - if (!out.equals("OK")) - throw new DInternalError("Impossible de reconnaitre le fichier: " + out); - })).chain(() -> { - if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" - )).invoke(Unchecked.consumer(out -> { - if (!out.equals("OK")) - throw new DInternalError("Impossible de reconnaitre le fichier: " + out); - })); - else - return Uni.createFrom().nullItem(); - }); - } - - @POST - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni addAdminMembre(FullMemberForm input) { - return membreService.add(input) - .invoke(Unchecked.consumer(id -> { - if (id == null) throw new InternalError("Fail to creat member data"); - })).call(id -> { - if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" - )); - else - return Uni.createFrom().nullItem(); - }); - } - - @DELETE - @Path("{id}") - @RolesAllowed({"federation_admin"}) - @Produces(MediaType.TEXT_PLAIN) - public Uni deleteAdminMembre(@PathParam("id") long id) { - return membreService.delete(id); - } - - @PUT - @Path("club/{id}") - @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni setMembre(@PathParam("id") long id, ClubMemberForm input) { - return membreService.update(id, input, idToken, securityIdentity) - .invoke(Unchecked.consumer(out -> { - if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out); - })).chain(() -> { - if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" - )).invoke(Unchecked.consumer(out -> { - if (!out.equals("OK")) - throw new DInternalError("Impossible de reconnaitre le fichier: " + out); - })); - else - return Uni.createFrom().nullItem(); - }); - } - - @POST - @Path("club") - @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.TEXT_PLAIN) - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Uni addMembre(FullMemberForm input) { - return membreService.add(input, idToken.getSubject()) - .invoke(Unchecked.consumer(id -> { - if (id == null) throw new InternalError("Fail to creat member data"); - })).call(id -> { - if (input.getPhoto_data().length > 0) - return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" - )); - else - return Uni.createFrom().nullItem(); - }); - } - - @DELETE - @Path("club/{id}") - @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) - @Produces(MediaType.TEXT_PLAIN) - public Uni deleteMembre(@PathParam("id") long id) { - return membreService.delete(id, idToken); - } - - @GET - @Path("me") - @Authenticated - @Produces(MediaType.APPLICATION_JSON) - public Uni getMe() { - return membreService.getMembre(idToken.getSubject()); - } - - @GET - @Path("{id}/photo") - @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) - public Uni getPhoto(@PathParam("id") long id) throws URISyntaxException { - return Utils.getMediaFile(id, media, "ppMembre", membreService.getById(id).onItem().invoke(checkPerm)); - } - -} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java index f6a5231..7a8ec62 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompteEndpoints.java @@ -10,13 +10,21 @@ import io.smallrye.mutiny.Uni; import io.vertx.mutiny.core.Vertx; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; -import jakarta.ws.rs.*; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; 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 org.eclipse.microprofile.openapi.annotations.tags.Tag; import org.keycloak.representations.idm.GroupRepresentation; import java.util.ArrayList; import java.util.List; +@Tag(name = "Compte", description = "Gestion des comptes utilisateurs") @Path("api/compte") public class CompteEndpoints { @@ -35,6 +43,14 @@ public class CompteEndpoints { @GET @Path("{id}") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) + @Operation(summary = "Renvoie les informations d'un compte utilisateur", description = "Renvoie les informations d'un" + + " compte utilisateur en fonction de son identifiant long (UUID)") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les informations du compte utilisateur"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le compte utilisateur n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni getCompte(@PathParam("id") String id) { return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> { if (!securityIdentity.getRoles().contains("federation_admin") && pair.getKey().groups().stream().map(GroupRepresentation::getPath) @@ -47,6 +63,14 @@ public class CompteEndpoints { @PUT @Path("{id}/init") @RolesAllowed("federation_admin") + @Operation(summary = "Initialise un compte utilisateur", description = "Initialise un compte utilisateur en fonction" + + " de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Le compte utilisateur a été initialisé avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni initCompte(@PathParam("id") long id) { return service.initCompte(id); } @@ -54,6 +78,7 @@ public class CompteEndpoints { @PUT @Path("{id}/setUUID/{nid}") @RolesAllowed("federation_admin") + @Operation(hidden = true) public Uni initCompte(@PathParam("id") long id, @PathParam("nid") String nid) { return service.setId(id, nid); } @@ -61,13 +86,29 @@ public class CompteEndpoints { @GET @Path("{id}/roles") @RolesAllowed("federation_admin") - public Uni getRole(@PathParam("id") String id) { + @Operation(summary = "Renvoie les rôles d'un compte utilisateur", description = "Renvoie les rôles d'un compte" + + " utilisateur en fonction de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les rôles du compte utilisateur"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le compte utilisateur n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni> getRole(@PathParam("id") String id) { return service.fetchRole(id); } @PUT @Path("{id}/roles") @RolesAllowed("federation_admin") + @Operation(summary = "Met à jour les rôles d'un compte utilisateur", description = "Met à jour les rôles d'un compte" + + " utilisateur en fonction de son identifiant et des rôles fournis dans le formulaire") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Les rôles du compte utilisateur ont été mis à jour avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le compte utilisateur n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni updateRole(@PathParam("id") String id, MemberPermForm form) { List toAdd = new ArrayList<>(); List toRemove = new ArrayList<>(); diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CountriesEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CountriesEndpoints.java index ff87b52..fc49914 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CountriesEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CountriesEndpoints.java @@ -6,6 +6,7 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.openapi.annotations.Operation; import java.util.HashMap; import java.util.Locale; @@ -16,6 +17,7 @@ public class CountriesEndpoints { @GET @Path("/{lang}/{code}") @Produces(MediaType.APPLICATION_JSON) + @Operation(hidden = true) public Uni> getCountries(@PathParam("lang") String lang, @PathParam("code") String code) { Locale locale = new Locale(lang, code); return Uni.createFrom().item(new HashMap()) diff --git a/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java index 91dd5fd..55cf203 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/LicenceEndpoints.java @@ -15,6 +15,9 @@ import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; 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.util.List; import java.util.function.Consumer; @@ -41,6 +44,14 @@ public class LicenceEndpoints { @Path("{id}") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les licences d'un membre", description = "Renvoie les licences d'un membre en fonction " + + "de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des licences du membre"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni> getLicence(@PathParam("id") long id) { return licenceService.getLicence(id, checkPerm).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList()); } @@ -49,6 +60,12 @@ public class LicenceEndpoints { @Path("current/admin") @RolesAllowed({"federation_admin"}) @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les licences de la saison en cours (pour les administrateurs)", description = "Renvoie" + + " les licences de la saison en cours (pour les administrateurs)") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des licences de la saison en cours"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni> getCurrentSaisonLicenceAdmin() { return licenceService.getCurrentSaisonLicence(null).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList()); } @@ -57,6 +74,12 @@ public class LicenceEndpoints { @Path("current/club") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les licences de la saison en cours (pour les clubs)", description = "Renvoie les " + + "licences de la saison en cours (pour les clubs)") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des licences de la saison en cours"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni> getCurrentSaisonLicenceClub() { return licenceService.getCurrentSaisonLicence(idToken).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList()); } @@ -66,6 +89,14 @@ public class LicenceEndpoints { @RolesAllowed("federation_admin") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Créer une licence", description = "Créer unr licence en fonction de son identifiant et des " + + "informations fournies dans le formulaire (pour les administrateurs)") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La licence a été mise à jour avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "La licence n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni setLicence(@PathParam("id") long id, LicenceForm form) { return licenceService.setLicence(id, form).map(SimpleLicence::fromModel); } @@ -74,6 +105,14 @@ public class LicenceEndpoints { @Path("{id}") @RolesAllowed("federation_admin") @Produces(MediaType.TEXT_PLAIN) + @Operation(summary = "Supprime une licence", description = "Supprime une licence en fonction de son identifiant " + + "(pour les administrateurs)") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "La licence a été supprimée avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "La licence n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni deleteLicence(@PathParam("id") long id) { return licenceService.deleteLicence(id); } @@ -83,6 +122,14 @@ public class LicenceEndpoints { @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Demande une nouvelle licence", description = "Demande une nouvelle licence en fonction de" + + " l'identifiant du membre et des informations fournies dans le formulaire (pour les clubs)") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La demande de licence a été envoyée avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni askLicence(@PathParam("id") long id, LicenceForm form) { return licenceService.askLicence(id, form, checkPerm).map(SimpleLicence::fromModel); } @@ -91,6 +138,14 @@ public class LicenceEndpoints { @Path("club/{id}") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.TEXT_PLAIN) + @Operation(summary = "Supprime une demande de licence", description = "Supprime une demande de licence en fonction " + + "de son identifiant (pour les clubs)") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "La demande de licence a été supprimée avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "La demande de licence n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) public Uni deleteAskLicence(@PathParam("id") long id) { return licenceService.deleteAskLicence(id, checkPerm); } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java new file mode 100644 index 0000000..485d4d1 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreAdminEndpoints.java @@ -0,0 +1,152 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.data.model.MembreModel; +import fr.titionfire.ffsaf.domain.service.MembreService; +import fr.titionfire.ffsaf.rest.data.SimpleMembre; +import fr.titionfire.ffsaf.rest.exception.DForbiddenException; +import fr.titionfire.ffsaf.rest.exception.DInternalError; +import fr.titionfire.ffsaf.rest.from.FullMemberForm; +import fr.titionfire.ffsaf.utils.GroupeUtils; +import fr.titionfire.ffsaf.utils.PageResult; +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 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.util.List; +import java.util.function.Consumer; + +@Tag(name = "Membre admin", description = "Gestion des membres (pour les administrateurs)") +@Path("api/member") +@RolesAllowed({"federation_admin"}) +public class MembreAdminEndpoints { + + @Inject + MembreService membreService; + + @ConfigProperty(name = "upload_dir") + String media; + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + SecurityIdentity securityIdentity; + + Consumer checkPerm = Unchecked.consumer(membreModel -> { + if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup( + membreModel.getClub().getId(), idToken)) + throw new DForbiddenException(); + }); + + @GET + @Path("/find/admin") + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Recherche des membres par critères ", description = "Recherche des membres en fonction de " + + "critères tels que le nom, le prénom, le club, etc. ") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des membres 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 = "Club à filter") @QueryParam("club") String club) { + if (limit == null) + limit = 50; + if (page == null || page < 1) + page = 1; + return membreService.searchAdmin(limit, page - 1, search, club); + } + + @GET + @Path("/find/similar") + @Produces(MediaType.APPLICATION_JSON) + @Operation(hidden = true) + public Uni> getSimilar(@QueryParam("fname") String fname, @QueryParam("lname") String lname) { + return membreService.getSimilar(fname, lname); + } + + @PUT + @Path("{id}") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Met à jour les informations d'un membre en fonction de son identifiant", description = "Met à " + + "jour les informations d'un membre en fonction de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le membre a été mis à jour avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni setAdminMembre( + @Parameter(description = "Identifiant de membre") @PathParam("id") long id, FullMemberForm input) { + return membreService.update(id, input) + .invoke(Unchecked.consumer(out -> { + if (!out.equals("OK")) + throw new DInternalError("Impossible de reconnaitre le fichier: " + out); + })).chain(() -> { + if (input.getPhoto_data().length > 0) + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )).invoke(Unchecked.consumer(out -> { + if (!out.equals("OK")) + throw new DInternalError("Impossible de reconnaitre le fichier: " + out); + })); + else + return Uni.createFrom().nullItem(); + }); + } + + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Ajoute un nouveau membre", description = "Ajoute un nouveau membre avec les informations " + + "fournies dans le formulaire") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le membre 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 addAdminMembre(FullMemberForm input) { + return membreService.add(input) + .invoke(Unchecked.consumer(id -> { + if (id == null) throw new InternalError("Fail to creat member data"); + })).call(id -> { + if (input.getPhoto_data().length > 0) + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )); + else + return Uni.createFrom().nullItem(); + }); + } + + @DELETE + @Path("{id}") + @Produces(MediaType.TEXT_PLAIN) + @Operation(summary = "Supprime un membre en fonction de son identifiant", description = "Supprime un membre en " + + "fonction de son identifiant, ainsi que toutes les informations associées") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Le membre a été supprimé avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni deleteAdminMembre( + @Parameter(description = "Identifiant de membre") @PathParam("id") long id) { + return membreService.delete(id); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java new file mode 100644 index 0000000..3ae36e4 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreClubEndpoints.java @@ -0,0 +1,132 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.domain.service.MembreService; +import fr.titionfire.ffsaf.rest.data.SimpleMembre; +import fr.titionfire.ffsaf.rest.exception.DInternalError; +import fr.titionfire.ffsaf.rest.from.ClubMemberForm; +import fr.titionfire.ffsaf.rest.from.FullMemberForm; +import fr.titionfire.ffsaf.utils.PageResult; +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 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; + +@Tag(name = "Membre club", description = "Gestion des membres (pour les clubs)") +@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) +@Path("api/member") +public class MembreClubEndpoints { + + @Inject + MembreService membreService; + + @ConfigProperty(name = "upload_dir") + String media; + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + SecurityIdentity securityIdentity; + + @GET + @Path("/find/club") + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Recherche des membres par critères", description = "Recherche des membres en " + + "fonction de critères tels que le nom, le prénom, etc.") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La liste des membres correspondant aux critères de recherche"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni> getFindClub( + @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) { + if (limit == null) + limit = 50; + if (page == null || page < 1) + page = 1; + return membreService.search(limit, page - 1, search, idToken.getSubject()); + } + + @PUT + @Path("club/{id}") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Met à jour les informations d'un membre en fonction de son identifiant", + description = "Met à jour les informations d'un membre en fonction de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le membre a été mis à jour avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni setMembre( + @Parameter(description = "Identifiant de membre") @PathParam("id") long id, ClubMemberForm input) { + return membreService.update(id, input, idToken, securityIdentity) + .invoke(Unchecked.consumer(out -> { + if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out); + })).chain(() -> { + if (input.getPhoto_data().length > 0) + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )).invoke(Unchecked.consumer(out -> { + if (!out.equals("OK")) + throw new DInternalError("Impossible de reconnaitre le fichier: " + out); + })); + else + return Uni.createFrom().nullItem(); + }); + } + + @POST + @Path("club") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Operation(summary = "Ajoute un nouveau membre", description = "Ajoute un nouveau membre avec les informations " + + "fournies dans le formulaire") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Le membre 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 addMembre(FullMemberForm input) { + return membreService.add(input, idToken.getSubject()) + .invoke(Unchecked.consumer(id -> { + if (id == null) throw new InternalError("Fail to creat member data"); + })).call(id -> { + if (input.getPhoto_data().length > 0) + return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre" + )); + else + return Uni.createFrom().nullItem(); + }); + } + + @DELETE + @Path("club/{id}") + @Produces(MediaType.TEXT_PLAIN) + @Operation(summary = "Supprime un membre en fonction de son identifiant", description = "Supprime " + + "un membre en fonction de son identifiant, ainsi que toutes les informations associées") + @APIResponses(value = { + @APIResponse(responseCode = "204", description = "Le membre a été supprimé avec succès"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni deleteMembre( + @Parameter(description = "Identifiant de membre") @PathParam("id") long id) { + return membreService.delete(id, idToken); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java new file mode 100644 index 0000000..7d73fc3 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java @@ -0,0 +1,115 @@ +package fr.titionfire.ffsaf.rest; + +import fr.titionfire.ffsaf.data.model.MembreModel; +import fr.titionfire.ffsaf.domain.service.MembreService; +import fr.titionfire.ffsaf.rest.data.MeData; +import fr.titionfire.ffsaf.rest.data.SimpleMembre; +import fr.titionfire.ffsaf.rest.exception.DForbiddenException; +import fr.titionfire.ffsaf.utils.GroupeUtils; +import fr.titionfire.ffsaf.utils.Utils; +import io.quarkus.oidc.IdToken; +import io.quarkus.security.Authenticated; +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 org.eclipse.microprofile.openapi.annotations.tags.Tag; + +import java.net.URISyntaxException; +import java.util.function.Consumer; + +@Tag(name = "Membre", description = "Gestion des membres") +@Authenticated +@Path("api/member") +public class MembreEndpoints { + + @Inject + MembreService membreService; + + @ConfigProperty(name = "upload_dir") + String media; + + @Inject + @IdToken + JsonWebToken idToken; + + @Inject + SecurityIdentity securityIdentity; + + Consumer checkPerm = Unchecked.consumer(membreModel -> { + if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup( + membreModel.getClub().getId(), idToken)) + throw new DForbiddenException(); + }); + + @GET + @Path("{id}") + @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les détails d'un membre en fonction de son identifiant", description = "Renvoie les " + + "détails d'un membre en fonction de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les détails du membre"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni getById( + @Parameter(description = "Identifiant de membre") @PathParam("id") long id) { + return membreService.getById(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel); + } + + @GET + @Path("/find/licence") + @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) + @Operation(summary = "Renvoie les détails d'un membre en fonction de son numéro de licence", description = "Renvoie " + + "les détails d'un membre en fonction de son numéro de licence") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les détails du membre"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + @Produces(MediaType.APPLICATION_JSON) + public Uni getByLicence(@QueryParam("id") long id) { + return membreService.getByLicence(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel); + } + + @GET + @Path("me") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Renvoie les informations du membre connecté", description = "Renvoie les informations du " + + "membre connecté, y compris le club et les licences") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "Les informations du membre connecté"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni getMe() { + return membreService.getMembre(idToken.getSubject()); + } + + @GET + @Path("{id}/photo") + @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) + @Operation(summary = "Renvoie la photo d'un membre", description = "Renvoie la photo d'un membre en fonction de son identifiant") + @APIResponses(value = { + @APIResponse(responseCode = "200", description = "La photo du membre"), + @APIResponse(responseCode = "403", description = "Accès refusé"), + @APIResponse(responseCode = "404", description = "Le membre n'existe pas ou n'a pas de photo"), + @APIResponse(responseCode = "500", description = "Erreur interne du serveur") + }) + public Uni getPhoto(@PathParam("id") long id) throws URISyntaxException { + return Utils.getMediaFile(id, media, "ppMembre", membreService.getById(id).onItem().invoke(checkPerm)); + } +}