feat: better error message

This commit is contained in:
Thibaut Valentin 2024-07-19 14:52:05 +02:00
parent a58dcdd08e
commit 47daa459e2
35 changed files with 327 additions and 89 deletions

View File

@ -4,6 +4,8 @@ import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm;
import fr.titionfire.ffsaf.utils.SequenceType;
@ -14,7 +16,6 @@ import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
@ -60,42 +61,42 @@ public class AffiliationService {
return Uni.createFrom().item(affModel)
.invoke(Unchecked.consumer(model -> {
if (model.getSaison() != currentSaison && model.getSaison() != currentSaison + 1) {
throw new IllegalArgumentException("Saison not valid");
throw new DBadRequestException("Saison non valid");
}
}))
.chain(() -> repositoryRequest.count("siret = ?1 and saison = ?2", affModel.getSiret(),
affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new IllegalArgumentException("Affiliation request already exists");
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
.chain(() -> clubRepository.find("SIRET = ?1", affModel.getSiret()).firstResult().chain(club ->
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) {
throw new IllegalArgumentException("Affiliation already exists");
throw new DBadRequestException("Affiliation déjà existante");
}
}))
.map(o -> affModel)
.call(model -> ((model.getM1_lincence() != -1) ? combRepository.find("licence",
model.getM1_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new IllegalArgumentException("Licence membre n°1 inconnue");
throw new DBadRequestException("Licence membre n°1 inconnue");
}
})) : Uni.createFrom().nullItem())
)
.call(model -> ((model.getM2_lincence() != -1) ? combRepository.find("licence",
model.getM2_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new IllegalArgumentException("Licence membre n°2 inconnue");
throw new DBadRequestException("Licence membre n°2 inconnue");
}
})) : Uni.createFrom().nullItem())
)
.call(model -> ((model.getM3_lincence() != -1) ? combRepository.find("licence",
model.getM3_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new IllegalArgumentException("Licence membre n°3 inconnue");
throw new DBadRequestException("Licence membre n°3 inconnue");
}
})) : Uni.createFrom().nullItem())
);
@ -104,7 +105,7 @@ public class AffiliationService {
public Uni<?> saveEdit(AffiliationRequestForm form) {
return pre_save(form, false)
.chain(model -> repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new NotFoundException("Affiliation request not found"))
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(origine -> {
origine.setName(model.getName());
origine.setRNA(model.getRNA());
@ -144,7 +145,7 @@ public class AffiliationService {
public Uni<?> saveAdmin(AffiliationRequestSaveForm form) {
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new NotFoundException("Affiliation request not found"))
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> {
model.setName(form.getName());
model.setSiret(form.getSiret());
@ -237,7 +238,7 @@ public class AffiliationService {
public Uni<?> accept(AffiliationRequestSaveForm form) {
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new NotFoundException("Affiliation request not found"))
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req ->
clubRepository.find("SIRET = ?1", form.getSiret()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
@ -298,7 +299,7 @@ public class AffiliationService {
public Uni<SimpleReqAffiliation> getRequest(long id) {
return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel)
.onItem().ifNull().failWith(new NotFoundException("Affiliation request not found"))
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.call(out -> clubRepository.find("SIRET = ?1", out.getSiret()).firstResult().invoke(c -> {
if (c != null) {
out.setClub(c.getId());
@ -322,7 +323,7 @@ public class AffiliationService {
public Uni<List<SimpleAffiliation>> getAffiliation(long id) {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new NotFoundException("Affiliation request not found"))
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.chain(model -> repositoryRequest.list("siret = ?1", model.getSIRET())
.map(reqs -> reqs.stream().map(req ->
@ -334,10 +335,10 @@ public class AffiliationService {
public Uni<SimpleAffiliation> setAffiliation(long id, int saison) {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new NotFoundException("Club non trouver"))
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.invoke(Unchecked.consumer(club -> {
if (club.getAffiliations().stream().anyMatch(affiliation -> affiliation.getSaison() == saison)) {
throw new IllegalArgumentException("Affiliation deja existante");
throw new DBadRequestException("Affiliation déjà existante");
}
}))
.chain(club ->

View File

@ -13,6 +13,9 @@ import fr.titionfire.ffsaf.net2.request.SReqClub;
import fr.titionfire.ffsaf.rest.data.DeskMember;
import fr.titionfire.ffsaf.rest.data.RenewAffData;
import fr.titionfire.ffsaf.rest.data.SimpleClubList;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.rest.from.FullClubForm;
import fr.titionfire.ffsaf.rest.from.PartClubForm;
import fr.titionfire.ffsaf.utils.*;
@ -26,9 +29,6 @@ import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.hibernate.reactive.mutiny.Mutiny;
@ -105,7 +105,7 @@ public class ClubService {
.call(result -> query.count().invoke(result::setResult_count))
.call(result -> query.pageCount()
.invoke(Unchecked.consumer(pages -> {
if (page > pages) throw new BadRequestException();
if (page > pages) throw new DBadRequestException("Page out of range");
}))
.invoke(result::setPage_count))
.call(result -> query.page(Page.of(page, limit)).list()
@ -124,7 +124,7 @@ public class ClubService {
public Uni<ClubModel> getOfUser(JsonWebToken idToken) {
return combRepository.find("userId = ?1", idToken.getSubject()).firstResult().invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new NotFoundException("Club not found");
throw new DNotFoundException("Club non trouvé");
}))
.map(MembreModel::getClub)
.call(club -> Mutiny.fetch(club.getContact()));
@ -146,9 +146,9 @@ public class ClubService {
return combRepository.find("userId = ?1", idToken.getSubject()).firstResult().invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new NotFoundException("Club not found");
throw new DNotFoundException("Club non trouvé");
if (!GroupeUtils.isInClubGroup(m.getClub().getId(), idToken))
throw new ForbiddenException();
throw new DForbiddenException();
}))
.map(MembreModel::getClub)
.call(club -> Mutiny.fetch(club.getContact()))
@ -159,7 +159,7 @@ public class ClubService {
try {
club.setContact(MAPPER.readValue(form.getContact(), typeRef));
} catch (JsonProcessingException e) {
throw new BadRequestException();
throw new DBadRequestException("Erreur de format des contacts");
}
club.setTraining_location(form.getTraining_location());
@ -191,7 +191,7 @@ public class ClubService {
try {
m.setContact(MAPPER.readValue(input.getContact(), typeRef));
} catch (JsonProcessingException e) {
throw new BadRequestException();
throw new DBadRequestException("Erreur de format des contacts");
}
}
return Panache.withTransaction(() -> repository.persist(m));

View File

@ -2,6 +2,7 @@ package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.smallrye.mutiny.Uni;
@ -78,7 +79,7 @@ public class KeycloakService {
public Uni<String> getUserFromMember(MembreModel membreModel) {
if (membreModel.getUserId() == null) {
return Uni.createFrom()
.failure(new NullPointerException("No keycloak user linked to the user id=" + membreModel.getId()));
.failure(new DInternalError("No keycloak user linked to the user id=" + membreModel.getId()));
}
return Uni.createFrom().item(membreModel::getUserId);
}

View File

@ -5,6 +5,7 @@ import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.data.repository.SequenceRepository;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.SequenceType;
import fr.titionfire.ffsaf.utils.Utils;
@ -14,7 +15,6 @@ import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.hibernate.reactive.mutiny.Mutiny;
@ -91,7 +91,7 @@ public class LicenceService {
return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count()
.invoke(Unchecked.consumer(count -> {
if (count > 0)
throw new BadRequestException();
throw new DBadRequestException("Licence déjà demandée");
})).chain(__ -> combRepository.findById(id).chain(combRepository -> {
LicenceModel model = new LicenceModel();
model.setMembre(combRepository);

View File

@ -11,6 +11,8 @@ import fr.titionfire.ffsaf.net2.request.SReqComb;
import fr.titionfire.ffsaf.rest.data.MeData;
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.ClubMemberForm;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.*;
@ -25,8 +27,6 @@ import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.hibernate.reactive.mutiny.Mutiny;
@ -101,7 +101,7 @@ public class MembreService {
.call(result -> query.count().invoke(result::setResult_count))
.call(result -> query.pageCount()
.invoke(Unchecked.consumer(pages -> {
if (page > pages) throw new BadRequestException();
if (page > pages) throw new DBadRequestException("Page out of range");
}))
.invoke(result::setPage_count))
.call(result -> query.page(Page.of(page, limit)).list()
@ -155,7 +155,7 @@ public class MembreService {
return repository.findById(id)
.invoke(Unchecked.consumer(membreModel -> {
if (!GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new ForbiddenException();
throw new DForbiddenException();
}))
.invoke(Unchecked.consumer(membreModel -> {
RoleAsso source = RoleAsso.MEMBRE;
@ -163,7 +163,7 @@ public class MembreService {
else if (securityIdentity.getRoles().contains("club_secretaire")) source = RoleAsso.SECRETAIRE;
else if (securityIdentity.getRoles().contains("club_respo_intra")) source = RoleAsso.MEMBREBUREAU;
if (!membre.getRole().equals(membreModel.getRole()) && membre.getRole().level >= source.level)
throw new ForbiddenException();
throw new DForbiddenException("Permission insuffisante");
}))
.onItem().transformToUni(target -> {
target.setFname(membre.getFname());
@ -225,12 +225,12 @@ public class MembreService {
return repository.findById(id)
.invoke(Unchecked.consumer(membreModel -> {
if (!GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new ForbiddenException();
throw new DForbiddenException();
}))
.call(membreModel -> licenceRepository.find("membre = ?1", membreModel).count()
.invoke(Unchecked.consumer(l -> {
if (l > 0)
throw new BadRequestException();
throw new DBadRequestException("Impossible de supprimer un membre avec des licences");
})))
.call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())

View File

@ -4,6 +4,7 @@ import fr.titionfire.ffsaf.domain.service.AffiliationService;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliationResume;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm;
import fr.titionfire.ffsaf.utils.GroupeUtils;
@ -42,7 +43,7 @@ public class AffiliationEndpoints {
Consumer<Long> checkPerm = Unchecked.consumer(id -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(id, idToken))
throw new ForbiddenException();
throw new DForbiddenException();
});
@GET
@ -68,7 +69,7 @@ public class AffiliationEndpoints {
public Uni<SimpleReqAffiliation> getAffRequest(@PathParam("id") long id) {
return service.getRequest(id).invoke(Unchecked.consumer(o -> {
if (o.getClub() == null && !securityIdentity.getRoles().contains("federation_admin"))
throw new ForbiddenException();
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()));
}
@ -79,7 +80,7 @@ public class AffiliationEndpoints {
public Uni<?> getDelAffRequest(@PathParam("id") long id) {
return service.getRequest(id).invoke(Unchecked.consumer(o -> {
if (o.getClub() == null && !securityIdentity.getRoles().contains("federation_admin"))
throw new ForbiddenException();
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()))
.chain(o -> service.deleteReqAffiliation(id));
}

View File

@ -2,6 +2,7 @@ package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@ -20,7 +21,7 @@ public class AssoEndpoints {
return sirenService.get_unite(siren).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 400)
return new BadRequestException("Not found");
return new DNotFoundException("Siret introuvable");
}
return throwable;
});

View File

@ -7,6 +7,8 @@ import fr.titionfire.ffsaf.rest.data.DeskMember;
import fr.titionfire.ffsaf.rest.data.RenewAffData;
import fr.titionfire.ffsaf.rest.data.SimpleClub;
import fr.titionfire.ffsaf.rest.data.SimpleClubList;
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;
@ -50,11 +52,11 @@ public class ClubEndpoints {
Consumer<ClubModel> checkPerm = Unchecked.consumer(clubModel -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(clubModel.getId(),
idToken))
throw new ForbiddenException();
throw new DForbiddenException();
});
Consumer<Long> checkPerm2 = Unchecked.consumer(id -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(id, idToken))
throw new ForbiddenException();
throw new DForbiddenException();
});
@GET
@ -112,7 +114,8 @@ public class ClubEndpoints {
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 InternalError("Fail to get MimeType " + out);
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
}));
else
return Uni.createFrom().nullItem();
@ -120,7 +123,8 @@ public class ClubEndpoints {
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 InternalError("Fail to get MimeType " + out);
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
}));
else
return Uni.createFrom().nullItem();

View File

@ -4,6 +4,8 @@ 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;
@ -46,7 +48,7 @@ public class CombEndpoints {
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(
membreModel.getClub().getId(), idToken))
throw new ForbiddenException();
throw new DForbiddenException();
});
@GET
@ -110,12 +112,14 @@ public class CombEndpoints {
public Uni<String> setAdminMembre(@PathParam("id") long id, FullMemberForm input) {
return membreService.update(id, input)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + 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 InternalError("Fail to get MimeType " + out);
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
}));
else
return Uni.createFrom().nullItem();
@ -160,7 +164,8 @@ public class CombEndpoints {
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 InternalError("Fail to get MimeType " + out);
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
}));
else
return Uni.createFrom().nullItem();

View File

@ -1,6 +1,7 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.KeycloakService;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.MemberPermForm;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import fr.titionfire.ffsaf.utils.Pair;
@ -38,7 +39,7 @@ public class CompteEndpoints {
return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> {
if (!securityIdentity.getRoles().contains("federation_admin") && pair.getKey().groups().stream().map(GroupRepresentation::getPath)
.noneMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, accessToken)))
throw new ForbiddenException();
throw new DForbiddenException();
return pair;
})).map(Pair::getValue);
}

View File

@ -3,6 +3,7 @@ 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.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import io.quarkus.oidc.IdToken;
@ -33,7 +34,7 @@ public class LicenceEndpoints {
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new ForbiddenException();
throw new DForbiddenException();
});
@GET

View File

@ -0,0 +1,14 @@
package fr.titionfire.ffsaf.rest.exception;
import jakarta.ws.rs.core.Response;
import java.io.Serial;
public class DBadRequestException extends DetailException {
@Serial
private static final long serialVersionUID = 7518556311032332135L;
public DBadRequestException(String message) {
super(Response.Status.BAD_REQUEST, message);
}
}

View File

@ -0,0 +1,18 @@
package fr.titionfire.ffsaf.rest.exception;
import jakarta.ws.rs.core.Response;
import java.io.Serial;
public class DForbiddenException extends DetailException {
@Serial
private static final long serialVersionUID = 8408920537659758038L;
public DForbiddenException() {
this("Accès a la ressource interdite");
}
public DForbiddenException(String message) {
super(Response.Status.FORBIDDEN, message);
}
}

View File

@ -0,0 +1,15 @@
package fr.titionfire.ffsaf.rest.exception;
import jakarta.ws.rs.core.Response;
import java.io.Serial;
public class DInternalError extends DetailException {
@Serial
private static final long serialVersionUID = -3635595157694180842L;
public DInternalError(String message) {
super(Response.Status.INTERNAL_SERVER_ERROR, message);
}
}

View File

@ -0,0 +1,15 @@
package fr.titionfire.ffsaf.rest.exception;
import jakarta.ws.rs.core.Response;
import java.io.Serial;
public class DNotFoundException extends DetailException{
@Serial
private static final long serialVersionUID = -5193524134675732376L;
public DNotFoundException(String message) {
super(Response.Status.NOT_FOUND, message);
}
}

View File

@ -0,0 +1,20 @@
package fr.titionfire.ffsaf.rest.exception;
import jakarta.ws.rs.core.Response;
import lombok.Getter;
import java.io.Serial;
@Getter
public class DetailException extends Exception {
@Serial
private static final long serialVersionUID = 5349921926328753676L;
private final Response.Status status;
public DetailException(Response.Status status, String message) {
super(message);
this.status = status;
}
}

View File

@ -0,0 +1,19 @@
package fr.titionfire.ffsaf.rest.exception;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class DetailExceptionMapper implements ExceptionMapper<DetailException> {
@Override
public Response toResponse(DetailException e) {
return Response.status(e.getStatus())
.entity(e.getMessage())
.type(MediaType.TEXT_PLAIN_TYPE)
.build();
}
}

View File

@ -0,0 +1,20 @@
package fr.titionfire.ffsaf.rest.exception;
import fr.titionfire.ffsaf.utils.KeycloakException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class KeycloakExceptionMapper implements ExceptionMapper<KeycloakException> {
@Override
public Response toResponse(KeycloakException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Erreur du gestionnaire d'identité: " + e.getMessage())
.type(MediaType.TEXT_PLAIN_TYPE)
.build();
}
}

View File

@ -80,6 +80,14 @@ function Root() {
check_validity(data => dispatch({type: 'init', val: data}))
}, []);
useEffect(() => {
const interval = setInterval(() => {
check_validity(data => dispatch({type: 'update', val: data}))
}, 1000 * 60 * 9)
return () => clearInterval(interval)
}, []);
return <>
<header>
<Nav/>

View File

@ -32,6 +32,8 @@ function authReducer(auth, action) {
case 'update': {
return {
...auth,
is_authenticated: action.val.state,
userinfo: action.val.userinfo
// data: {realm_access: {roles: ["federation_admin"]}}
// data: JSON.parse(atob(action.token.split('.')[1]))
}

View File

@ -8,9 +8,7 @@ export function useCountries(country = 'fr') {
useEffect(() => {
if (countries[country] === undefined) {
console.log('fetch')
apiAxios.get(`/countries/${country}/${country}`).then(data => {
console.log(data.data)
countries[country] = data.data
setOut(data.data)
})

View File

@ -1,5 +1,5 @@
import {useEffect, useState} from "react";
import {apiAxios, getSaison} from "../utils/Tools.js";
import {apiAxios, errFormater, getSaison} from "../utils/Tools.js";
import {toast} from "react-toastify";
import {useLocation, useNavigate} from "react-router-dom";
@ -89,7 +89,11 @@ export function DemandeAff() {
{
pending: "Annulation de la demande d'affiliation en cours",
success: "Demande d'affiliation annulée avec succès 🎉",
error: "Échec de l'annulation de la demande d'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'annulation de la demande d'affiliation")
}
}
}
).then(_ => {
navigate("/club/me")
@ -102,7 +106,11 @@ export function DemandeAff() {
{
pending: "Enregistrement des modifications en cours",
success: "Modifications enregistrées avec succès 🎉",
error: "Échec de l'enregistrement des modifications 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement des modifications")
}
}
}
).then(_ => {
navigate("/club/me")
@ -115,7 +123,11 @@ export function DemandeAff() {
{
pending: "Enregistrement de la demande d'affiliation en cours",
success: "Demande d'affiliation enregistrée avec succès 🎉",
error: "Échec de la demande d'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la demande d'affiliation")
}
}
}
).then(_ => {
navigate("/affiliation/ok")
@ -208,7 +220,11 @@ function AssoInfo({initData, needFile}) {
{
pending: "Recherche de l'association en cours",
success: "Association trouvée avec succès 🎉",
error: "Échec de la recherche de l'association 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la recherche de l'association")
}
}
}
).then(data => {
const data2 = data.data.unite_legale

View File

@ -7,7 +7,7 @@ import {Input} from "../components/Input.jsx";
import {useLocation, useNavigate} from "react-router-dom";
import {Checkbox} from "../components/MemberCustomFiels.jsx";
import axios from "axios";
import {apiAxios} from "../utils/Tools.js";
import {apiAxios, errFormater} from "../utils/Tools.js";
import {toast} from "react-toastify";
import {SearchBar} from "../components/SearchBar.jsx";
@ -58,7 +58,11 @@ export function MemberList({source}) {
{
pending: "Chargement des licences...",
success: "Licences chargées",
error: "Impossible de charger les licences"
error: {
render({data}) {
return errFormater(data, "Impossible de charger les licences")
}
}
})
.then(data => {
setLicenceData(data.data);

View File

@ -3,7 +3,7 @@ import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx
import {useFetch} from "../../../hooks/useFetch.js";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {toast} from "react-toastify";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {RoleList, TextField} from "../../../components/MemberCustomFiels.jsx";
import {useEffect, useRef, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
@ -44,7 +44,11 @@ function Content({data, refresh}) {
{
pending: "Suppression de la demande d'affiliation en cours",
success: "Demande d'affiliation supprimée avec succès 🎉",
error: "Échec de la suppression de la demande d'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression de la demande d'affiliation")
}
}
}
).then(_ => {
navigate("/admin/affiliation/request")
@ -128,7 +132,11 @@ function Content({data, refresh}) {
{
pending: "Enregistrement de la demande d'affiliation en cours",
success: "Demande d'affiliation enregistrée avec succès 🎉",
error: "Échec de l'enregistrement de la demande d'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement de la demande d'affiliation")
}
}
}
).then(_ => {
refresh(`/affiliation/request/${data.id}`)
@ -139,7 +147,11 @@ function Content({data, refresh}) {
{
pending: "Acceptation de l'affiliation en cours",
success: "Affiliation acceptée avec succès 🎉",
error: "Échec de l'acceptation de l'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'acceptation de l'affiliation")
}
}
}
).then(_ => {
navigate("/admin/affiliation/request")

View File

@ -4,7 +4,7 @@ import {useEffect, useReducer, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEye, faPen} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js";
import {apiAxios, errFormater, getSaison} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
import {useNavigate} from "react-router-dom";
@ -71,7 +71,11 @@ function sendAffiliation(event, dispatch) {
{
pending: "Enregistrement de l'affiliation en cours",
success: "Affiliation enregistrée avec succès 🎉",
error: "Échec de l'enregistrement de l'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement de l'affiliation")
}
}
}
).then(data => {
dispatch({type: 'UPDATE_OR_ADD', payload: data.data})
@ -87,7 +91,11 @@ function removeAffiliation(id, dispatch) {
{
pending: "Suppression de l'affiliation en cours",
success: "Affiliation supprimée avec succès 🎉",
error: "Échec de la suppression de l'affiliation 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression de l'affiliation")
}
}
}
).then(_ => {
dispatch({type: 'REMOVE', payload: id})

View File

@ -3,7 +3,7 @@ import {useEffect, useState} from "react";
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {toast} from "react-toastify";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {Checkbox} from "../../../components/MemberCustomFiels.jsx";
import {ThreeDots} from "react-loader-spinner";
@ -55,7 +55,11 @@ export function ClubList() {
{
pending: "Chargement des affiliation...",
success: "Affiliation chargées",
error: "Impossible de charger les affiliations"
error: {
render({data}) {
return errFormater(data, "Impossible de charger les affiliations")
}
}
})
.then(data => {
setAffiliationData(data.data);

View File

@ -2,7 +2,7 @@ import {useNavigate, useParams} from "react-router-dom";
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {toast} from "react-toastify";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {AffiliationCard} from "./AffiliationCard.jsx";
@ -30,7 +30,11 @@ export function ClubPage() {
{
pending: "Suppression du club en cours...",
success: "Club supprimé avec succès 🎉",
error: "Échec de la suppression du club 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression du club")
}
},
}
).then(_ => {
navigate("/admin/club")
@ -85,7 +89,11 @@ function InformationForm({data}) {
{
pending: "Enregistrement du club en cours",
success: "Club enregistrée avec succès 🎉",
error: "Échec de l'enregistrement du club 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement du club")
}
},
}
)
}

View File

@ -2,7 +2,7 @@ import {useNavigate} from "react-router-dom";
import {LoadingProvider} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {toast} from "react-toastify";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
import {useRef, useState} from "react";
@ -48,7 +48,11 @@ function InformationForm() {
{
pending: "Création du club en cours",
success: "Club créé avec succès 🎉",
error: "Échec de la création du club 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la création du club")
}
}
}
).then(data => {
navigate(`/admin/club/${data.data}`);

View File

@ -1,5 +1,5 @@
import {toast} from "react-toastify";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {ColoredCircle} from "../../../components/ColoredCircle.jsx";
@ -8,15 +8,16 @@ import {AxiosError} from "../../../components/AxiosError.jsx";
export function CompteInfo({userData}) {
const creatAccount = () => {
let err = {};
toast.promise(
apiAxios.put(`/compte/${userData.id}/init`).catch(e => {
err = e
}),
apiAxios.put(`/compte/${userData.id}/init`),
{
pending: 'Création du compte en cours',
success: 'Compte créé avec succès 🎉',
error: 'Échec de la création du compte 😕 (code: ' + err.response.status + ')'
error: {
render({data}) {
return errFormater(data, "Échec de la création du compte")
}
}
}
)
}
@ -28,7 +29,11 @@ export function CompteInfo({userData}) {
{
pending: "Définition de l'identifient en cours",
success: "Identifient défini avec succès 🎉",
error: "Échec de la définition de l'identifient 😕 "
error: {
render({data}) {
return errFormater(data, "Échec de la définition de l'identifient")
}
}
}
)
}

View File

@ -5,7 +5,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faPen} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {CheckField, TextField} from "../../../components/MemberCustomFiels.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js";
import {apiAxios, errFormater, getSaison} from "../../../utils/Tools.js";
import {Input} from "../../../components/Input.jsx";
import {toast} from "react-toastify";
@ -98,7 +98,11 @@ function sendLicence(event, dispatch) {
{
pending: "Enregistrement de la licence en cours",
success: "Licence enregistrée avec succès 🎉",
error: "Échec de l'enregistrement de la licence 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement de la licence")
}
}
}
).then(data => {
dispatch({type: 'UPDATE_OR_ADD', payload: data.data})
@ -113,7 +117,11 @@ function removeLicence(id, dispatch) {
{
pending: "Suppression de la licence en cours",
success: "Licence supprimée avec succès 🎉",
error: "Échec de la suppression de la licence 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression de la licence")
}
}
}
).then(_ => {
dispatch({type: 'REMOVE', payload: id})

View File

@ -7,7 +7,7 @@ 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 {apiAxios, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
const vite_url = import.meta.env.VITE_URL;
@ -25,7 +25,11 @@ export function MemberPage() {
{
pending: "Suppression du compte en cours...",
success: "Compte supprimé avec succès 🎉",
error: "Échec de la suppression du compte 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression du compte")
}
}
}
).then(_ => {
navigate("/admin/member")

View File

@ -2,7 +2,7 @@ import {useNavigate, useParams} from "react-router-dom";
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {toast} from "react-toastify";
import {apiAxios} from "../../../utils/Tools.js";
import {apiAxios, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {AffiliationCard, BureauCard} from "./AffiliationCard.jsx";
@ -58,7 +58,11 @@ function InformationForm({data}) {
{
pending: "Enregistrement des modifications en cours",
success: "Modifications enregistrées avec succès 🎉",
error: "Échec de l'enregistrement des modifications 😕"
error: {
render({data}) {
return errFormater(data, "Échec de l'enregistrement des modifications")
}
}
}
)
}

View File

@ -4,7 +4,7 @@ import {useEffect, useReducer, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faInfo, faPen} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js";
import {apiAxios, errFormater, getSaison} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import {ColoredText} from "../../../components/ColoredCircle.jsx";
@ -95,7 +95,11 @@ function sendLicence(event, dispatch) {
{
pending: "Enregistrement de la demande de licence en cours",
success: "Demande de licence enregistrée avec succès 🎉",
error: "Échec de la demande de licence 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la demande de licence")
}
}
}
).then(data => {
dispatch({type: 'UPDATE_OR_ADD', payload: data.data})
@ -110,7 +114,11 @@ function removeLicence(id, dispatch) {
{
pending: "Suppression de la demande en cours",
success: "Demande supprimée avec succès 🎉",
error: "Échec de la suppression de la demande de licence 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression de la demande de licence")
}
}
}
).then(_ => {
dispatch({type: 'REMOVE', payload: id})

View File

@ -6,7 +6,7 @@ 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 {apiAxios, errFormater} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
const vite_url = import.meta.env.VITE_URL;
@ -24,7 +24,11 @@ export function MemberPage() {
{
pending: "Suppression du compte en cours...",
success: "Compte supprimé avec succès 🎉",
error: "Échec de la suppression du compte 😕"
error: {
render({data}) {
return errFormater(data, "Échec de la suppression du compte")
}
}
}
).then(_ => {
navigate("/club/member")

View File

@ -7,6 +7,11 @@ export const apiAxios = axios.create({
});
apiAxios.defaults.headers.post['Accept'] = 'application/json; charset=UTF-8';
export const errFormater = (data, msg) => {
return `${msg} (${data.response.statusText}: ${data.response.data}) 😕`
}
export function getCategoryFormBirthDate(birth_date, currentDate = new Date()) {
const currentSaison = getSaison(currentDate)
const birthYear = birth_date.getFullYear()