package fr.titionfire.ffsaf.rest; import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.domain.service.ClubService; import fr.titionfire.ffsaf.domain.service.PDFService; import fr.titionfire.ffsaf.net2.data.SimpleClubModel; import fr.titionfire.ffsaf.rest.data.*; import fr.titionfire.ffsaf.rest.exception.DForbiddenException; import fr.titionfire.ffsaf.rest.exception.DInternalError; import fr.titionfire.ffsaf.rest.from.FullClubForm; import fr.titionfire.ffsaf.rest.from.PartClubForm; import fr.titionfire.ffsaf.utils.Contact; import fr.titionfire.ffsaf.utils.PageResult; import fr.titionfire.ffsaf.utils.SecurityCtx; import fr.titionfire.ffsaf.utils.Utils; import io.quarkus.security.Authenticated; 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.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 { @Inject ClubService clubService; @Inject PDFService pdfService; @Inject SecurityCtx securityCtx; @ConfigProperty(name = "upload_dir") String media; Consumer checkPerm = Unchecked.consumer(clubModel -> { if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(clubModel.getId())) throw new DForbiddenException(); }); Consumer checkPerm2 = Unchecked.consumer(id -> { if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(id)) throw new DForbiddenException(); }); @GET @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()); } @GET @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()); } @GET @Path("/find") @RolesAllowed({"federation_admin"}) @Produces(MediaType.APPLICATION_JSON) @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) page = 1; return clubService.search(limit, page - 1, search, country); } @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 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()); }); } @PUT @Path("{id}") @RolesAllowed({"federation_admin"}) @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.MULTIPART_FORM_DATA) @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); })).chain(() -> { if (input.getLogo().length > 0) return Uni.createFrom().future(Utils.replacePhoto(id, input.getLogo(), media, "ppClub" )).invoke(Unchecked.consumer(out -> { if (!out.equals("OK")) throw new DInternalError("Impossible de reconnaitre le fichier: " + out); })); // TODO log else return Uni.createFrom().nullItem(); }).chain(() -> { if (input.getStatus().length > 0) return Uni.createFrom().future(Utils.replacePhoto(id, input.getStatus(), media, "clubStatus" )).invoke(Unchecked.consumer(out -> { if (!out.equals("OK")) throw new DInternalError("Impossible de reconnaitre le fichier: " + out); })); // TODO log else return Uni.createFrom().nullItem(); }); } @PUT @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 -> { if (id == null) throw new InternalError("Fail to create club data"); })).call(id -> { if (input.getLogo().length > 0) return Uni.createFrom().future(Utils.replacePhoto(id, input.getLogo(), media, "ppClub" )); // TODO log else return Uni.createFrom().nullItem(); }).call(id -> { if (input.getStatus().length > 0) return Uni.createFrom().future(Utils.replacePhoto(id, input.getStatus(), media, "clubStatus" )); // TODO log else return Uni.createFrom().nullItem(); }); } @DELETE @Path("{id}") @RolesAllowed({"federation_admin"}) @Produces(MediaType.TEXT_PLAIN) @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); } @GET @Path("/{id}/affiliation") @RolesAllowed({"federation_admin"}) @Operation(summary = "Renvoie l'attestation d'affiliation du club en fonction de son identifiant", description = "Renvoie l'attestation d'affiliation du club en fonction de son identifiant") @APIResponses(value = { @APIResponse(responseCode = "200", description = "L'attestation d'affiliation"), @APIResponse(responseCode = "403", description = "Accès refusé"), @APIResponse(responseCode = "404", description = "Le club n'existe pas ou n'a pas d'affiliation active"), @APIResponse(responseCode = "500", description = "Erreur interne du serveur") }) public Uni getAffiliation(@Parameter(description = "Identifiant de club") @PathParam("id") long id) { return pdfService.getAffiliationPdf(id); } @GET @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(securityCtx).map(SimpleClub::fromModel) .invoke(m -> m.setContactMap(Contact.toSite())); } @PUT @Path("/me") @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(securityCtx, form); } @GET @Path("/me/affiliation") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Operation(summary = "Renvoie l'attestation d'affiliation du club de l'utilisateur connecté", description = "Renvoie l'attestation d'affiliation du club de l'utilisateur connecté") @APIResponses(value = { @APIResponse(responseCode = "200", description = "L'attestation d'affiliation"), @APIResponse(responseCode = "403", description = "Accès refusé"), @APIResponse(responseCode = "404", description = "Le club n'a pas d'affiliation active"), @APIResponse(responseCode = "500", description = "Erreur interne du serveur") }) public Uni getMeAffiliation() { return pdfService.getAffiliationPdf(securityCtx.getSubject()); } @GET @Path("/renew/{id}") @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) @Operation(hidden = true) 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))); } @GET @Path("/desk/{id}") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @Produces(MediaType.APPLICATION_JSON) @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") @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).chain(Unchecked.function(clubModel -> { try { return Utils.getMediaFile((clubModel != null) ? clubModel.getId() : -1, media, "ppClub", Uni.createFrom().nullItem()); } catch (URISyntaxException e) { throw new InternalError(); } })); } @GET @Path("{id}/status") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @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", "statue-" + clubModel.getName(), Uni.createFrom().nullItem()); } catch (URISyntaxException e) { throw new InternalError(); } })); } @GET @Path("get_map_data") public Uni> getMapData() { return clubService.getMapData(); } }