feat: add membre
This commit is contained in:
parent
9615660101
commit
bbc8c470c5
@ -121,7 +121,7 @@ public class KeycloakService {
|
||||
return vertx.getOrCreateContext().executeBlocking(() -> {
|
||||
UserResource user = keycloak.realm(realm).users().get(id);
|
||||
UserRepresentation user2 = user.toRepresentation();
|
||||
return new Pair<>(user, new UserCompteState(user2.isEnabled(), user2.getUsername(), user2.isEmailVerified())) ;
|
||||
return new Pair<>(user, new UserCompteState(user2.isEnabled(), user2.getUsername(), user2.isEmailVerified()));
|
||||
});
|
||||
}
|
||||
|
||||
@ -195,6 +195,17 @@ public class KeycloakService {
|
||||
return membreService.setUserId(id, nid).map(__ -> "OK");
|
||||
}
|
||||
|
||||
public Uni<?> removeAccount(String userId) {
|
||||
return vertx.getOrCreateContext().executeBlocking(() -> {
|
||||
try (Response response = keycloak.realm(realm).users().delete(userId)) {
|
||||
System.out.println(response.getStatusInfo());
|
||||
if (!response.getStatusInfo().equals(Response.Status.NO_CONTENT))
|
||||
throw new KeycloakException("Fail to delete user %s (reason=%s)".formatted(userId, response.getStatusInfo().getReasonPhrase()));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<UserRepresentation> getUser(String username) {
|
||||
List<UserRepresentation> users = keycloak.realm(realm).users().searchByUsername(username, true);
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
|
||||
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
||||
import fr.titionfire.ffsaf.net2.request.SReqComb;
|
||||
@ -35,6 +36,9 @@ public class MembreService {
|
||||
CombRepository repository;
|
||||
@Inject
|
||||
ClubRepository clubRepository;
|
||||
@Inject
|
||||
LicenceRepository licenceRepository;
|
||||
|
||||
@Inject
|
||||
ServerCustom serverCustom;
|
||||
@Inject
|
||||
@ -183,6 +187,33 @@ public class MembreService {
|
||||
.map(MembreModel::getId);
|
||||
}
|
||||
|
||||
public Uni<String> delete(long id) {
|
||||
return repository.findById(id)
|
||||
.call(membreModel -> (membreModel.getUserId() != null) ?
|
||||
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
|
||||
.call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
|
||||
.invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
|
||||
.map(__ -> "Ok");
|
||||
}
|
||||
|
||||
public Uni<String> delete(long id, JsonWebToken idToken) {
|
||||
return repository.findById(id)
|
||||
.invoke(Unchecked.consumer(membreModel -> {
|
||||
if (!GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
|
||||
throw new ForbiddenException();
|
||||
}))
|
||||
.call(membreModel -> licenceRepository.find("membre = ?1", membreModel).count()
|
||||
.invoke(Unchecked.consumer(l -> {
|
||||
if (l > 0)
|
||||
throw new BadRequestException();
|
||||
})))
|
||||
.call(membreModel -> (membreModel.getUserId() != null) ?
|
||||
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
|
||||
.call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
|
||||
.invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
|
||||
.map(__ -> "Ok");
|
||||
}
|
||||
|
||||
public Uni<?> setUserId(Long id, String id1) {
|
||||
return repository.findById(id).chain(membreModel -> {
|
||||
membreModel.setUserId(id1);
|
||||
|
||||
@ -129,6 +129,14 @@ public class CombEndpoints {
|
||||
});
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Uni<String> deleteAdminMembre(@PathParam("id") long id) {
|
||||
return membreService.delete(id);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@ -167,6 +175,40 @@ public class CombEndpoints {
|
||||
});
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Uni<String> deleteMembre(@PathParam("id") long id) {
|
||||
return membreService.delete(id, idToken);
|
||||
}
|
||||
|
||||
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)
|
||||
throw new IOException("Fail to detect file extension for MIME type " + mimeType);
|
||||
|
||||
FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id));
|
||||
File[] files = new File(media, "ppMembre").listFiles(filter);
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
String extension = "." + detectedExtensions[0];
|
||||
Files.write(new File(media, "ppMembre/" + id + extension).toPath(), input);
|
||||
return "OK";
|
||||
} catch (IOException e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}/photo")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
|
||||
19
src/main/webapp/src/components/ConfirmDialog.jsx
Normal file
19
src/main/webapp/src/components/ConfirmDialog.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
export function ConfirmDialog({title, message, onConfirm = () => {}, onCancel = () => {}, id = "confirm-delete"}) {
|
||||
return <div className="modal fade" id={id} tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
{title && <h4 className="modal-title" id="myModalLabel">{title}</h4>}
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{message}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-default" data-dismiss="modal" data-bs-dismiss="modal" onClick={onCancel}>Annuler</button>
|
||||
<a className="btn btn-danger btn-ok" data-bs-dismiss="modal" onClick={onConfirm}>Confirmer</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -6,6 +6,9 @@ import {CompteInfo} from "./CompteInfo.jsx";
|
||||
import {PremForm} from "./PremForm.jsx";
|
||||
import {InformationForm} from "./InformationForm.jsx";
|
||||
import {LicenceCard} from "./LicenceCard.jsx";
|
||||
import {toast} from "react-toastify";
|
||||
import {apiAxios} from "../../../utils/Tools.js";
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -16,6 +19,19 @@ export function MemberPage() {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
|
||||
|
||||
const handleRm = () => {
|
||||
toast.promise(
|
||||
apiAxios.delete(`/member/${id}`),
|
||||
{
|
||||
pending: "Suppression du compte en cours...",
|
||||
success: "Compte supprimé avec succès 🎉",
|
||||
error: "Échec de la suppression du compte 😕"
|
||||
}
|
||||
).then(_ => {
|
||||
navigate("/admin/member")
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Page membre</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
||||
@ -39,6 +55,12 @@ export function MemberPage() {
|
||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||
<button className="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirm-delete">Supprimer le compte
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmDialog title="Supprimer le compte" message="Êtes-vous sûr de vouloir supprimer ce compte ?"
|
||||
onConfirm={handleRm}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,9 @@ import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||
import {CompteInfo} from "./CompteInfo.jsx";
|
||||
import {InformationForm} from "./InformationForm.jsx";
|
||||
import {LicenceCard} from "./LicenceCard.jsx";
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
import {apiAxios} from "../../../utils/Tools.js";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -15,6 +18,19 @@ export function MemberPage() {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
|
||||
|
||||
const handleRm = () => {
|
||||
toast.promise(
|
||||
apiAxios.delete(`/member/club/${id}`),
|
||||
{
|
||||
pending: "Suppression du compte en cours...",
|
||||
success: "Compte supprimé avec succès 🎉",
|
||||
error: "Échec de la suppression du compte 😕"
|
||||
}
|
||||
).then(_ => {
|
||||
navigate("/club/member")
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Page membre</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/club/member")}>
|
||||
@ -37,6 +53,12 @@ export function MemberPage() {
|
||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||
<button className="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirm-delete">Supprimer le compte
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmDialog title="Supprimer le compte" message="Êtes-vous sûr de vouloir supprimer ce compte ?"
|
||||
onConfirm={handleRm}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user