13 Commits

Author SHA1 Message Date
d3a62e980d Merge pull request 'feat: add aff req log detail' (#55) from dev into master
Reviewed-on: #55
2025-11-05 20:19:10 +00:00
b89ed62795 feat: add aff req log detail
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m33s
fix: membre import null email filter
2025-11-05 21:18:29 +01:00
5ffc9fb495 Merge pull request 'fix: typo' (#54) from dev into master
Reviewed-on: #54
2025-09-05 18:31:45 +00:00
5e48bc4623 fix: typo
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 9m51s
2025-09-05 20:31:10 +02:00
81b953fb05 Merge pull request 'dev' (#53) from dev into master
Reviewed-on: #53
2025-09-03 19:47:13 +00:00
c6659f8d85 feat: keep log
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 10m19s
2025-09-03 21:46:26 +02:00
e86fe42b3d fix: club order 2025-09-03 21:35:36 +02:00
ef528aa524 fix: log message length 2025-09-03 19:53:19 +02:00
e6cc4cbc96 Merge pull request 'dev' (#52) from dev into master
Reviewed-on: #52
2025-08-21 09:43:54 +00:00
c58dedf80a Merge pull request 'competition rework' (#51) from dev into master
Reviewed-on: #51
2025-08-18 09:33:51 +00:00
1908de681e Merge pull request 'dev' (#50) from dev into master
Reviewed-on: #50
2025-08-15 19:49:13 +00:00
76381c75bd Merge pull request 'fix: Membre edition' (#49) from dev into master
Reviewed-on: #49
2025-08-15 18:34:43 +00:00
7d281196c1 Merge pull request 'dev' (#48) from dev into master
Reviewed-on: #48
2025-08-15 16:26:23 +00:00
15 changed files with 97 additions and 36 deletions

View File

@@ -76,6 +76,7 @@ jobs:
key: ${{ secrets.SSH_KEY }}
script: |
cd ${{ secrets.TARGET_DIR }}
docker logs ffsaf > "log/ffsaf_logs_$(date +"%Y-%m-%d_%H-%M-%S").log" 2>&1
docker stop ffsaf
docker rm ffsaf
docker compose up --build -d ffsaf

View File

@@ -30,8 +30,10 @@ public class LogModel {
Long target_id;
@Column(columnDefinition = "TEXT")
String target_name;
@Column(columnDefinition = "TEXT")
String message;
public enum ActionType {

View File

@@ -20,6 +20,7 @@ import java.util.List;
@RegisterForReflection
@Entity
@ToString
@Table(name = "membre")
public class MembreModel implements LoggableModel {

View File

@@ -22,6 +22,7 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.ArrayList;
import java.util.List;
@@ -30,6 +31,7 @@ import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class AffiliationService {
private static final Logger LOGGER = Logger.getLogger(AffiliationService.class);
@Inject
CombRepository combRepository;
@@ -146,6 +148,9 @@ public class AffiliationService {
}
public Uni<String> save(AffiliationRequestForm form) {
LOGGER.debug("Affiliation Request Created");
LOGGER.debug(form.toString());
// noinspection ResultOfMethodCallIgnored,ReactiveStreamsUnusedPublisher
return pre_save(form, true)
.chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model)))
@@ -169,6 +174,9 @@ public class AffiliationService {
}
public Uni<?> saveAdmin(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Saved");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> {
@@ -273,6 +281,9 @@ public class AffiliationService {
}
public Uni<?> accept(AffiliationRequestSaveForm form) {
LOGGER.debug("Affiliation Request Accepted");
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req ->
@@ -298,6 +309,7 @@ public class AffiliationService {
}
private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) {
LOGGER.debug("New Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
ClubModel club = new ClubModel();
@@ -336,6 +348,7 @@ public class AffiliationService {
}
private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) {
LOGGER.debug("Old Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
club.setName(form.getName());

View File

@@ -197,6 +197,11 @@ public class MembreService {
return Uni.createFrom().nullItem();
AtomicReference<ClubModel> clubModel = new AtomicReference<>();
LOGGER.debugf("Membre import (size=%d)", data2.size());
for (SimpleMembreInOutData simpleMembreInOutData : data2) {
LOGGER.debugf("-> %s", simpleMembreInOutData.toString());
}
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
clubModel.set(membreModel.getClub());
@@ -205,13 +210,15 @@ public class MembreService {
return repository.list("licence IN ?1 OR LOWER(lname || ' ' || fname) IN ?2 OR email IN ?3",
data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList(),
data2.stream().map(SimpleMembreInOutData::getEmail).filter(Objects::nonNull).toList());
data2.stream().map(SimpleMembreInOutData::getEmail).filter(o -> o != null && !o.isBlank()).toList());
})
.call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get()))
if (!Objects.equals(membreModel.getClub(), clubModel.get())){
LOGGER.info("Similar membres found: " + membreModel);
throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
}
}
Uni<Void> uniResult = Uni.createFrom().voidItem();
for (SimpleMembreInOutData dataIn : data2) {
@@ -229,12 +236,14 @@ public class MembreService {
if (model.getEmail() != null) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email déja utiliser");
}
if (StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email déja utiliser");
}
}
@@ -244,6 +253,7 @@ public class MembreService {
if ((!add && StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3) || (!add && StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
}

View File

@@ -29,6 +29,7 @@ import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.net.URISyntaxException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
@@ -69,7 +70,8 @@ public class ClubEndpoints {
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleClubModel>> getAll() {
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).sorted(
Comparator.comparing(SimpleClubModel::getName)).toList());
}
@GET

View File

@@ -4,12 +4,10 @@ import fr.titionfire.ffsaf.utils.RoleAsso;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MediaType;
import lombok.Getter;
import lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.resteasy.reactive.PartType;
@Getter
@ToString
public class AffiliationRequestSaveForm {
@Schema(description = "L'identifiant de l'affiliation.", example = "1", required = true)
@FormParam("id")
@@ -171,4 +169,39 @@ public class AffiliationRequestSaveForm {
}
}
}
@Override
public String toString() {
return "AffiliationRequestSaveForm{" +
"id=" + id +
", name='" + name + '\'' +
", siret=" + siret +
", rna='" + rna + '\'' +
", address='" + address + '\'' +
", contact='" + contact + '\'' +
", status_len=" + status.length +
", logo_len=" + logo.length +
", m1_mode=" + m1_mode +
", m1_role=" + m1_role +
", m1_lincence='" + m1_lincence + '\'' +
", m1_lname='" + m1_lname + '\'' +
", m1_fname='" + m1_fname + '\'' +
", m1_email='" + m1_email + '\'' +
", m1_email_mode=" + m1_email_mode +
", m2_mode=" + m2_mode +
", m2_role=" + m2_role +
", m2_lincence='" + m2_lincence + '\'' +
", m2_lname='" + m2_lname + '\'' +
", m2_fname='" + m2_fname + '\'' +
", m2_email='" + m2_email + '\'' +
", m2_email_mode=" + m2_email_mode +
", m3_mode=" + m3_mode +
", m3_role=" + m3_role +
", m3_lincence='" + m3_lincence + '\'' +
", m3_lname='" + m3_lname + '\'' +
", m3_fname='" + m3_fname + '\'' +
", m3_email='" + m3_email + '\'' +
", m3_email_mode=" + m3_email_mode +
'}';
}
}

View File

@@ -38,7 +38,7 @@ export function HoraireEditor({data}) {
return <div className="row mb-3">
<input name="training_day_time" value={JSON.stringify(out_data)} readOnly hidden/>
<span className="input-group-text">Horaires d'entrainements</span>
<span className="input-group-text">Horaires d'entraînements</span>
<ul className="list-group form-control">
{state.map((d, index) => {
return <div key={index} className="input-group">

View File

@@ -42,7 +42,7 @@ export function LocationEditor({data, setModal, sendData}) {
return <div className="row mb-3">
<input name="training_location" value={JSON.stringify(out_data)} readOnly hidden/>
<span className="input-group-text">Lieux d'entrainements</span>
<span className="input-group-text">Lieux d'entraînements</span>
<ul className="list-group form-control">
{state.map((d, index) => {
return <div key={index} className="input-group">

View File

@@ -54,7 +54,7 @@ function ClubMenu() {
</div>
<ul className="dropdown-menu">
<li className="nav-item"><NavLink className="nav-link" to="/club/me">Mon club</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/club/member">Member</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/club/member">Membres</NavLink></li>
</ul>
</li>
}
@@ -70,7 +70,7 @@ function AdminMenu() {
Administration
</div>
<ul className="dropdown-menu">
<li className="nav-item"><NavLink className="nav-link" to="/admin/member">Member</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/member">Membres</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/club">Club</NavLink></li>
<li className="nav-item"><NavLink className="nav-link" to="/admin/stats">Statistiques</NavLink></li>
</ul>

View File

@@ -5,22 +5,22 @@ export const Home = () => {
return <>
<div className="container">
<div style={{textAlign: "center", margin: "2em"}}>
<h1 className="text-green-800 text-4xl">Bienvenu sur l'intranet de Fédération Française de Soft Armored Fighting</h1>
<h1 className="text-green-800 text-4xl">Bienvenue sur lintranet de la Fédération France Soft Armored Fighting</h1>
</div>
<div className="row" style={{marginTop: "3em"}}>
<div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}>
<div className="align-content-center"
style={{textAlign: "center", backgroundColor: "#FFFFFF79", padding: "1em 1em 0em 1em", borderRadius: "3em 3em 0 0"}}>
<h2><FontAwesomeIcon icon={faUser} size="2xl"/></h2>
<h2>Pour les combatants</h2>
<h2>Pour les licenciés</h2>
</div>
<p style={{padding: "0.5em 1em 0.5em 1em"}}>
Vous y retrouverez toutes vos informations ainsi que l'état de votre inscription à la fédération. Vous pouvez également
télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi qu'en consultée vos résultats sous réserve
que le club organisateur les ait renseignés. <br/>
télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi que consulter vos résultats sous réserve que
le club organisateur les ait renseignés. <br/>
<br/>
Lors de votre première inscription, vous réservez un email contenant vos
informations d'identification sur ce site, ce mail sera envoyé une fois votre inscription validée par nos soins.
Lors de votre première inscription, vous recevrez un email contenant vos informations d'identification, ce mail sera envoyé
une fois votre licence validée par le secrétariat.
</p>
</div>
<div className="col" style={{backgroundColor: "#FFFFFF79", padding: "0", borderRadius: "3em 3em 1em 1em", margin: "1em"}}>
@@ -30,12 +30,12 @@ export const Home = () => {
<h2>Pour les clubs</h2>
</div>
<p style={{padding: "0.5em 1em 0.5em 1em"}}>
C'est ici que vous pouvez faire l'inscription de vos membres à la fédération, que vous pouvez demanderrenouveler votre
demande d'affiliation, renseigné vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur le
site ffsaf.fr.<br/>
C'est ici que vous pouvez prendre les licences fédérales pour vos adhérents, que vous pouvez demander ou renouveler votre
affiliation, renseigner vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur
le site ffsaf.fr.<br/>
Vous aurez par ailleurs la possibilité de publier des formulaires d'inscriptions pour vos compétitions ainsi
que d'un publié les résultats.<br/><br/>
Vous n'étes pas encore affilié à la fédération ? Vous pouvez faire une demande d'affiliation en cliquant <a href="/affiliation">içi</a>.
que d'enregistrer les résultats.<br/><br/>
Vous n'êtes pas encore affilié à la fédération ? Cliquez <a href="/affiliation">içi</a> pour faire votre première demande.
</p>
</div>
</div>

View File

@@ -172,6 +172,7 @@ function InformationForm({data}) {
export function BureauCard({clubData}) {
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/club/desk/${clubData.id}`, setLoading, 1)
const navigate = useNavigate();
return <>
<div className="card mb-4">
@@ -179,7 +180,8 @@ export function BureauCard({clubData}) {
<div className="card-body">
<ul className="list-group">
{data && data.map((d, index) => {
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start list-group-item-action"
onClick={__ => navigate(`/admin/member/${d.id}`)}>
<div className="me-auto"><small>{d.role}</small><br/>{d.lname} {d.fname}</div>
</div>
})}

View File

@@ -23,7 +23,7 @@ export function ClubRoot() {
return <>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap'}}>
<h1>Espace club</h1><h3 style={{marginLeft: '0.75em'}}>{club}</h3></div>
<h3 style={{marginLeft: '0.75em'}}>Club: {club}</h3></div>
<LoadingProvider>
<Outlet/>
</LoadingProvider>

View File

@@ -1,12 +1,11 @@
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {useFetch} from "../../../hooks/useFetch.js";
import {useEffect, useReducer, useState} from "react";
import {useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faEye, faFilePdf, faPen} from "@fortawesome/free-solid-svg-icons";
import {faEye, faFilePdf} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js";
import {apiAxios} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
import {useNavigate} from "react-router-dom";
const vite_url = import.meta.env.VITE_URL;
@@ -42,8 +41,8 @@ export function AffiliationCard({clubData}) {
<a href={`${vite_url}/api/club/me/affiliation`} target='#'>
<button className="btn btn-primary" type="button" id="button-addon1" style={{marginTop: '1em'}}
onClick={e => null}>
Téléchargée l'attestation d'affiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
onClick={_ => null}>
Télécharger lattestation daffiliation <FontAwesomeIcon icon={faFilePdf}></FontAwesomeIcon>
</button>
</a>
</div>

View File

@@ -1,9 +1,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, errFormater} from "../../../utils/Tools.js";
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {AxiosError} from "../../../components/AxiosError.jsx";
import {AffiliationCard, BureauCard} from "./AffiliationCard.jsx";
import {CountryList, TextField} from "../../../components/MemberCustomFiels.jsx";
@@ -22,7 +20,7 @@ export function MyClubPage() {
const {data, error} = useFetch(`/club/me`, setLoading, 1)
return <>
<h2>Mon club</h2>
<h3>Données administratives</h3>
{data
? <div>
<div className="row">
@@ -91,7 +89,7 @@ function InformationForm({data}) {
<div className="col-md-6">
<a href={`${vite_url}/api/club/${data.id}/status`} target='_blank'>
<button className="btn btn-outline-secondary" type="button" id="button-addon1"
onClick={e => null}>
onClick={_ => null}>
<FontAwesomeIcon icon={faFilePdf} size="5x"></FontAwesomeIcon><br/>
Voir les statues
</button>