feat: add club member's gestion page
This commit is contained in:
@@ -2,8 +2,8 @@ package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.ClubService;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
@@ -20,7 +20,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/no_detail")
|
||||
@RolesAllowed("federation_admin")
|
||||
@Authenticated
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<List<SimpleClubModel>> getAll() {
|
||||
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
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.from.ClubMemberForm;
|
||||
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
|
||||
import fr.titionfire.ffsaf.utils.GroupeUtils;
|
||||
import fr.titionfire.ffsaf.utils.Pair;
|
||||
import io.quarkus.oidc.IdToken;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
@@ -14,6 +19,7 @@ import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jodd.net.MimeTypes;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
@@ -23,7 +29,9 @@ import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Authenticated
|
||||
@Path("api/member")
|
||||
public class CombEndpoints {
|
||||
|
||||
@@ -33,6 +41,15 @@ public class CombEndpoints {
|
||||
@ConfigProperty(name = "upload_dir")
|
||||
String media;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
|
||||
if (!idToken.getGroups().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
|
||||
throw new ForbiddenException();
|
||||
});
|
||||
|
||||
@GET
|
||||
@Path("/all")
|
||||
@RolesAllowed("federation_admin")
|
||||
@@ -42,22 +59,62 @@ public class CombEndpoints {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed("federation_admin")
|
||||
@Path("/club")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<SimpleMembre> getById(@PathParam("id") long id) {
|
||||
return membreService.getById(id).map(SimpleMembre::fromModel);
|
||||
public Uni<List<SimpleMembre>> getClub() {
|
||||
return membreService.getInClub(idToken.getSubject()).map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<SimpleMembre> getById(@PathParam("id") long id) {
|
||||
return membreService.getById(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}")
|
||||
@RolesAllowed("federation_admin")
|
||||
@RolesAllowed({"federation_admin"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
public Uni<String> setAdminMembre(@PathParam("id") long id, FullMemberForm input) {
|
||||
Future<String> future = CompletableFuture.supplyAsync(() -> {
|
||||
try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input.getPhoto_data()))) {
|
||||
return membreService.update(id, input)
|
||||
.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(replacePhoto(id, input.getPhoto_data())).invoke(Unchecked.consumer(out -> {
|
||||
if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out);
|
||||
}));
|
||||
else
|
||||
return Uni.createFrom().nullItem();
|
||||
});
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
public Uni<String> setMembre(@PathParam("id") long id, ClubMemberForm input) {
|
||||
return membreService.update(id, input, idToken)
|
||||
.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(replacePhoto(id, input.getPhoto_data())).invoke(Unchecked.consumer(out -> {
|
||||
if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out);
|
||||
}));
|
||||
else
|
||||
return Uni.createFrom().nullItem();
|
||||
});
|
||||
}
|
||||
|
||||
private Future<String> 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)
|
||||
@@ -73,33 +130,17 @@ public class CombEndpoints {
|
||||
}
|
||||
|
||||
String extension = "." + detectedExtensions[0];
|
||||
Files.write(new File(media, "ppMembre/" + input.getId() + extension).toPath(), input.getPhoto_data());
|
||||
Files.write(new File(media, "ppMembre/" + id + extension).toPath(), input);
|
||||
return "OK";
|
||||
} catch (IOException e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
});
|
||||
|
||||
if (input.getPhoto_data().length > 0) {
|
||||
return membreService.update(id, input)
|
||||
.invoke(Unchecked.consumer(out -> {
|
||||
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
|
||||
}))
|
||||
.chain(() -> Uni.createFrom().future(future))
|
||||
.invoke(Unchecked.consumer(out -> {
|
||||
if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out);
|
||||
}));
|
||||
} else {
|
||||
return membreService.update(id, input)
|
||||
.invoke(Unchecked.consumer(out -> {
|
||||
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}/photo")
|
||||
@RolesAllowed("federation_admin")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
public Uni<Response> getPhoto(@PathParam("id") long id) throws URISyntaxException {
|
||||
Future<Pair<File, byte[]>> future = CompletableFuture.supplyAsync(() -> {
|
||||
FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id));
|
||||
@@ -117,7 +158,7 @@ public class CombEndpoints {
|
||||
|
||||
URI uri = new URI("https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-chat/ava2.webp");
|
||||
|
||||
return Uni.createFrom().future(future)
|
||||
return membreService.getById(id).onItem().invoke(checkPerm).chain(__ -> Uni.createFrom().future(future)
|
||||
.map(filePair -> {
|
||||
if (filePair == null)
|
||||
return Response.temporaryRedirect(uri).build();
|
||||
@@ -131,7 +172,7 @@ public class CombEndpoints {
|
||||
resp.header(HttpHeaders.CONTENT_DISPOSITION, "inline; ");
|
||||
|
||||
return resp.build();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.KeycloakService;
|
||||
import fr.titionfire.ffsaf.rest.from.MemberPermForm;
|
||||
import fr.titionfire.ffsaf.utils.GroupeUtils;
|
||||
import fr.titionfire.ffsaf.utils.Pair;
|
||||
import io.quarkus.oidc.IdToken;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.vertx.mutiny.core.Vertx;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.*;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -19,11 +22,26 @@ public class CompteEndpoints {
|
||||
@Inject
|
||||
KeycloakService service;
|
||||
|
||||
@Inject
|
||||
JsonWebToken accessToken;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@Inject
|
||||
Vertx vertx;
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed("federation_admin")
|
||||
public Uni<?> getCompte(@PathParam("id") String id) {
|
||||
return service.fetchCompte(id);
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
public Uni<KeycloakService.UserCompteState> getCompte(@PathParam("id") String id) {
|
||||
return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> {
|
||||
if (!idToken.getGroups().contains("federation_admin") && !pair.getKey().groups().stream().map(GroupRepresentation::getPath)
|
||||
.anyMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, accessToken)))
|
||||
throw new ForbiddenException();
|
||||
return pair;
|
||||
})).map(Pair::getValue);
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.domain.service.LicenceService;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
|
||||
import fr.titionfire.ffsaf.rest.from.LicenceForm;
|
||||
import fr.titionfire.ffsaf.utils.GroupeUtils;
|
||||
import io.quarkus.oidc.IdToken;
|
||||
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.jwt.JsonWebToken;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Path("api/licence")
|
||||
public class LicenceEndpoints {
|
||||
@@ -17,12 +23,21 @@ public class LicenceEndpoints {
|
||||
@Inject
|
||||
LicenceService licenceService;
|
||||
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
|
||||
if (!idToken.getGroups().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
|
||||
throw new ForbiddenException();
|
||||
});
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed("federation_admin")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<List<SimpleLicence>> getLicence(@PathParam("id") long id) {
|
||||
return licenceService.getLicence(id).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
|
||||
return licenceService.getLicence(id, checkPerm).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -41,4 +56,21 @@ public class LicenceEndpoints {
|
||||
public Uni<?> deleteLicence(@PathParam("id") long id) {
|
||||
return licenceService.deleteLicence(id);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
public Uni<SimpleLicence> askLicence(@PathParam("id") long id, LicenceForm form) {
|
||||
return licenceService.askLicence(id, form, checkPerm).map(SimpleLicence::fromModel);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Uni<?> deleteAskLicence(@PathParam("id") long id) {
|
||||
return licenceService.deleteAskLicence(id, checkPerm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@@ -19,7 +21,7 @@ public class UserInfo {
|
||||
String email;
|
||||
boolean emailVerified;
|
||||
long expiration;
|
||||
Set<String> groups;
|
||||
List<String> groups;
|
||||
Set<String> roles;
|
||||
|
||||
public static UserInfo makeUserInfo(JsonWebToken accessToken, SecurityIdentity securityIdentity) {
|
||||
@@ -31,7 +33,13 @@ public class UserInfo {
|
||||
builder.email(accessToken.getClaim("email"));
|
||||
builder.emailVerified(accessToken.getClaim("email_verified"));
|
||||
builder.expiration(accessToken.getExpirationTime());
|
||||
builder.groups(accessToken.getGroups());
|
||||
List<String> groups = new ArrayList<>();
|
||||
if (accessToken.getClaim("user_groups") instanceof Iterable<?>) {
|
||||
for (Object str : (Iterable<?>) accessToken.getClaim("user_groups")) {
|
||||
groups.add(str.toString().substring(1, str.toString().length() - 1));
|
||||
}
|
||||
}
|
||||
builder.groups(groups);
|
||||
builder.roles(securityIdentity.getRoles());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package fr.titionfire.ffsaf.rest.from;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import fr.titionfire.ffsaf.utils.RoleAsso;
|
||||
import jakarta.ws.rs.FormParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import lombok.Getter;
|
||||
import org.jboss.resteasy.reactive.PartType;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Getter
|
||||
public class ClubMemberForm {
|
||||
@FormParam("id")
|
||||
private String id = null;
|
||||
|
||||
@FormParam("lname")
|
||||
private String lname = null;
|
||||
|
||||
@FormParam("fname")
|
||||
private String fname = null;
|
||||
|
||||
@FormParam("categorie")
|
||||
private Categorie categorie = null;
|
||||
|
||||
@FormParam("genre")
|
||||
private Genre genre;
|
||||
|
||||
@FormParam("country")
|
||||
private String country;
|
||||
|
||||
@FormParam("birth_date")
|
||||
private Date birth_date;
|
||||
|
||||
@FormParam("email")
|
||||
private String email;
|
||||
|
||||
@FormParam("role")
|
||||
private RoleAsso role;
|
||||
|
||||
@FormParam("photo_data")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
private byte[] photo_data = new byte[0];
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClubMemberForm{" +
|
||||
"id='" + id + '\'' +
|
||||
", lname='" + lname + '\'' +
|
||||
", fname='" + fname + '\'' +
|
||||
", categorie=" + categorie +
|
||||
", genre=" + genre +
|
||||
", country='" + country + '\'' +
|
||||
", birth_date=" + birth_date +
|
||||
", email='" + email + '\'' +
|
||||
", role=" + role +
|
||||
", url_photo=" + photo_data.length +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user