commit
8cc03de96d
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -35,7 +35,6 @@ jobs:
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
script: |
|
||||
cd ${{ secrets.TARGET_DIR }}/ffsaf
|
||||
cp ../vite.env src/main/webapp/.env
|
||||
chmod 740 mvnw
|
||||
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 ./mvnw package -Pnative -DskipTests
|
||||
|
||||
@ -76,6 +75,7 @@ jobs:
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
script: |
|
||||
cd ${{ secrets.TARGET_DIR }}/ffsaf/src/main/webapp
|
||||
cp ${{ secrets.TARGET_DIR }}/vite.env .env
|
||||
npm run build
|
||||
rm -rf ${{ secrets.TARGET_DIR }}/ffsaf-site
|
||||
mv dist ${{ secrets.TARGET_DIR }}/ffsaf-site
|
||||
16
pom.xml
16
pom.xml
@ -72,12 +72,28 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-oidc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-keycloak-authorization</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.22</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
<version>3.0.0-BETA</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@ -1,16 +1,90 @@
|
||||
package fr.titionfire;
|
||||
|
||||
import io.quarkus.oidc.IdToken;
|
||||
import io.quarkus.oidc.RefreshToken;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
|
||||
@Path("/hello")
|
||||
public class ExampleResource {
|
||||
|
||||
/*@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String hello() {
|
||||
return "Hello from RESTEasy Reactive";
|
||||
return "Hello, " + idToken.getClaim("name");
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Injection point for the ID Token issued by the OpenID Connect Provider
|
||||
*/
|
||||
@Inject
|
||||
@IdToken
|
||||
JsonWebToken idToken;
|
||||
|
||||
/**
|
||||
* Injection point for the Access Token issued by the OpenID Connect Provider
|
||||
*/
|
||||
@Inject
|
||||
JsonWebToken accessToken;
|
||||
|
||||
/**
|
||||
* Injection point for the Refresh Token issued by the OpenID Connect Provider
|
||||
*/
|
||||
@Inject
|
||||
RefreshToken refreshToken;
|
||||
|
||||
@Inject
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
/**
|
||||
* Returns the tokens available to the application. This endpoint exists only for demonstration purposes, you should not
|
||||
* expose these tokens in a real application.
|
||||
*
|
||||
* @return a HTML page containing the tokens available to the application
|
||||
*/
|
||||
@GET
|
||||
@Produces("text/html")
|
||||
@NoCache
|
||||
public String getTokens() {
|
||||
StringBuilder response = new StringBuilder().append("<html>")
|
||||
.append("<body>")
|
||||
.append("<ul>");
|
||||
|
||||
|
||||
Object userName = this.idToken.getClaim("preferred_username");
|
||||
|
||||
if (userName != null) {
|
||||
response.append("<li>username: ").append(userName.toString()).append("</li>");
|
||||
}
|
||||
if (userName != null) {
|
||||
response.append("<li>username: ").append(this.idToken.toString()).append("</li>");
|
||||
}
|
||||
|
||||
Object scopes = this.accessToken.getClaim("scope");
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>scopes: ").append(scopes.toString()).append("</li>");
|
||||
}
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>scopes: ").append(this.accessToken.toString()).append("</li>");
|
||||
}
|
||||
|
||||
if (scopes != null) {
|
||||
response.append("<li>getRoles: ").append(this.securityIdentity.getRoles()).append("</li>");
|
||||
}
|
||||
|
||||
response.append("<li>refresh_token: ").append(refreshToken.getToken() != null).append("</li>");
|
||||
|
||||
return response.append("</ul>").append("</body>").append("</html>").toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Contact;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
@ -25,4 +26,25 @@ public class ClubModel {
|
||||
String country;
|
||||
|
||||
String shieldURL;
|
||||
|
||||
//@Enumerated(EnumType.STRING)
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "club_contact_mapping",
|
||||
joinColumns = {@JoinColumn(name = "club_id", referencedColumnName = "id")})
|
||||
@MapKeyColumn(name = "contact_type")
|
||||
Map<Contact, String> contact;
|
||||
|
||||
String training_location;
|
||||
|
||||
String training_day_time;
|
||||
|
||||
String contact_intern;
|
||||
|
||||
String RNA;
|
||||
|
||||
String SIRET;
|
||||
|
||||
String no_affiliation;
|
||||
|
||||
boolean international;
|
||||
}
|
||||
|
||||
@ -2,29 +2,31 @@ package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import fr.titionfire.ffsaf.utils.GradeArbitrage;
|
||||
import fr.titionfire.ffsaf.utils.RoleAsso;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
|
||||
@Entity
|
||||
@Table(name = "comb")
|
||||
public class CombModel {
|
||||
@Table(name = "membre")
|
||||
public class MembreModel {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
String lname = "";
|
||||
String fname = "";
|
||||
String lname;
|
||||
String fname;
|
||||
|
||||
Categorie categorie;
|
||||
|
||||
@ -34,7 +36,17 @@ public class CombModel {
|
||||
|
||||
Genre genre;
|
||||
|
||||
int licence = 0;
|
||||
int licence;
|
||||
|
||||
String country = "fr";
|
||||
String country;
|
||||
|
||||
Date birth_date;
|
||||
|
||||
String email;
|
||||
|
||||
RoleAsso role;
|
||||
|
||||
GradeArbitrage grade_arbitrage;
|
||||
|
||||
String url_photo;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ClubRepository implements PanacheRepositoryBase<ClubModel, Long> {
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CombRepository implements PanacheRepositoryBase<MembreModel, Long> {
|
||||
}
|
||||
@ -1,11 +1,16 @@
|
||||
package fr.titionfire.ffsaf.domain.entity;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||
import fr.titionfire.ffsaf.utils.Contact;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class ClubEntity {
|
||||
@ -13,12 +18,39 @@ public class ClubEntity {
|
||||
private String name;
|
||||
private String country;
|
||||
private String shieldURL;
|
||||
private Map<Contact, String> contact;
|
||||
private String training_location;
|
||||
private String training_day_time;
|
||||
private String contact_intern;
|
||||
private String RNA;
|
||||
private String SIRET;
|
||||
private String no_affiliation;
|
||||
private boolean international;
|
||||
|
||||
public static ClubEntity fromModel (ClubModel model) {
|
||||
return new ClubEntity(model.getId(), model.getName(), model.getCountry(), model.getShieldURL());
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ClubEntity.builder()
|
||||
.id(model.getId())
|
||||
.name(model.getName())
|
||||
.country(model.getCountry())
|
||||
.shieldURL(model.getShieldURL())
|
||||
.contact(model.getContact())
|
||||
.training_location(model.getTraining_location())
|
||||
.training_day_time(model.getTraining_day_time())
|
||||
.contact_intern(model.getContact_intern())
|
||||
.RNA(model.getRNA())
|
||||
.SIRET(model.getSIRET())
|
||||
.no_affiliation(model.getNo_affiliation())
|
||||
.international(model.isInternational())
|
||||
.build();
|
||||
}
|
||||
|
||||
public ClubModel toModel () {
|
||||
return new ClubModel(this.id, this.name, this.country, this.shieldURL);
|
||||
return new ClubModel(this.id, this.name, this.country, this.shieldURL, this.contact, this.training_location,
|
||||
this.training_day_time, this.contact_intern, this.RNA, this.SIRET, this.no_affiliation,
|
||||
this.international);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
package fr.titionfire.ffsaf.domain.entity;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CombModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class CombEntity {
|
||||
private long id;
|
||||
private String lname = "";
|
||||
private String fname = "";
|
||||
private Categorie categorie;
|
||||
private ClubEntity club;
|
||||
private Genre genre;
|
||||
private int licence;
|
||||
private String country;
|
||||
|
||||
public static CombEntity fromModel(CombModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
|
||||
(model.getClub() == null) ? null : ClubEntity.fromModel(model.getClub()),
|
||||
model.getGenre(), model.getLicence(), model.getCountry());
|
||||
}
|
||||
|
||||
public static String getFullName(CombModel model) {
|
||||
return model.getFname() + " " + model.getLname();
|
||||
}
|
||||
public String getFullName() {
|
||||
return this.fname + " " + this.lname;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package fr.titionfire.ffsaf.domain.entity;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import fr.titionfire.ffsaf.utils.GradeArbitrage;
|
||||
import fr.titionfire.ffsaf.utils.RoleAsso;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class MembreEntity {
|
||||
private long id;
|
||||
private String lname = "";
|
||||
private String fname = "";
|
||||
private Categorie categorie;
|
||||
private ClubEntity club;
|
||||
private Genre genre;
|
||||
private int licence;
|
||||
private String country;
|
||||
private Date birth_date;
|
||||
private String email;
|
||||
private RoleAsso role;
|
||||
private GradeArbitrage grade_arbitrage;
|
||||
private String url_photo;
|
||||
|
||||
|
||||
public static MembreEntity fromModel(MembreModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new MembreEntityBuilder()
|
||||
.id(model.getId())
|
||||
.lname(model.getLname())
|
||||
.fname(model.getFname())
|
||||
.categorie(model.getCategorie())
|
||||
.club(ClubEntity.fromModel(model.getClub()))
|
||||
.genre(model.getGenre())
|
||||
.licence(model.getLicence())
|
||||
.country(model.getCountry())
|
||||
.birth_date(model.getBirth_date())
|
||||
.email(model.getEmail())
|
||||
.role(model.getRole())
|
||||
.grade_arbitrage(model.getGrade_arbitrage())
|
||||
.url_photo(model.getUrl_photo())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static String getFullName(MembreModel model) {
|
||||
return model.getFname() + " " + model.getLname();
|
||||
}
|
||||
public String getFullName() {
|
||||
return this.fname + " " + this.lname;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.vertx.VertxContextSupport;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
public class ClubService {
|
||||
|
||||
@Inject
|
||||
ClubRepository repository;
|
||||
|
||||
public SimpleClubModel findByIdOptionalClub(long id) throws Throwable {
|
||||
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
|
||||
}
|
||||
|
||||
public Collection<SimpleClubModel> findAllClub() throws Throwable {
|
||||
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(
|
||||
() -> repository.findAll().list().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
|
||||
}
|
||||
|
||||
public Uni<List<ClubModel>> getAll() {
|
||||
return repository.listAll();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
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.net2.data.SimpleCombModel;
|
||||
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
|
||||
import fr.titionfire.ffsaf.utils.Pair;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import io.quarkus.vertx.VertxContextSupport;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
public class MembreService {
|
||||
|
||||
@Inject
|
||||
CombRepository repository;
|
||||
@Inject
|
||||
ClubRepository clubRepository;
|
||||
|
||||
public SimpleCombModel find(int licence, String np) throws Throwable {
|
||||
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() ->
|
||||
repository.find("licence = ?1 AND (lname = ?2 OR fname = ?2)", licence, np).firstResult().map(SimpleCombModel::fromModel)));
|
||||
}
|
||||
|
||||
public SimpleCombModel findByIdOptionalComb(long id) throws Throwable {
|
||||
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleCombModel::fromModel)));
|
||||
}
|
||||
|
||||
public Uni<List<MembreModel>> getAll() {
|
||||
return repository.listAll(Sort.ascending("fname", "lname"));
|
||||
}
|
||||
|
||||
public Uni<MembreModel> getById(long id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
public Uni<String> update(long id, FullMemberForm membre) {
|
||||
return repository.findById(id)
|
||||
.chain(membreModel -> clubRepository.findById(membre.getClub()).map(club -> new Pair<>(membreModel, club)))
|
||||
.onItem().transformToUni(pair -> {
|
||||
MembreModel m = pair.getKey();
|
||||
m.setFname(membre.getFname());
|
||||
m.setLname(membre.getLname());
|
||||
m.setClub(pair.getValue());
|
||||
m.setCountry(membre.getCountry());
|
||||
m.setBirth_date(membre.getBirth_date());
|
||||
m.setGenre(membre.getGenre());
|
||||
m.setCategorie(membre.getCategorie());
|
||||
m.setRole(membre.getRole());
|
||||
m.setGrade_arbitrage(membre.getGrade_arbitrage());
|
||||
m.setEmail(membre.getEmail());
|
||||
return Panache.withTransaction(() -> repository.persist(m));
|
||||
}).map(__ -> "OK");
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,15 @@
|
||||
package fr.titionfire.ffsaf.net2;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import fr.titionfire.ffsaf.domain.service.ClubService;
|
||||
import fr.titionfire.ffsaf.domain.service.MembreService;
|
||||
import fr.titionfire.ffsaf.net2.packet.IAction;
|
||||
import fr.titionfire.ffsaf.net2.packet.RegisterAction;
|
||||
import fr.titionfire.ffsaf.utils.Pair;
|
||||
import io.quarkus.runtime.ShutdownEvent;
|
||||
import io.quarkus.runtime.StartupEvent;
|
||||
import jakarta.enterprise.event.Observes;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
@ -25,8 +28,6 @@ import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@Singleton
|
||||
public class ServerCustom extends Thread {
|
||||
private static final Logger LOGGER = Logger.getLogger("SocketServer");
|
||||
@ -38,6 +39,8 @@ public class ServerCustom extends Thread {
|
||||
private SSLServerSocket server;
|
||||
private boolean run;
|
||||
|
||||
private static ServerCustom serverCustom;
|
||||
|
||||
@ConfigProperty(name = "internal-port")
|
||||
int port;
|
||||
|
||||
@ -50,6 +53,12 @@ public class ServerCustom extends Thread {
|
||||
@Inject
|
||||
protected Scheduler quartz;
|
||||
|
||||
@Inject
|
||||
public MembreService membreService;
|
||||
|
||||
@Inject
|
||||
public ClubService clubService;
|
||||
|
||||
private PublicKey publicKey;
|
||||
|
||||
private X509TrustManager tm(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {
|
||||
@ -134,6 +143,7 @@ public class ServerCustom extends Thread {
|
||||
|
||||
void onStart(@Observes StartupEvent ev) {
|
||||
LOGGER.info("The server is starting...");
|
||||
serverCustom = this;
|
||||
this.start();
|
||||
}
|
||||
|
||||
@ -142,6 +152,10 @@ public class ServerCustom extends Thread {
|
||||
this.stop2();
|
||||
}
|
||||
|
||||
public static ServerCustom getInstance(){
|
||||
return serverCustom;
|
||||
}
|
||||
|
||||
public Stream<Client_Thread> get_clients() {
|
||||
return clients.stream();
|
||||
}
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package fr.titionfire.ffsaf.net2.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class SimpleClubModel {
|
||||
Long id;
|
||||
String name;
|
||||
String country;
|
||||
String shieldURL;
|
||||
|
||||
public static SimpleClubModel fromModel(ClubModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new SimpleClubModel(model.getId(), model.getName(), model.getCountry(), model.getShieldURL());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package fr.titionfire.ffsaf.net2.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class SimpleCombModel {
|
||||
Long id;
|
||||
String lname = "";
|
||||
String fname = "";
|
||||
Categorie categorie;
|
||||
SimpleClubModel club;
|
||||
Genre genre;
|
||||
int licence = 0;
|
||||
String country = "fr";
|
||||
|
||||
public static SimpleCombModel fromModel(MembreModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new SimpleCombModel(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
|
||||
(model.getClub() == null) ? null : SimpleClubModel.fromModel(model.getClub()),
|
||||
model.getGenre(), model.getLicence(), model.getCountry());
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,9 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import fr.titionfire.ffsaf.net2.Client_Thread;
|
||||
import fr.titionfire.ffsaf.net2.Message;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.vertx.SafeVertxContext;
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import fr.titionfire.ffsaf.net2.Client_Thread;
|
||||
import fr.titionfire.ffsaf.net2.Message;
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
public interface IAction {
|
||||
void dataIn(Client_Thread client_Thread, Message<JsonNode> message) throws JsonProcessingException;
|
||||
|
||||
39
src/main/java/fr/titionfire/ffsaf/net2/packet/RClub.java
Normal file
39
src/main/java/fr/titionfire/ffsaf/net2/packet/RClub.java
Normal file
@ -0,0 +1,39 @@
|
||||
package fr.titionfire.ffsaf.net2.packet;
|
||||
|
||||
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class RClub {
|
||||
private static final Logger LOGGER = Logger.getLogger(RClub.class);
|
||||
|
||||
final CIA<Long> findByIdOptionalClub = new CIA<>(Long.class, (client_Thread, message) -> {
|
||||
try {
|
||||
SimpleClubModel clubModel = ServerCustom.getInstance().clubService.findByIdOptionalClub(message.data());
|
||||
client_Thread.sendRepTo(clubModel, message);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
client_Thread.sendErrTo(e.getMessage(), message);
|
||||
}
|
||||
});
|
||||
|
||||
final IAction findAllClub = (client_Thread, message) -> {
|
||||
try {
|
||||
Collection<SimpleClubModel> clubModels = ServerCustom.getInstance().clubService.findAllClub();
|
||||
client_Thread.sendRepTo(clubModels, message);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
client_Thread.sendErrTo(e.getMessage(), message);
|
||||
}
|
||||
};
|
||||
|
||||
public static void register(HashMap<String, IAction> iMap) {
|
||||
RClub rClub = new RClub();
|
||||
|
||||
iMap.put("findByIdOptionalClub", rClub.findByIdOptionalClub);
|
||||
iMap.put("findAllClub", rClub.findAllClub);
|
||||
}
|
||||
}
|
||||
40
src/main/java/fr/titionfire/ffsaf/net2/packet/RComb.java
Normal file
40
src/main/java/fr/titionfire/ffsaf/net2/packet/RComb.java
Normal file
@ -0,0 +1,40 @@
|
||||
package fr.titionfire.ffsaf.net2.packet;
|
||||
|
||||
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@ApplicationScoped
|
||||
public class RComb {
|
||||
private static final Logger LOGGER = Logger.getLogger(RComb.class);
|
||||
|
||||
final IAction findComb = (client_Thread, message) -> {
|
||||
try {
|
||||
SimpleCombModel combModel = ServerCustom.getInstance().membreService.find(message.data().get("licence").asInt(), message.data().get("np").asText());
|
||||
client_Thread.sendRepTo(combModel, message);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
client_Thread.sendErrTo(e.getMessage(), message);
|
||||
}
|
||||
};
|
||||
|
||||
final CIA<Long> findByIdOptionalComb = new CIA<>(Long.class, (client_Thread, message) -> {
|
||||
try {
|
||||
SimpleCombModel combModel = ServerCustom.getInstance().membreService.findByIdOptionalComb(message.data());
|
||||
client_Thread.sendRepTo(combModel, message);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
client_Thread.sendErrTo(e.getMessage(), message);
|
||||
}
|
||||
});
|
||||
|
||||
public static void register(HashMap<String, IAction> iMap) {
|
||||
RComb rComb = new RComb();
|
||||
|
||||
iMap.put("findComb", rComb.findComb);
|
||||
iMap.put("findByIdOptionalComb", rComb.findByIdOptionalComb);
|
||||
}
|
||||
}
|
||||
@ -7,5 +7,7 @@ public class RegisterAction {
|
||||
public static void register(HashMap<String, IAction> iMap) {
|
||||
iMap.clear();
|
||||
|
||||
RComb.register(iMap);
|
||||
RClub.register(iMap);
|
||||
}
|
||||
}
|
||||
|
||||
45
src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java
Normal file
45
src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java
Normal file
@ -0,0 +1,45 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@Path("/auth")
|
||||
public class AuthEndpoints {
|
||||
|
||||
@ConfigProperty(name = "login_redirect")
|
||||
String redirect;
|
||||
|
||||
@Inject
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Boolean auth() {
|
||||
return !securityIdentity.isAnonymous();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/userinfo")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String userinfo() {
|
||||
return securityIdentity.getPrincipal().getName();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/login")
|
||||
@Authenticated
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public Response login() throws URISyntaxException {
|
||||
return Response.temporaryRedirect(new URI(redirect)).build();
|
||||
}
|
||||
}
|
||||
28
src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java
Normal file
28
src/main/java/fr/titionfire/ffsaf/rest/ClubEndpoints.java
Normal file
@ -0,0 +1,28 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.ClubService;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Path("/club")
|
||||
public class ClubEndpoints {
|
||||
|
||||
@Inject
|
||||
ClubService clubService;
|
||||
|
||||
@GET
|
||||
@Path("/no_detail")
|
||||
@RolesAllowed("federation_admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<List<SimpleClubModel>> getAll() {
|
||||
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
|
||||
}
|
||||
}
|
||||
125
src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java
Normal file
125
src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java
Normal file
@ -0,0 +1,125 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.MembreService;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
|
||||
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
|
||||
import fr.titionfire.ffsaf.utils.Pair;
|
||||
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.HttpHeaders;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.tika.mime.MimeTypeException;
|
||||
import org.apache.tika.mime.MimeTypes;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@Path("/member")
|
||||
public class CombEndpoints {
|
||||
|
||||
@Inject
|
||||
MembreService membreService;
|
||||
|
||||
@ConfigProperty(name = "upload_dir")
|
||||
String media;
|
||||
|
||||
@GET
|
||||
@Path("/all")
|
||||
@RolesAllowed("federation_admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<List<SimpleMembre>> getAll() {
|
||||
return membreService.getAll().map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList());
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed("federation_admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<SimpleMembre> getById(@PathParam("id") long id) {
|
||||
return membreService.getById(id).map(SimpleMembre::fromModel);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}")
|
||||
@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()))) {
|
||||
String mimeType = URLConnection.guessContentTypeFromStream(is);
|
||||
String extension = MimeTypes.getDefaultMimeTypes().forName(mimeType).getExtension();
|
||||
FileUtils.writeByteArrayToFile(new File(media, "ppMembre/" + input.getId() + extension), input.getPhoto_data());
|
||||
return "OK";
|
||||
} catch (IOException | MimeTypeException 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")
|
||||
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));
|
||||
File[] files = new File(media, "ppMembre").listFiles(filter);
|
||||
if (files != null && files.length > 0) {
|
||||
File file = files[0];
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(file.toPath());
|
||||
return new Pair<>(file, data);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
URI uri = new URI("https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-chat/ava2.webp");
|
||||
|
||||
return Uni.createFrom().future(future)
|
||||
.map(filePair -> {
|
||||
if (filePair == null)
|
||||
return Response.temporaryRedirect(uri).build();
|
||||
|
||||
String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName());
|
||||
|
||||
Response.ResponseBuilder resp = Response.ok(filePair.getValue());
|
||||
resp.type(MediaType.APPLICATION_OCTET_STREAM);
|
||||
resp.header(HttpHeaders.CONTENT_LENGTH, filePair.getValue().length);
|
||||
resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
|
||||
resp.header(HttpHeaders.CONTENT_DISPOSITION, "inline; ");
|
||||
|
||||
return resp.build();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package fr.titionfire.ffsaf.rest.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import fr.titionfire.ffsaf.utils.GradeArbitrage;
|
||||
import fr.titionfire.ffsaf.utils.RoleAsso;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class SimpleMembre {
|
||||
private long id;
|
||||
private String lname = "";
|
||||
private String fname = "";
|
||||
private Categorie categorie;
|
||||
private SimpleClubModel club;
|
||||
private Genre genre;
|
||||
private int licence;
|
||||
private String country;
|
||||
private Date birth_date;
|
||||
private String email;
|
||||
private RoleAsso role;
|
||||
private GradeArbitrage grade_arbitrage;
|
||||
private String url_photo;
|
||||
|
||||
public static SimpleMembre fromModel(MembreModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new SimpleMembreBuilder()
|
||||
.id(model.getId())
|
||||
.lname(model.getLname())
|
||||
.fname(model.getFname())
|
||||
.categorie(model.getCategorie())
|
||||
.club(SimpleClubModel.fromModel(model.getClub()))
|
||||
.genre(model.getGenre())
|
||||
.licence(model.getLicence())
|
||||
.country(model.getCountry())
|
||||
.birth_date(model.getBirth_date())
|
||||
.email(model.getEmail())
|
||||
.role(model.getRole())
|
||||
.grade_arbitrage(model.getGrade_arbitrage())
|
||||
.url_photo(model.getUrl_photo())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package fr.titionfire.ffsaf.rest.from;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import fr.titionfire.ffsaf.utils.GradeArbitrage;
|
||||
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 FullMemberForm {
|
||||
@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("club")
|
||||
private Long club = null;
|
||||
|
||||
@FormParam("genre")
|
||||
private Genre genre;
|
||||
|
||||
@FormParam("licence")
|
||||
private int licence;
|
||||
|
||||
@FormParam("country")
|
||||
private String country;
|
||||
|
||||
@FormParam("birth_date")
|
||||
private Date birth_date;
|
||||
|
||||
@FormParam("email")
|
||||
private String email;
|
||||
|
||||
@FormParam("role")
|
||||
private RoleAsso role;
|
||||
|
||||
@FormParam("grade_arbitrage")
|
||||
private GradeArbitrage grade_arbitrage;
|
||||
|
||||
@FormParam("photo_data")
|
||||
@PartType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
private byte[] photo_data = new byte[0];
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FullMemberForm{" +
|
||||
"id='" + id + '\'' +
|
||||
", lname='" + lname + '\'' +
|
||||
", fname='" + fname + '\'' +
|
||||
", categorie=" + categorie +
|
||||
", club=" + club +
|
||||
", genre=" + genre +
|
||||
", licence=" + licence +
|
||||
", country='" + country + '\'' +
|
||||
", birth_date=" + birth_date +
|
||||
", email='" + email + '\'' +
|
||||
", role=" + role +
|
||||
", grade_arbitrage=" + grade_arbitrage +
|
||||
", url_photo=" + photo_data.length +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
20
src/main/java/fr/titionfire/ffsaf/utils/Contact.java
Normal file
20
src/main/java/fr/titionfire/ffsaf/utils/Contact.java
Normal file
@ -0,0 +1,20 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
public enum Contact {
|
||||
COURRIEL("Courriel"),
|
||||
TELEPHONE("Téléphone"),
|
||||
SITE("Site web"),
|
||||
FACEBOOK("Facebook"),
|
||||
INSTAGRAM("Instagram");
|
||||
|
||||
public final String name;
|
||||
|
||||
Contact(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
public enum Genre {
|
||||
H, F
|
||||
H, F, NA
|
||||
}
|
||||
|
||||
18
src/main/java/fr/titionfire/ffsaf/utils/GradeArbitrage.java
Normal file
18
src/main/java/fr/titionfire/ffsaf/utils/GradeArbitrage.java
Normal file
@ -0,0 +1,18 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
public enum GradeArbitrage {
|
||||
NA("N/A"),
|
||||
ASSESSEUR("Assesseur"),
|
||||
ARBITRE("Arbitre");
|
||||
|
||||
public final String name;
|
||||
|
||||
GradeArbitrage(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
20
src/main/java/fr/titionfire/ffsaf/utils/RoleAsso.java
Normal file
20
src/main/java/fr/titionfire/ffsaf/utils/RoleAsso.java
Normal file
@ -0,0 +1,20 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
|
||||
public enum RoleAsso {
|
||||
MEMBRE("Membre"),
|
||||
PRESIDENT("Président"),
|
||||
TRESORIER("Trésorier"),
|
||||
SECRETAIRE("Secrétaire");
|
||||
|
||||
public final String name;
|
||||
|
||||
RoleAsso(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,19 @@ quarkus.hibernate-orm.physical-naming-strategy=fr.titionfire.ffsaf.data.SafcaNam
|
||||
quarkus.http.cors=true
|
||||
quarkus.quartz.start-mode=forced
|
||||
|
||||
%dev.quarkus.log.min-level=ALL
|
||||
%dev.quarkus.log.category."fr.titionfire.ffsaf".level=ALL
|
||||
|
||||
quarkus.oidc.auth-server-url=https://auth.safca.fr/auth/realms/safca
|
||||
quarkus.oidc.client-id=backend
|
||||
quarkus.oidc.credentials.secret=secret
|
||||
quarkus.oidc.tls.verification=required
|
||||
#quarkus.oidc.tls.verification=none
|
||||
|
||||
quarkus.http.limits.max-body-size=10M
|
||||
|
||||
quarkus.oidc.token-state-manager.split-tokens=true
|
||||
|
||||
database.prefix = test2_
|
||||
database.database=ffsaf
|
||||
database.hostname=localhost
|
||||
|
||||
@ -6,3 +6,6 @@ Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
|
||||
https://mhnpd.github.io/react-loader-spinner/docs/intro
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Vite + React</title>
|
||||
|
||||
<link href="/index.css" rel="stylesheet">
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
|
||||
@ -15,12 +17,17 @@
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="root">
|
||||
<div class="loader-container">
|
||||
<h1 style="color: #00aff2">Chargement de l'application FFSAF...</h1>
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script async type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
169
src/main/webapp/package-lock.json
generated
169
src/main/webapp/package-lock.json
generated
@ -14,8 +14,10 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-router-dom": "^6.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -378,6 +380,24 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
|
||||
"integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
|
||||
"integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
|
||||
@ -1258,6 +1278,11 @@
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw=="
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
@ -1501,6 +1526,14 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/browser-image-compression": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/browser-image-compression/-/browser-image-compression-2.0.2.tgz",
|
||||
"integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==",
|
||||
"dependencies": {
|
||||
"uzip": "0.20201231.0"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
@ -1556,6 +1589,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001576",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz",
|
||||
@ -1642,6 +1683,24 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@ -3140,7 +3199,6 @@
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -3375,8 +3433,7 @@
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.33",
|
||||
@ -3406,6 +3463,11 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@ -3487,6 +3549,27 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-loader-spinner": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz",
|
||||
"integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==",
|
||||
"dependencies": {
|
||||
"react-is": "^18.2.0",
|
||||
"styled-components": "^6.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-loader-spinner/node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||
@ -3750,6 +3833,11 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@ -3789,7 +3877,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -3883,6 +3970,70 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components": {
|
||||
"version": "6.1.8",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz",
|
||||
"integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==",
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "1.2.1",
|
||||
"@emotion/unitless": "0.8.0",
|
||||
"@types/stylis": "4.2.0",
|
||||
"css-to-react-native": "3.2.0",
|
||||
"csstype": "3.1.2",
|
||||
"postcss": "8.4.31",
|
||||
"shallowequal": "1.1.0",
|
||||
"stylis": "4.3.1",
|
||||
"tslib": "2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/styled-components"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0",
|
||||
"react-dom": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||
},
|
||||
"node_modules/styled-components/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz",
|
||||
"integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ=="
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@ -3922,6 +4073,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@ -4065,6 +4221,11 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uzip": {
|
||||
"version": "0.20201231.0",
|
||||
"resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
|
||||
"integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.0.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz",
|
||||
|
||||
@ -16,8 +16,10 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-router-dom": "^6.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
33
src/main/webapp/public/index.css
Normal file
33
src/main/webapp/public/index.css
Normal file
@ -0,0 +1,33 @@
|
||||
:root {
|
||||
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 0.834);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border: 8px solid;
|
||||
border-color: #3d5af1 transparent #3d5af1 transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin-anim 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin-anim {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@ -3,10 +3,11 @@ import './App.css'
|
||||
import {Nav} from "./components/Nav.jsx";
|
||||
import {createBrowserRouter, Outlet, RouterProvider, useRouteError} from "react-router-dom";
|
||||
import {Home} from "./pages/Homepage.jsx";
|
||||
import {AdminRoot} from "./pages/admin/AdminRoot.jsx";
|
||||
import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx";
|
||||
import {AuthCallback} from "./components/auhCallback.jsx";
|
||||
import {useAuthDispatch} from "./hooks/useAuth.jsx";
|
||||
import {KeycloakContextProvider, useAuthDispatch} from "./hooks/useAuth.jsx";
|
||||
import {check_validity} from "./utils/auth.js";
|
||||
import {MemberList} from "./pages/admin/MemberList.jsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -21,16 +22,7 @@ const router = createBrowserRouter([
|
||||
{
|
||||
path: 'admin',
|
||||
element: <AdminRoot/>,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <div>Admin</div>
|
||||
},
|
||||
{
|
||||
path: 'b',
|
||||
element: <div>Admin B</div>
|
||||
}
|
||||
]
|
||||
children: getAdminChildren()
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -51,6 +43,16 @@ function PageError() {
|
||||
}
|
||||
|
||||
function Root() {
|
||||
const dispatch = useAuthDispatch()
|
||||
const isInit = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isInit.current)
|
||||
return;
|
||||
isInit.current = true
|
||||
check_validity(b => dispatch({type: 'init', val: b}))
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<header>
|
||||
<Nav/>
|
||||
@ -64,19 +66,9 @@ function Root() {
|
||||
function App() {
|
||||
console.log('render')
|
||||
|
||||
const dispatch = useAuthDispatch()
|
||||
const isInit = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isInit.current)
|
||||
return;
|
||||
isInit.current = true
|
||||
check_validity(b => dispatch({type: 'init', val: b}))
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return <RouterProvider router={router}/>;
|
||||
return <KeycloakContextProvider>
|
||||
<RouterProvider router={router}/>
|
||||
</KeycloakContextProvider>;
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
17
src/main/webapp/src/api/membre.js
Normal file
17
src/main/webapp/src/api/membre.js
Normal file
@ -0,0 +1,17 @@
|
||||
import axios from "axios";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function getAllMembre() {
|
||||
return axios.get(`${api_url}/membre/all`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`
|
||||
}
|
||||
}).then(data => {
|
||||
console.log(data.data)
|
||||
return data.data;
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
return null;
|
||||
})
|
||||
}
|
||||
3
src/main/webapp/src/components/AxiosError.jsx
Normal file
3
src/main/webapp/src/components/AxiosError.jsx
Normal file
@ -0,0 +1,3 @@
|
||||
export function AxiosError(data) {
|
||||
return <span>{data.error.status} - {data.error.statusText}</span>
|
||||
}
|
||||
44
src/main/webapp/src/components/ClubSelect.jsx
Normal file
44
src/main/webapp/src/components/ClubSelect.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
import {LoadingContextProvider, useLoadingSwitcher} from "../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../hooks/useFetch.js";
|
||||
import {AxiosError} from "./AxiosError.jsx";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function ClubSelect({defaultValue, name}) {
|
||||
return <LoadingContextProvider>
|
||||
<div className="input-group mb-3">
|
||||
<ClubSelect_ defaultValue={defaultValue} name={name}/>
|
||||
</div>
|
||||
</LoadingContextProvider>
|
||||
}
|
||||
|
||||
function ClubSelect_({defaultValue, name}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`${api_url}/club/no_detail`, setLoading, 1)
|
||||
|
||||
return <>
|
||||
{data
|
||||
? <div className="input-group mb-3">
|
||||
<label className="input-group-text" id="inputGroupSelect02">Club</label>
|
||||
<select className="form-select" id="inputGroupSelect02"
|
||||
defaultValue={defaultValue} name={name}>
|
||||
<option>Sélectionner...</option>
|
||||
{data.map(club => (<option key={club.id} value={club.id}>{club.name}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="input-group mb-3">
|
||||
<label className="input-group-text" id="inputGroupSelect02">Club</label>
|
||||
<select className="form-select" id="inputGroupSelect02"
|
||||
defaultValue="Chargement...">
|
||||
<option>Chargement...</option>
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
11
src/main/webapp/src/components/Input.jsx
Normal file
11
src/main/webapp/src/components/Input.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
export function Input({placeholder, value, onChange}) {
|
||||
return <div>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@ -2,12 +2,10 @@ import LogoIcon from '../assets/FFSSAF-bord-blanc-fond-transparent.webp'
|
||||
import './Nav.css'
|
||||
import {NavLink} from "react-router-dom";
|
||||
import {useAuth, useAuthDispatch} from "../hooks/useAuth.jsx";
|
||||
import {logout, redirect_auth_page} from "../utils/auth.js";
|
||||
import {login, logout} from "../utils/auth.js";
|
||||
|
||||
|
||||
export function Nav() {
|
||||
const {token, refresh, is_authenticated} = useAuth()
|
||||
const dispatch = useAuthDispatch()
|
||||
|
||||
return <nav className="navbar navbar-light navbar-expand-md bg-body-tertiary " id="main-navbar">
|
||||
<div className="container-fluid">
|
||||
@ -25,20 +23,43 @@ export function Nav() {
|
||||
<div className="collapse-item">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/">Accueil</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin">Administration</NavLink></li>
|
||||
|
||||
<li className="nav-item">
|
||||
{!is_authenticated ? (
|
||||
<div className="nav-link" onClick={() => redirect_auth_page()}>Login</div>
|
||||
) : (
|
||||
<div className="nav-link" onClick={() => {
|
||||
logout(token, refresh).then(() => dispatch({type: 'invalidate'}))
|
||||
}}>Logout</div>
|
||||
)}
|
||||
</li>
|
||||
<AdminMenu/>
|
||||
<LoginMenu/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
|
||||
function AdminMenu() {
|
||||
const {is_authenticated, data} = useAuth()
|
||||
|
||||
if (!is_authenticated || !data?.realm_access?.roles?.includes("federation_admin"))
|
||||
return <></>
|
||||
|
||||
return <li className="nav-item dropdown">
|
||||
<div className="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
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/b">B</NavLink></li>
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
|
||||
function LoginMenu() {
|
||||
const {token, refresh, is_authenticated} = useAuth()
|
||||
const dispatch = useAuthDispatch()
|
||||
|
||||
return <li className="nav-item">
|
||||
{!is_authenticated ? (
|
||||
<div className="nav-link" onClick={() => login()}>Connexion</div>
|
||||
) : (
|
||||
<div className="nav-link" onClick={() => {
|
||||
logout(token, refresh).then(() => dispatch({type: 'invalidate'}))
|
||||
}}>Déconnexion</div>
|
||||
)}
|
||||
</li>
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import {sendTokenRequestPart2} from "../utils/auth.js";
|
||||
import {login_redirect} from "../utils/auth.js";
|
||||
import {useEffect, useRef} from "react";
|
||||
|
||||
export const AuthCallback = () => {
|
||||
@ -8,8 +8,7 @@ export const AuthCallback = () => {
|
||||
if (isInit.current)
|
||||
return;
|
||||
isInit.current = true
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
sendTokenRequestPart2(params.get('code'), params.get('state'));
|
||||
login_redirect();
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {createContext, useContext, useEffect, useReducer} from "react";
|
||||
import {sendTokenRequest} from "../utils/auth.js";
|
||||
|
||||
const AuthContext = createContext(undefined);
|
||||
const AuthDispatchContext = createContext(null);
|
||||
@ -15,23 +14,6 @@ export function useAuthDispatch() {
|
||||
export function KeycloakContextProvider({children}) {
|
||||
const [auth, dispatch] = useReducer(authReducer, initialAuth);
|
||||
|
||||
useEffect(() => {
|
||||
if (auth.token === undefined || !auth.is_authenticated)
|
||||
return;
|
||||
const jwt = JSON.parse(atob(auth.token.split('.')[1]));
|
||||
|
||||
const ref = setTimeout(() => {
|
||||
if (auth.refresh !== undefined && auth.refresh !== null)
|
||||
sendTokenRequest(auth.refresh).then(data => dispatch({
|
||||
type: 'update',
|
||||
token: data.access_token,
|
||||
refresh: data.refresh_token,
|
||||
})
|
||||
).catch(() => dispatch({type: 'invalidate'}));
|
||||
}, jwt.exp * 1000 - new Date().getTime() - 1000)
|
||||
return () => clearTimeout(ref);
|
||||
}, [auth])
|
||||
|
||||
return <AuthContext.Provider value={auth}>
|
||||
<AuthDispatchContext.Provider value={dispatch}>
|
||||
{children}
|
||||
@ -61,8 +43,6 @@ function authReducer(auth, action) {
|
||||
case 'invalidate': {
|
||||
return {
|
||||
...auth,
|
||||
token: undefined,
|
||||
refresh: undefined,
|
||||
is_authenticated: false
|
||||
}
|
||||
}
|
||||
@ -73,8 +53,6 @@ function authReducer(auth, action) {
|
||||
}
|
||||
|
||||
const initialAuth = {
|
||||
token: undefined,
|
||||
refresh: undefined,
|
||||
is_authenticated: undefined,
|
||||
data: undefined,
|
||||
}
|
||||
56
src/main/webapp/src/hooks/useFetch.js
Normal file
56
src/main/webapp/src/hooks/useFetch.js
Normal file
@ -0,0 +1,56 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import axios from "axios";
|
||||
|
||||
function stdAction(promise, setData, setErrors, setLoading = null, loadingLevel = 1) {
|
||||
if (setLoading)
|
||||
setLoading(loadingLevel)
|
||||
|
||||
promise.then(data => {
|
||||
setData(data.data)
|
||||
}).catch(e => {
|
||||
setErrors(e.response)
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
export function useFetch(url, setLoading = null, loadingLevel = 1, options = {}) {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setErrors] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
stdAction(axios.get(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`,
|
||||
'Accept': 'application/json; charset=UTF-8',
|
||||
...options?.headers
|
||||
}
|
||||
}), setData, setErrors, setLoading, loadingLevel)
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data, error
|
||||
}
|
||||
}
|
||||
|
||||
export function useFetchPut(url, dataRequest, setLoading = null, loadingLevel = 1, options = {}) {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setErrors] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
stdAction(axios.put(url, dataRequest, {
|
||||
...options,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`,
|
||||
'Accept': 'application/json; charset=UTF-8',
|
||||
...options?.headers
|
||||
}
|
||||
}), setData, setErrors, setLoading, loadingLevel)
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data, error
|
||||
}
|
||||
}
|
||||
22
src/main/webapp/src/hooks/useLoading.css
Normal file
22
src/main/webapp/src/hooks/useLoading.css
Normal file
@ -0,0 +1,22 @@
|
||||
.overlayBG {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overlayContent {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
cursor: default;
|
||||
z-index: 100;
|
||||
}
|
||||
43
src/main/webapp/src/hooks/useLoading.jsx
Normal file
43
src/main/webapp/src/hooks/useLoading.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import {createContext, useContext, useState} from "react";
|
||||
import './useLoading.css'
|
||||
import {FallingLines} from "react-loader-spinner";
|
||||
|
||||
const LoadingContext = createContext(0);
|
||||
const LoadingSwitcherContext = createContext(undefined);
|
||||
|
||||
export function useLoading() {
|
||||
return useContext(LoadingContext);
|
||||
}
|
||||
|
||||
export function useLoadingSwitcher() {
|
||||
return useContext(LoadingSwitcherContext);
|
||||
}
|
||||
|
||||
export function LoadingContextProvider({children}) {
|
||||
const [showOverlay, setOverlay] = useState(0);
|
||||
|
||||
return <LoadingContext.Provider value={showOverlay}>
|
||||
<LoadingSwitcherContext.Provider value={v => setOverlay(v)}>
|
||||
<div style={{position: 'relative'}}>
|
||||
{children}
|
||||
<LoadingOverLay/>
|
||||
</div>
|
||||
</LoadingSwitcherContext.Provider>
|
||||
</LoadingContext.Provider>
|
||||
}
|
||||
|
||||
function LoadingOverLay() {
|
||||
const showOverlay = useLoading()
|
||||
|
||||
if (showOverlay) {
|
||||
return <div className="overlayBG" style={{position: showOverlay === 1 ? 'absolute' : 'fixed'}}>
|
||||
<div className="overlayContent" onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
<FallingLines/>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
:root {
|
||||
|
||||
}
|
||||
@ -1,13 +1,9 @@
|
||||
import React from 'react'
|
||||
import React, {lazy, Suspense} from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
import {KeycloakContextProvider} from "./hooks/useAuth.jsx";
|
||||
import App from "./App.jsx";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<KeycloakContextProvider>
|
||||
<App/>
|
||||
</KeycloakContextProvider>
|
||||
<App/>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
#admin-navbar {
|
||||
background: #eeeeee;
|
||||
|
||||
.nav-link{
|
||||
color: #00aff2;
|
||||
}
|
||||
.nav-link.active{
|
||||
color: #004969;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #00aff2;
|
||||
}
|
||||
}
|
||||
@ -1,29 +1,31 @@
|
||||
import {NavLink, Outlet} from "react-router-dom";
|
||||
import './AdminRoot.css'
|
||||
import {LoadingContextProvider} from "../../hooks/useLoading.jsx";
|
||||
import {MemberList} from "./MemberList.jsx";
|
||||
import {MemberPage} from "./MemberPage.jsx";
|
||||
|
||||
export function AdminRoot() {
|
||||
return <>
|
||||
<NavAdmin/>
|
||||
<Outlet/>
|
||||
<h1>Espace administration</h1>
|
||||
<LoadingContextProvider>
|
||||
<Outlet/>
|
||||
</LoadingContextProvider>
|
||||
</>
|
||||
}
|
||||
|
||||
function NavAdmin() {
|
||||
return <nav className="navbar navbar-expand-md bg-body-tertiary " id="admin-navbar">
|
||||
<div className="container-fluid">
|
||||
<button className="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#adminNavbarNavDropdown" aria-controls="adminNavbarNavDropdown"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div className="collapse navbar-collapse" id="adminNavbarNavDropdown">
|
||||
<div className="collapse-item">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin">A</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin/b">B</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
export function getAdminChildren () {
|
||||
return [
|
||||
{
|
||||
path: 'member',
|
||||
element: <MemberList/>
|
||||
},
|
||||
{
|
||||
path: 'member/:id',
|
||||
element: <MemberPage/>
|
||||
},
|
||||
{
|
||||
path: 'b',
|
||||
element: <div>Admin B</div>
|
||||
}
|
||||
]
|
||||
}
|
||||
62
src/main/webapp/src/pages/admin/MemberList.jsx
Normal file
62
src/main/webapp/src/pages/admin/MemberList.jsx
Normal file
@ -0,0 +1,62 @@
|
||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {useState} from "react";
|
||||
import {Input} from "../../components/Input.jsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
const removeDiacritics = str => {
|
||||
return str
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
}
|
||||
|
||||
export function MemberList() {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`${api_url}/member/all`, setLoading, 1)
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const visibleMember = data ? data.filter(member => {
|
||||
const lo = removeDiacritics(searchInput).toLowerCase()
|
||||
return !searchInput
|
||||
|| (removeDiacritics(member.fname).toLowerCase().startsWith(lo)
|
||||
|| removeDiacritics(member.lname).toLowerCase().startsWith(lo));
|
||||
}) : [];
|
||||
|
||||
return <>
|
||||
<SearchBar searchInput={searchInput} onSearchInputChange={setSearchInput}/>
|
||||
{data
|
||||
? <div className="list-group">
|
||||
{visibleMember.map(member => (
|
||||
<span key={member.id}
|
||||
onClick={() => navigate("/admin/member/" + member.id)}
|
||||
className="list-group-item list-group-item-action">{member.fname} {member.lname}</span>))}
|
||||
</div>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function SearchBar({searchInput, onSearchInputChange}) {
|
||||
return <div>
|
||||
<div className="mb-3">
|
||||
<Input value={searchInput} onChange={onSearchInputChange} placeholder="Rechercher..."/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="list-group">
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
</div>
|
||||
}
|
||||
228
src/main/webapp/src/pages/admin/MemberPage.jsx
Normal file
228
src/main/webapp/src/pages/admin/MemberPage.jsx
Normal file
@ -0,0 +1,228 @@
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch, useFetchPut} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import {getCategoryFormBirthDate, getSaison} from "../../utils/Tools.js";
|
||||
import axios from "axios";
|
||||
import imageCompression from "browser-image-compression";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function MemberPage() {
|
||||
const {id} = useParams()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`${api_url}/member/${id}`, setLoading, 1)
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
setLoading(1)
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("id", data.id);
|
||||
formData.append("lname", event.target.lname?.value);
|
||||
formData.append("fname", event.target.fname?.value);
|
||||
formData.append("categorie", event.target.category?.value);
|
||||
formData.append("club", event.target.club?.value);
|
||||
formData.append("genre", event.target.genre?.value);
|
||||
formData.append("country", event.target.country?.value);
|
||||
formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString());
|
||||
formData.append("email", event.target.email?.value);
|
||||
formData.append("role", event.target.role?.value);
|
||||
formData.append("grade_arbitrage", event.target.grade_arbitrage?.value);
|
||||
|
||||
const send = (formData_) => {
|
||||
axios.post(`${api_url}/member/${id}`, formData_, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`,
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
}).then(data => {
|
||||
console.log(data.data)
|
||||
}).catch(e => {
|
||||
console.log(e.response)
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
const imageFile = event.target.url_photo.files[0];
|
||||
if (imageFile) {
|
||||
console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`);
|
||||
|
||||
const options = {
|
||||
maxSizeMB: 1,
|
||||
maxWidthOrHeight: 1920,
|
||||
useWebWorker: true,
|
||||
}
|
||||
|
||||
imageCompression(imageFile, options).then(compressedFile => {
|
||||
console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB
|
||||
formData.append("photo_data", compressedFile)
|
||||
send(formData)
|
||||
});
|
||||
} else {
|
||||
send(formData)
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Page membre</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
||||
<< retour
|
||||
</button>
|
||||
{data
|
||||
? <MemberForm data={data} handleSubmit={handleSubmit}/>
|
||||
: error && <AxiosError error={error}/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
function MemberForm({data, handleSubmit}) {
|
||||
return <div>
|
||||
<div className="row">
|
||||
<div className="col-lg-4">
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Licence n°{data.licence}</div>
|
||||
<div className="card-body text-center">
|
||||
<div className="input-group mb-3">
|
||||
<img
|
||||
src={"http://localhost:8080/member/"+data.id+"/photo"}
|
||||
alt="avatar"
|
||||
className="rounded-circle img-fluid" style={{object_fit: 'contain'}}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-8">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Information</div>
|
||||
<div className="card-body">
|
||||
<TextField name="lname" text="Nom" value={data.lname}/>
|
||||
<TextField name="fname" text="Prénom" value={data.fname}/>
|
||||
<TextField name="email" text="Email" value={data.email} placeholder="name@example.com"
|
||||
type="email"/>
|
||||
<OptionField name="genre" text="Genre" value={data.genre}
|
||||
values={{NA: 'N/A', H: 'H', F: 'F'}}/>
|
||||
<OptionField name="country" text="Pays" value={data.country}
|
||||
values={{NA: 'Sélectionner...', fr: 'FR', es: 'ES', be: 'BE'}}/>
|
||||
<BirthDayField inti_date={data.birth_date ? data.birth_date.split('T')[0] : ''}
|
||||
inti_category={data.categorie}/>
|
||||
<div className="row">
|
||||
<ClubSelect defaultValue={data?.club?.id} name="club"/>
|
||||
</div>
|
||||
<OptionField name="grade_arbitrage" text="Rôle" value={data.role}
|
||||
values={{NA: 'N/A', ASSESSEUR: 'Assesseur', ARBITRE: 'Arbitre'}}/>
|
||||
<OptionField name="role" text="Grade d'arbitrage" value={data.grade_arbitrage}
|
||||
values={{
|
||||
MEMBRE: 'Membre',
|
||||
PRESIDENT: 'Président',
|
||||
TRESORIER: 'Trésorier',
|
||||
SECRETAIRE: 'Secrétaire'
|
||||
}}/>
|
||||
<div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<label className="input-group-text" htmlFor="url_photo">Photos
|
||||
(optionnelle)</label>
|
||||
<input type="file" className="form-control" id="url_photo" name="url_photo"
|
||||
accept=".jpg,.jpeg,.gif,.png,.svg"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12 text-right">
|
||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<div className="card mb-4 mb-md-0">
|
||||
<div className="card-header">Licence</div>
|
||||
<div className="card-body">
|
||||
<p className="mb-1">Web Design</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="card mb-4 mb-md-0">
|
||||
<div className="card-header">Sélection en équipe de France</div>
|
||||
<div className="card-body">
|
||||
<p className="mb-1">Web Design</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function BirthDayField({inti_date, inti_category}) {
|
||||
const [date, setDate] = useState(inti_date)
|
||||
const [category, setCategory] = useState(inti_category)
|
||||
const [canUpdate, setCanUpdate] = useState(false)
|
||||
useEffect(() => {
|
||||
const b = category !== getCategoryFormBirthDate(new Date(date), new Date('2023-09-01'))
|
||||
if (b !== canUpdate)
|
||||
setCanUpdate(b)
|
||||
}, [date, category])
|
||||
|
||||
const updateCat = (e) => {
|
||||
console.log(date)
|
||||
setCategory(getCategoryFormBirthDate(new Date(date), new Date('2023-09-01')))
|
||||
}
|
||||
|
||||
|
||||
return <>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="birth_date">Date de naissance</span>
|
||||
<input type="date" className="form-control" placeholder="jj/mm/aaaa" aria-label="birth_date"
|
||||
name="birth_date" aria-describedby="birth_date" defaultValue={date} required
|
||||
onChange={(e) => setDate(e.target.value)}/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="category">Catégorie</span>
|
||||
<input type="text" className="form-control" placeholder="" name="category"
|
||||
aria-label="category" value={category} aria-describedby="category"
|
||||
disabled/>
|
||||
{canUpdate && <button className="btn btn-outline-secondary" type="button" id="button-addon1"
|
||||
onClick={updateCat}>Mettre à jours</button>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function OptionField({name, text, values, value}) {
|
||||
return <div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<label className="input-group-text" id={name}>{text}</label>
|
||||
<select className="form-select" id={name} name={name} defaultValue={value} required>
|
||||
{Object.keys(values).map((key, _) => {
|
||||
return (<option key={key} value={key}>{values[key]}</option>)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function TextField({name, text, value, placeholder, type = "text"}) {
|
||||
return <div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id={name}>{text}</span>
|
||||
<input type={type} className="form-control" placeholder={placeholder ? placeholder : text} aria-label={name}
|
||||
name={name} aria-describedby={name} defaultValue={value} required/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
37
src/main/webapp/src/utils/Tools.js
Normal file
37
src/main/webapp/src/utils/Tools.js
Normal file
@ -0,0 +1,37 @@
|
||||
export function getCategoryFormBirthDate(birth_date, currentDate = new Date()) {
|
||||
const currentSaison = getSaison(currentDate)
|
||||
const birthYear = birth_date.getFullYear()
|
||||
|
||||
const diff = currentSaison - birthYear;
|
||||
if (diff < 6) {
|
||||
return "SUPER_MINI";
|
||||
} else if (diff < 8) {
|
||||
return "MINI_POUSSIN";
|
||||
} else if (diff < 10) {
|
||||
return "POUSSIN";
|
||||
} else if (diff < 12) {
|
||||
return "BENJAMIN";
|
||||
} else if (diff < 14) {
|
||||
return "MINIME";
|
||||
} else if (diff < 16) {
|
||||
return "CADET";
|
||||
} else if (diff < 18) {
|
||||
return "JUNIOR";
|
||||
} else if (diff < 24) {
|
||||
return "SENIOR1";
|
||||
} else if (diff < 34) {
|
||||
return "SENIOR2";
|
||||
} else if (diff < 44) {
|
||||
return "VETERAN1";
|
||||
} else {
|
||||
return "VETERAN2";
|
||||
}
|
||||
}
|
||||
|
||||
export function getSaison(currentDate = new Date()) {
|
||||
if (currentDate.getMonth() >= 8) { //septembre et plus
|
||||
return currentDate.getFullYear()
|
||||
} else {
|
||||
return currentDate.getFullYear() - 1
|
||||
}
|
||||
}
|
||||
@ -4,93 +4,30 @@ const auth_url = import.meta.env.VITE_AUTH_URL;
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
const client_id = import.meta.env.VITE_CLIENT_ID;
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function check_validity(online_callback = () => {
|
||||
}) {
|
||||
const token = localStorage.getItem("access_token")
|
||||
if (token !== undefined && token !== null) {
|
||||
const jwt = JSON.parse(atob(token.split('.')[1]));
|
||||
|
||||
if (jwt.exp < new Date().getTime() / 1000 + 1)
|
||||
if (online_callback) online_callback(false); else return false;
|
||||
|
||||
if (online_callback) {
|
||||
return axios.get(`${auth_url}/userinfo`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}).then(() => {
|
||||
online_callback(true);
|
||||
}).catch(() => {
|
||||
online_callback(false);
|
||||
})
|
||||
}
|
||||
if (online_callback) online_callback(true); else return true;
|
||||
}
|
||||
|
||||
if (online_callback) online_callback(false); else return false;
|
||||
}
|
||||
|
||||
export function sendTokenRequest(refresh) {
|
||||
return axios.post(`${auth_url}/token`, {
|
||||
client_id: client_id,
|
||||
redirect_uri: `${vite_url}/complete/auth/`,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refresh
|
||||
}, {
|
||||
headers: {
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
}).then(data => {
|
||||
localStorage.setItem("access_token", data.data.access_token);
|
||||
localStorage.setItem("refresh_token", data.data.refresh_token);
|
||||
|
||||
return data.data;
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
alert("Failed get token from refresh");
|
||||
return axios.get(`${api_url}/auth`).then(data => {
|
||||
console.log(data.data)
|
||||
online_callback(data.data);
|
||||
}).catch(() => {
|
||||
online_callback(false);
|
||||
})
|
||||
}
|
||||
|
||||
export function sendTokenRequestPart2(code, state) {
|
||||
axios.post(`${auth_url}/token`, {
|
||||
client_id: client_id,
|
||||
redirect_uri: `${vite_url}/complete/auth/`,
|
||||
code: code,
|
||||
grant_type: 'authorization_code',
|
||||
}, {
|
||||
headers: {
|
||||
"Accept": "application/x-www-form-urlencoded",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
}).then(data => {
|
||||
localStorage.setItem("access_token", data.data.access_token);
|
||||
localStorage.setItem("refresh_token", data.data.refresh_token);
|
||||
|
||||
const next = localStorage.getItem("cb_" + state)
|
||||
if (next) {
|
||||
clearNext()
|
||||
window.location.href = next
|
||||
} else {
|
||||
window.location.href = import.meta.env.VITE_URL
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
// redirect_auth_page()
|
||||
})
|
||||
export function login() {
|
||||
sessionStorage.setItem("next", window.location.href)
|
||||
window.location.href = `${api_url}/auth/login`;
|
||||
}
|
||||
|
||||
function clearNext() {
|
||||
const arr = []; // Array to hold the keys
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
if (localStorage.key(i).substring(0, 3) === 'cb_') {
|
||||
arr.push(localStorage.key(i));
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
localStorage.removeItem(arr[i]);
|
||||
export function login_redirect() {
|
||||
const next = sessionStorage.getItem("next")
|
||||
if (next) {
|
||||
sessionStorage.removeItem("next")
|
||||
window.location.href = next
|
||||
} else {
|
||||
window.location.href = import.meta.env.VITE_URL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user