feat: add membre
This commit is contained in:
parent
9615660101
commit
bbc8c470c5
@ -195,6 +195,17 @@ public class KeycloakService {
|
|||||||
return membreService.setUserId(id, nid).map(__ -> "OK");
|
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) {
|
private Optional<UserRepresentation> getUser(String username) {
|
||||||
List<UserRepresentation> users = keycloak.realm(realm).users().searchByUsername(username, true);
|
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.model.MembreModel;
|
||||||
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
||||||
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
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.ServerCustom;
|
||||||
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
||||||
import fr.titionfire.ffsaf.net2.request.SReqComb;
|
import fr.titionfire.ffsaf.net2.request.SReqComb;
|
||||||
@ -35,6 +36,9 @@ public class MembreService {
|
|||||||
CombRepository repository;
|
CombRepository repository;
|
||||||
@Inject
|
@Inject
|
||||||
ClubRepository clubRepository;
|
ClubRepository clubRepository;
|
||||||
|
@Inject
|
||||||
|
LicenceRepository licenceRepository;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ServerCustom serverCustom;
|
ServerCustom serverCustom;
|
||||||
@Inject
|
@Inject
|
||||||
@ -183,6 +187,33 @@ public class MembreService {
|
|||||||
.map(MembreModel::getId);
|
.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) {
|
public Uni<?> setUserId(Long id, String id1) {
|
||||||
return repository.findById(id).chain(membreModel -> {
|
return repository.findById(id).chain(membreModel -> {
|
||||||
membreModel.setUserId(id1);
|
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
|
@PUT
|
||||||
@Path("club/{id}")
|
@Path("club/{id}")
|
||||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
@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
|
@GET
|
||||||
@Path("{id}/photo")
|
@Path("{id}/photo")
|
||||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
@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 {PremForm} from "./PremForm.jsx";
|
||||||
import {InformationForm} from "./InformationForm.jsx";
|
import {InformationForm} from "./InformationForm.jsx";
|
||||||
import {LicenceCard} from "./LicenceCard.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;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@ -16,6 +19,19 @@ export function MemberPage() {
|
|||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
|
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 <>
|
return <>
|
||||||
<h2>Page membre</h2>
|
<h2>Page membre</h2>
|
||||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
||||||
@ -39,6 +55,12 @@ export function MemberPage() {
|
|||||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
<LoadingProvider><SelectCard/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,6 +5,9 @@ import {AxiosError} from "../../../components/AxiosError.jsx";
|
|||||||
import {CompteInfo} from "./CompteInfo.jsx";
|
import {CompteInfo} from "./CompteInfo.jsx";
|
||||||
import {InformationForm} from "./InformationForm.jsx";
|
import {InformationForm} from "./InformationForm.jsx";
|
||||||
import {LicenceCard} from "./LicenceCard.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;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@ -15,6 +18,19 @@ export function MemberPage() {
|
|||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
|
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 <>
|
return <>
|
||||||
<h2>Page membre</h2>
|
<h2>Page membre</h2>
|
||||||
<button type="button" className="btn btn-link" onClick={() => navigate("/club/member")}>
|
<button type="button" className="btn btn-link" onClick={() => navigate("/club/member")}>
|
||||||
@ -37,6 +53,12 @@ export function MemberPage() {
|
|||||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
<LoadingProvider><SelectCard/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user