Compare commits

..

No commits in common. "master" and "Avon1" have entirely different histories.

477 changed files with 1405 additions and 15421 deletions

View File

@ -1,5 +1,5 @@
postgres-data/ *
ffsaf-media/ !target/*-runner
docker-compose.yml !target/*-runner.jar
ffsaf_cle_prive.jks !target/lib/*
prod.env !target/quarkus-app/*

View File

@ -1,97 +0,0 @@
name: Deploy Production Server
# Only run the workflow when a PR is merged on main and closed
on:
pull_request:
types:
- closed
branches:
- 'master'
# Here we check that the PR was correctly merged to main
jobs:
if_merged:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17.0.12'
distribution: 'graalvm'
cache: 'maven'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: src/main/webapp/package-lock.json
- name: Build frontend
run: |
echo "${{ vars.VITE_ENV }}" > src/main/webapp/.env
cd src/main/webapp
npm install
npm run build
cd ../../..
- name: Inject frontend in backend
run: |
rm -rf src/main/resources/META-INF/resources
mkdir -p src/main/resources/META-INF/
mv src/main/webapp/dist src/main/resources/META-INF/resources
- name: Build backend make_pdf tool
run: |
chmod 740 mvnw
cd src/main/pdf_gen
../../../mvnw clean compile assembly:single
cd ../../..
- name: Build backend
run: |
chmod 740 mvnw
./mvnw package -Pnative -DskipTests
- name: Copy runner to vps via scp
uses: appleboy/scp-action@v0.1.7 # Latest in date when creating the workflow
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.SSH_USER }}
port: ${{ secrets.SSH_PORT }}
key: ${{ secrets.SSH_KEY }}
source: "target/*-runner,src/main/resources/cacerts,src/main/docker/Dockerfile.native,docker-compose.yml,.dockerignore,src/main/pdf_gen/target/pdf_gen-*.jar"
target: ${{ secrets.TARGET_DIR }} # Need to create it first on the VPS
- name: Re-start ffsaf container
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.SSH_USER }}
port: ${{ secrets.SSH_PORT }}
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
- name: Check ffsaf container
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.SSH_USER }}
port: ${{ secrets.SSH_PORT }}
key: ${{ secrets.SSH_KEY }}
script: |
if docker ps | grep ffsaf; then
echo 'Container is running'
else
echo 'Container is not running'
exit 1 # This mark the pipeline as failed
fi

2
.gitignore vendored
View File

@ -45,8 +45,6 @@ nb-configuration.xml
# Custom # Custom
/config/application.properties /config/application.properties
/cle_prive.jks /cle_prive.jks
/mail-truststore.p12
/src/main/resources/META-INF/resources/ /src/main/resources/META-INF/resources/
/media/ /media/
/media-ext/ /media-ext/
/sign.jpg

View File

@ -1,22 +1,8 @@
# FFSAF - Intranet # ffsaf-site
## Introduction et context This project uses Quarkus, the Supersonic Subatomic Java Framework.
Lintranet de la Fédération française de Soft Armored Fighting a pour but centralise la prise de licences des adhérents If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
et laffiliation des clubs. Il permet aussi de recueillir les résultats des compétitions organisées par les clubs et,
plus récemment, den faciliter lorganisation grâce à un outil intégré.
Le système de prise de licence se divise en trois parties :
* Un formulaire public pour la première demande daffiliation ;
* Un espace club, accessible après validation, pour la saisie des informations relatives aux demandes de licence des
adhérents ;
* Un espace fédération pour accepter les demandes de licences et daffiliation.
Un espace membre permet enfin à chaque adhérent de télécharger son attestation de licence.
Pour les compétitions, le système permet la création de catégories, de poules et de matchs, avec génération automatique
des matchs au sein dune poule. Il supporte plusieurs lices, assure la synchronisation en temps réel des modifications
entre toutes les instances de lapplication web et publie automatiquement les résultats sur le site de lorganisateur.
## Running the application in dev mode ## Running the application in dev mode
@ -66,3 +52,34 @@ Or, if you don't have GraalVM installed, you can run the native executable build
You can then execute your native executable with: `./target/ffsaf-site-1.0-SNAPSHOT-runner` You can then execute your native executable with: `./target/ffsaf-site-1.0-SNAPSHOT-runner`
If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling. If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling.
## Related Guides
- Reactive MySQL client ([guide](https://quarkus.io/guides/reactive-sql-clients)): Connect to the MySQL database using the reactive pattern
- RESTEasy Reactive ([guide](https://quarkus.io/guides/resteasy-reactive)): A Jakarta REST implementation utilizing build time processing and Vert.x.
This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it.
- Hibernate ORM with Panache ([guide](https://quarkus.io/guides/hibernate-orm-panache)): Simplify your persistence code for Hibernate ORM via the
active record or the repository pattern
- Reactive PostgreSQL client ([guide](https://quarkus.io/guides/reactive-sql-clients)): Connect to the PostgreSQL database using the reactive pattern
## Provided Code
### Hibernate ORM
Create your first JPA entity
[Related guide section...](https://quarkus.io/guides/hibernate-orm)
[Related Hibernate with Panache section...](https://quarkus.io/guides/hibernate-orm-panache)
### RESTEasy Reactive
Easily start your Reactive RESTful Web Services
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
### RESTEasy Reactive Qute
Create your web page using Quarkus RESTEasy Reactive & Qute
[Related guide section...](https://quarkus.io/guides/qute#type-safe-templates)

View File

@ -1,51 +0,0 @@
services:
ffsaf:
container_name: ffsaf
hostname: ffsaf
restart: always
build:
context: .
dockerfile: src/main/docker/Dockerfile.native
volumes:
- ${PWD}/ffsaf.properties:/work/config/application.properties
- ${PWD}/ffsaf_cle_prive.jks:/work/cle_prive.jks
- ${PWD}/mail-truststore.p12:/work/mail-truststore.p12
- ${PWD}/sign.jpg:/work/sign.jpg
- ${PWD}/ffsaf-media:/work/media
depends_on:
ffsaf-db:
condition: service_healthy
restart: true
networks:
- default
- intra
- nginx
ffsaf-db:
image: public.ecr.aws/docker/library/postgres:17.2
hostname: ffsaf-db
container_name: ffsaf-db
user: postgres
restart: always
networks:
- pgadmin
- default
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 5s
timeout: 3s
retries: 10
volumes:
- ${PWD}/postgres-data:/var/lib/postgresql/data
env_file: prod.env
networks:
intra:
name: intra
driver: bridge
pgadmin:
name: pgadmin
external: true
nginx:
name: ${NETWORK_NAME:-gateway}
external: true

65
pom.xml
View File

@ -1,19 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>fr.titionfire</groupId> <groupId>fr.titionfire</groupId>
<artifactId>ffsaf-site</artifactId> <artifactId>ffsaf-site</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<properties> <properties>
<compiler-plugin.version>3.12.1</compiler-plugin.version> <compiler-plugin.version>3.11.0</compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release> <maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.16.4</quarkus.platform.version> <quarkus.platform.version>3.6.5</quarkus.platform.version>
<skipITs>true</skipITs> <skipITs>true</skipITs>
<surefire-plugin.version>3.2.3</surefire-plugin.version> <surefire-plugin.version>3.1.2</surefire-plugin.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@ -33,27 +34,31 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId> <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-qute</artifactId> <artifactId>quarkus-resteasy-reactive-qute</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId> <artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId> <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId> <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId> <artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@ -65,24 +70,17 @@
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId> <artifactId>quarkus-arc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkiverse.tika</groupId>
<artifactId>quarkus-tika</artifactId>
<version>2.0.4</version>
</dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId> <artifactId>quarkus-junit5</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId> <artifactId>quarkus-oidc</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-authorization</artifactId> <artifactId>quarkus-keycloak-authorization</artifactId>
@ -90,9 +88,10 @@
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-rest-client</artifactId> <artifactId>quarkus-keycloak-admin-client-reactive</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@ -105,34 +104,11 @@
<artifactId>jodd-util</artifactId> <artifactId>jodd-util</artifactId>
<version>6.2.1</version> <version>6.2.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets</artifactId> <artifactId>quarkus-websockets</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mailer</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
@ -201,11 +177,10 @@
</activation> </activation>
<properties> <properties>
<skipITs>false</skipITs> <skipITs>false</skipITs>
<quarkus.package.type>native</quarkus.package.type>
<quarkus.native.additional-build-args> <quarkus.native.additional-build-args>
--initialize-at-run-time=com.fasterxml.jackson.databind.ext.DOMDeserializer -H:+UnlockExperimentalVMOptions
</quarkus.native.additional-build-args> </quarkus.native.additional-build-args>
<quarkus.native.enabled>true</quarkus.native.enabled>
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
</properties> </properties>
</profile> </profile>
</profiles> </profiles>

View File

@ -14,17 +14,13 @@
# docker run -i --rm -p 8080:8080 quarkus/ffsaf-site # docker run -i --rm -p 8080:8080 quarkus/ffsaf-site
# #
### ###
# replace FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 for jvm need sub application (ie. make_pdf) FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9
FROM registry.access.redhat.com/ubi8/openjdk-17:1.18
USER 0
WORKDIR /work/ WORKDIR /work/
RUN chown 1001 /work \ RUN chown 1001 /work \
&& chmod "g+rwX" /work \ && chmod "g+rwX" /work \
&& chown 1001:root /work && chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application COPY --chown=1001:root ffsaf/target/*-runner /work/application
COPY --chown=1001:root src/main/resources/cacerts /work/cacerts COPY --chown=1001:root ffsaf/src/main/resources/cacerts /work/cacerts
COPY --chown=1001:root src/main/pdf_gen/target/pdf_gen-*.jar /work/make_pdf.jar
RUN mkdir /work/media && chown -R 1001:root /work/media RUN mkdir /work/media && chown -R 1001:root /work/media
EXPOSE 8080 EXPOSE 8080

View File

@ -2,14 +2,12 @@ package fr.titionfire;
import io.quarkus.oidc.IdToken; import io.quarkus.oidc.IdToken;
import io.quarkus.oidc.RefreshToken; import io.quarkus.oidc.RefreshToken;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.security.identity.SecurityIdentity;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.jboss.resteasy.reactive.NoCache; import org.jboss.resteasy.reactive.NoCache;
@Path("/hello") @Path("/hello")
@ -32,9 +30,6 @@ public class ExampleResource {
@IdToken @IdToken
JsonWebToken idToken; JsonWebToken idToken;
@Inject
UserInfo userInfo;
/** /**
* Injection point for the Access Token issued by the OpenID Connect Provider * Injection point for the Access Token issued by the OpenID Connect Provider
*/ */
@ -59,14 +54,12 @@ public class ExampleResource {
@GET @GET
@Produces("text/html") @Produces("text/html")
@NoCache @NoCache
@Operation(hidden = true)
public String getTokens() { public String getTokens() {
StringBuilder response = new StringBuilder().append("<html>") StringBuilder response = new StringBuilder().append("<html>")
.append("<body>") .append("<body>")
.append("<ul>"); .append("<ul>");
System.out.println(idToken);
System.out.println(accessToken);
Object userName = this.idToken.getClaim("preferred_username"); Object userName = this.idToken.getClaim("preferred_username");
if (userName != null) { if (userName != null) {
@ -76,17 +69,25 @@ public class ExampleResource {
response.append("<li>username: ").append(this.idToken.toString()).append("</li>"); response.append("<li>username: ").append(this.idToken.toString()).append("</li>");
} }
/*Object scopes = this.accessToken.getClaim("scope"); Object scopes = this.accessToken.getClaim("scope");
if (scopes != null) { if (scopes != null) {
response.append("<li>scopes: ").append(scopes.toString()).append("</li>"); response.append("<li>scopes: ").append(scopes.toString()).append("</li>");
} }
response.append("<li>scopes: ").append(this.accessToken.toString()).append("</li>"); if (scopes != null) {
response.append("<li>scopes: ").append(this.accessToken.getClaim("user_groups").toString()).append("</li>");*/ response.append("<li>scopes: ").append(this.accessToken.toString()).append("</li>");
}
response.append("<li>getRoles: ").append(this.securityIdentity.getRoles()).append("</li>"); if (scopes != null) {
response.append("<li>scopes: ").append(this.accessToken.getClaim("user_groups").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>"); response.append("<li>refresh_token: ").append(refreshToken.getToken() != null).append("</li>");
return response.append("</ul>").append("</body>").append("</html>").toString(); return response.append("</ul>").append("</body>").append("</html>").toString();

View File

@ -1,27 +0,0 @@
package fr.titionfire;
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.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@Tag(name = "Ping API", description = "API pour tester la connectivité")
@Path("/api")
public class PingPage {
@Operation(summary = "Renvoie un message de réussite", description = "Cette méthode renvoie un message de réussite si la connexion est établie avec succès.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite")
})
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response get() {
return Response.ok().build();
}
}

View File

@ -1,16 +1,17 @@
package fr.titionfire; package fr.titionfire;
import io.quarkus.qute.Template; import io.quarkus.qute.Template;
import io.smallrye.mutiny.Uni; import io.quarkus.qute.TemplateInstance;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces; import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@Path("api/some-page") @Path("/some-page")
public class SomePage { public class SomePage {
private final Template page; private final Template page;
@ -21,12 +22,8 @@ public class SomePage {
@GET @GET
@Produces(MediaType.TEXT_HTML) @Produces(MediaType.TEXT_HTML)
@Operation(hidden = true) public TemplateInstance get(@QueryParam("name") String name) {
public Uni<String> get() { return page.data("name", name);
return Uni.createFrom()
.completionStage(() -> page
.data("name", "test")
.renderAsync());
} }
} }

View File

@ -1,68 +0,0 @@
package fr.titionfire.ffsaf;
import io.vertx.core.http.HttpServerRequest;
import jakarta.annotation.Priority;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import org.jboss.logging.Logger;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Scanner;
@Provider
@Priority(Priorities.USER)
public class FrontendForwardingFilterREST implements ExceptionMapper<NotFoundException> {
private static final Logger LOG = Logger.getLogger(FrontendForwardingFilterREST.class);
@Context
UriInfo info;
@Context
HttpServerRequest request;
@Context
ResourceInfo resourceInfo;
private static String text = null;
private static final String API_NAMESPACE_REGEX = "^/(api/.*|api|openapi|q/.*)";
private static final String FILENAME_REGEX = "^/.*\\.[^.]+$";
@Override
public Response toResponse(NotFoundException exception) {
final String path = info.getPath();
final String address = request.remoteAddress().toString();
boolean isApiNamespace = path.matches(API_NAMESPACE_REGEX);
if (isApiNamespace) {
LOG.infof("Request %s from IP %s => %d", "method", path, address, exception.getResponse().getStatus());
return exception.getResponse();
}
boolean isFilename = path.matches(FILENAME_REGEX);
if (isFilename) {
LOG.infof("Request %s from IP %s => %d", "method", path, address, exception.getResponse().getStatus());
return exception.getResponse();
}
boolean actualErrorResponse = resourceInfo != null && resourceInfo.getResourceMethod() != null;
if (actualErrorResponse) {
LOG.infof("Request %s from IP %s => %d", "method", path, address, exception.getResponse().getStatus());
return exception.getResponse();
}
if (text == null)
text = new Scanner(
Objects.requireNonNull(this.getClass().getResourceAsStream("/META-INF/resources/index.html")),
StandardCharsets.UTF_8).useDelimiter("\\A").next();
LOG.infof("Request %s from IP %s => redirect", "method", path, address);
return Response.status(200).entity(text).type(MediaType.TEXT_HTML_TYPE).build();
}
}

View File

@ -1,35 +0,0 @@
package fr.titionfire.ffsaf.data;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
public class CustomPostgreSQLDialect extends PostgreSQLDialect {
private static final Logger LOGGER = Logger.getLogger(PostgreSQLDialect.class);
public CustomPostgreSQLDialect() {
super();
}
@Override
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
LOGGER.info("Initializing custom function registry");
SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
BasicType<String> stringType = basicTypeRegistry.resolve(StandardBasicTypes.STRING);
functionRegistry.namedDescriptorBuilder("unaccent").setInvariantType(stringType).setExactArgumentCount(1)
.setParameterTypes(new FunctionParameterType[]{FunctionParameterType.STRING}).register();
}
}

View File

@ -1,17 +0,0 @@
package fr.titionfire.ffsaf.data.id;
import jakarta.persistence.Embeddable;
import lombok.*;
import java.io.Serializable;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Embeddable
public class RegisterId implements Serializable {
private Long competitionId;
private Long membreId;
}

View File

@ -3,7 +3,6 @@ package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Getter @Getter
@Setter @Setter
@ -17,13 +16,11 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
public class AffiliationModel { public class AffiliationModel {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "Identifiant de l'affiliation", example = "42")
Long id; Long id;
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club", referencedColumnName = "id") @JoinColumn(name = "club", referencedColumnName = "id")
ClubModel club; ClubModel club;
@Schema(description = "Saison de l'affiliation", example = "2021")
int saison; int saison;
} }

View File

@ -1,6 +1,5 @@
package fr.titionfire.ffsaf.data.model; package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.RoleAsso;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
@ -21,27 +20,24 @@ public class AffiliationRequestModel {
Long id; Long id;
String name; String name;
String state_id; String siren;
String RNA;
String address; String address;
String contact;
String m1_lname; String president_lname;
String m1_fname; String president_fname;
String m1_email; String president_email;
int m1_lincence; int president_lincence;
RoleAsso m1_role;
String m2_lname; String tresorier_lname;
String m2_fname; String tresorier_fname;
String m2_email; String tresorier_email;
int m2_lincence; int tresorier_lincence;
RoleAsso m2_role;
String m3_lname; String secretaire_lname;
String m3_fname; String secretaire_fname;
String m3_email; String secretaire_email;
int m3_lincence; int secretaire_lincence;
RoleAsso m3_role;
int saison; int saison;
} }

View File

@ -1,45 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "category")
public class CategoryModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "system_type")
CompetitionSystem system;
Long systemId;
String name = "";
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "id_compet", referencedColumnName = "id")
CompetitionModel compet;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "id_category", referencedColumnName = "id")
List<MatchModel> matchs;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "id_category", referencedColumnName = "id")
List<TreeModel> tree;
Integer type;
}

View File

@ -1,41 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.Date;
import java.util.List;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "checkout")
public class CheckoutModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "Identifiant du checkout", example = "42")
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre", referencedColumnName = "id")
MembreModel membre;
Date creationDate = new Date();
List<Long> licenseIds;
Integer checkoutId;
PaymentStatus paymentStatus;
public enum PaymentStatus {
PENDING, AUTHORIZED, REFUSED, UNKNOW, REGISTERED, REFUNDING, REFUNDED, CONTESTED
}
}

View File

@ -4,7 +4,6 @@ import fr.titionfire.ffsaf.utils.Contact;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -18,63 +17,40 @@ import java.util.Map;
@Entity @Entity
@Table(name = "club") @Table(name = "club")
public class ClubModel implements LoggableModel { public class ClubModel {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "Identifiant du club", example = "1")
Long id; Long id;
@Schema(description = "Identifiant long du club (UUID)", example = "b94f3167-3f6a-449c-a73b-ec84202bf07e")
String clubId; String clubId;
@Schema(description = "Nom du club", example = "Association sportive")
String name; String name;
@Schema(description = "Pays du club", example = "FR")
String country; String country;
String shieldURL;
//@Enumerated(EnumType.STRING) //@Enumerated(EnumType.STRING)
@ElementCollection @ElementCollection
@CollectionTable(name = "club_contact_mapping", @CollectionTable(name = "club_contact_mapping",
joinColumns = {@JoinColumn(name = "club_id", referencedColumnName = "id")}) joinColumns = {@JoinColumn(name = "club_id", referencedColumnName = "id")})
@MapKeyColumn(name = "contact_type") @MapKeyColumn(name = "contact_type")
@Schema(description = "Les contacts du club", example = "{\"SITE\": \"www.test.com\", \"COURRIEL\": \"test@test.com\"}")
Map<Contact, String> contact; Map<Contact, String> contact;
@Column(columnDefinition = "TEXT")
@Schema(description = "Liste des lieux d'entraînement", example = "[{\"text\":\"addr 1\",\"lng\":2.24654,\"lat\":52.4868658},{\"text\":\"addr 2\",\"lng\":2.88654,\"lat\":52.7865456}]")
String training_location; String training_location;
@Column(columnDefinition = "TEXT")
@Schema(description = "Liste des jours et horaires d'entraînement (jours 0-6, 0=>lundi) (temps en minute depuis 00:00, 122=>2h02)", example = "[{\"day\":0,\"time_start\":164,\"time_end\":240},{\"day\":3,\"time_start\":124,\"time_end\":250}]")
String training_day_time; String training_day_time;
@Schema(description = "Contact interne du club", example = "john.doe@test.com")
String contact_intern; String contact_intern;
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris") String RNA;
String address;
@Schema(description = "Numéro SIRET ou RNA du club", example = "12345678901234") String SIRET;
String StateId;
@Schema(description = "Numéro d'affiliation du club", example = "12345") String no_affiliation;
Long no_affiliation;
@Schema(description = "Club international", example = "false")
boolean international; boolean international;
@OneToMany(mappedBy = "club", fetch = FetchType.LAZY, cascade = CascadeType.ALL) @OneToMany(mappedBy = "club", fetch = FetchType.EAGER)
@Schema(description = "Liste des affiliations du club (optionnel)")
List<AffiliationModel> affiliations; List<AffiliationModel> affiliations;
@Override
public String getObjectName() {
return this.name;
}
@Override
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Club;
}
} }

View File

@ -1,66 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.RegisterMode;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "compet")
public class CompetitionModel {
@Id
@Access(AccessType.PROPERTY)
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "system_type")
CompetitionSystem system;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club", referencedColumnName = "id")
ClubModel club;
String name;
String uuid;
Date date;
Date todate;
@Column(columnDefinition = "TEXT")
String description;
String adresse;
Date startRegister;
Date endRegister;
RegisterMode registerMode;
boolean publicVisible;
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
List<RegisterModel> insc;
List<Long> banMembre = new ArrayList<>();
String owner;
String data1;
String data2;
String data3;
String data4;
}

View File

@ -1,41 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.data.id.RegisterId;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "helloasso_register")
public class HelloAssoRegisterModel {
@EmbeddedId
RegisterId id;
@MapsId("competitionId")
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "id_competition")
CompetitionModel competition;
@MapsId("membreId")
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "id_membre")
MembreModel membre;
Integer orderId;
public HelloAssoRegisterModel(CompetitionModel competition, MembreModel membre, Integer orderId) {
this.id = new RegisterId(competition.getId(), membre.getId());
this.competition = competition;
this.membre = membre;
this.orderId = orderId;
}
}

View File

@ -3,7 +3,6 @@ package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Getter @Getter
@Setter @Setter
@ -14,39 +13,18 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Entity @Entity
@Table(name = "licence") @Table(name = "licence")
public class LicenceModel implements LoggableModel { public class LicenceModel {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "L'identifiant de la licence.")
Long id; Long id;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre", referencedColumnName = "id") @JoinColumn(name = "membre", referencedColumnName = "id")
@Schema(description = "Le membre de la licence. (optionnel)")
MembreModel membre; MembreModel membre;
Long club_id;
@Schema(description = "La saison de la licence.", example = "2025")
int saison; int saison;
@Schema(description = "Nom et date du médecin sur certificat médical.", example = "M. Jean¤2025-02-03", format = "<Nom>¤<yyyy-mm-dd>") boolean certificate;
String certificate;
@Schema(description = "Licence validée", example = "true")
boolean validate; boolean validate;
@Schema(description = "Licence payer", example = "true")
@Column(nullable = false, columnDefinition = "boolean default false")
boolean pay = false;
@Override
public String getObjectName() {
return "licence " + id.toString();
}
@Override
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Licence;
}
} }

View File

@ -1,47 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import java.util.Date;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "log")
public class LogModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String subject;
Date dateTime;
ActionType action;
ObjectType object;
Long target_id;
@Column(columnDefinition = "TEXT")
String target_name;
@Column(columnDefinition = "TEXT")
String message;
public enum ActionType {
ADD, REMOVE, UPDATE
}
public enum ObjectType {
Membre, Affiliation, Licence, Club, Competition, Register
}
}

View File

@ -1,7 +0,0 @@
package fr.titionfire.ffsaf.data.model;
public interface LoggableModel {
Long getId();
String getObjectName();
LogModel.ObjectType getObjectType();
}

View File

@ -1,56 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@ToString
@Table(name = "match")
public class MatchModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "system_type")
CompetitionSystem system;
Long systemId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "c1", referencedColumnName = "id")
MembreModel c1_id = null;
String c1_str = null;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "c2", referencedColumnName = "id")
MembreModel c2_id = null;
String c2_str = null;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "id_category", referencedColumnName = "id")
CategoryModel category = null;
long category_ord = 0;
boolean isEnd = true;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
List<ScoreEmbeddable> scores = new ArrayList<>();
char poule = 'A';
}

View File

@ -7,7 +7,6 @@ import fr.titionfire.ffsaf.utils.RoleAsso;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -21,83 +20,39 @@ import java.util.List;
@Entity @Entity
@Table(name = "membre") @Table(name = "membre")
public class MembreModel implements LoggableModel { public class MembreModel {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Access(AccessType.PROPERTY)
@Schema(description = "L'identifiant du membre.", example = "1")
Long id; Long id;
@Schema(description = "L'identifiant long du membre (userID).", example = "e81d1d35-d897-421e-8086-6c5e74d13c6e")
String userId; String userId;
@Schema(description = "Le nom du membre.", example = "Dupont")
String lname; String lname;
@Schema(description = "Le prénom du membre.", example = "Jean")
String fname; String fname;
@Schema(description = "La catégorie du membre.", example = "SENIOR")
Categorie categorie; Categorie categorie;
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club", referencedColumnName = "id") @JoinColumn(name = "club", referencedColumnName = "id")
@Schema(description = "Le club du membre.")
ClubModel club; ClubModel club;
@Schema(description = "Le genre du membre.", example = "H")
Genre genre; Genre genre;
@Schema(description = "Le numéro de licence du membre.", example = "12345") int licence;
Integer licence;
@Schema(description = "Le pays du membre.", example = "FR")
String country; String country;
@Schema(description = "La date de naissance du membre.")
Date birth_date; Date birth_date;
@Schema(description = "L'adresse e-mail du membre.", example = "jean.dupont@example.com")
String email; String email;
@Schema(description = "Le rôle du membre dans l'association.", example = "MEMBRE")
RoleAsso role; RoleAsso role;
@Schema(description = "Le grade d'arbitrage du membre.", example = "NA")
GradeArbitrage grade_arbitrage; GradeArbitrage grade_arbitrage;
@Schema(hidden = true)
String url_photo; String url_photo;
@OneToMany(mappedBy = "membre", fetch = FetchType.LAZY, cascade = CascadeType.ALL) @OneToMany(mappedBy = "membre", fetch = FetchType.LAZY)
@Schema(description = "Les licences du membre. (optionnel)")
List<LicenceModel> licences; List<LicenceModel> licences;
@Override
public String getObjectName() {
return fname + " " + lname;
}
@Override
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Membre;
}
@Override
public String toString() {
return "MembreModel{" +
"id=" + id +
", userId='" + userId + '\'' +
", lname='" + lname + '\'' +
", fname='" + fname + '\'' +
", categorie=" + categorie +
", genre=" + genre +
", licence=" + licence +
", country='" + country + '\'' +
", birth_date=" + birth_date +
", email='" + email + '\'' +
", role=" + role +
", grade_arbitrage=" + grade_arbitrage +
'}';
}
} }

View File

@ -1,59 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.data.id.RegisterId;
import fr.titionfire.ffsaf.utils.Categorie;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "register")
public class RegisterModel {
@EmbeddedId
RegisterId id;
@MapsId("competitionId")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_competition")
CompetitionModel competition;
@MapsId("membreId")
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "id_membre")
MembreModel membre;
Integer weight;
int overCategory = 0;
Categorie categorie;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club")
@OnDelete(action = OnDeleteAction.SET_NULL)
ClubModel club = null;
@Column(nullable = false, columnDefinition = "boolean default false")
boolean lockEdit = false;
public RegisterModel(CompetitionModel competition, MembreModel membre, Integer weight, int overCategory,
Categorie categorie, ClubModel club) {
this.id = new RegisterId(competition.getId(), membre.getId());
this.competition = competition;
this.membre = membre;
this.weight = weight;
this.overCategory = overCategory;
this.categorie = categorie;
this.club = club;
}
}

View File

@ -1,24 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.SequenceType;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "sequence")
public class SequenceModel {
@Id
SequenceType type;
long value;
}

View File

@ -1,39 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "tree")
public class TreeModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@Column(name = "id_category")
Long category;
Integer level;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "match_id", referencedColumnName = "id")
MatchModel match;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(referencedColumnName = "id")
TreeModel left;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinColumn(referencedColumnName = "id")
TreeModel right;
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.AffiliationModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class AffiliationRepository implements PanacheRepositoryBase<AffiliationModel, Long> {
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CategoryModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CategoryRepository implements PanacheRepositoryBase<CategoryModel, Long> {
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CheckoutModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CheckoutRepository implements PanacheRepositoryBase<CheckoutModel, Long> {
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CompetitionRepository implements PanacheRepositoryBase<CompetitionModel, Long> {
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.HelloAssoRegisterModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class HelloAssoRegisterRepository implements PanacheRepositoryBase<HelloAssoRegisterModel, Long> {
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.LogModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class LogRepository implements PanacheRepositoryBase<LogModel, Long> {
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.MatchModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long> {
}

View File

@ -1,11 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.id.RegisterId;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class RegisterRepository implements PanacheRepositoryBase<RegisterModel, RegisterId> {
}

View File

@ -1,21 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.SequenceModel;
import fr.titionfire.ffsaf.utils.SequenceType;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class SequenceRepository implements PanacheRepositoryBase<SequenceModel, SequenceType> {
public Uni<Long> getNextValueInTransaction(SequenceType type) {
return this.findById(type).onItem().ifNull()
.switchTo(() -> this.persist(new SequenceModel(type, 1L)))
.chain(v -> {
v.setValue(v.getValue() + 1);
return this.persistAndFlush(v);
})
.map(SequenceModel::getValue);
}
}

View File

@ -1,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.TreeModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class TreeRepository implements PanacheRepositoryBase<TreeModel, Long> {
}

View File

@ -18,12 +18,14 @@ public class ClubEntity {
private String name; private String name;
private String clubId; private String clubId;
private String country; private String country;
private String shieldURL;
private Map<Contact, String> contact; private Map<Contact, String> contact;
private String training_location; private String training_location;
private String training_day_time; private String training_day_time;
private String contact_intern; private String contact_intern;
private String StateId; private String RNA;
private Long no_affiliation; private String SIRET;
private String no_affiliation;
private boolean international; private boolean international;
public static ClubEntity fromModel (ClubModel model) { public static ClubEntity fromModel (ClubModel model) {
@ -36,11 +38,13 @@ public class ClubEntity {
.name(model.getName()) .name(model.getName())
.clubId(model.getClubId()) .clubId(model.getClubId())
.country(model.getCountry()) .country(model.getCountry())
.shieldURL(model.getShieldURL())
.contact(model.getContact()) .contact(model.getContact())
.training_location(model.getTraining_location()) .training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time()) .training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern()) .contact_intern(model.getContact_intern())
.StateId(model.getStateId()) .RNA(model.getRNA())
.SIRET(model.getSIRET())
.no_affiliation(model.getNo_affiliation()) .no_affiliation(model.getNo_affiliation())
.international(model.isInternational()) .international(model.isInternational())
.build(); .build();

View File

@ -23,7 +23,7 @@ public class MembreEntity {
private Categorie categorie; private Categorie categorie;
private ClubEntity club; private ClubEntity club;
private Genre genre; private Genre genre;
private Integer licence; private int licence;
private String country; private String country;
private Date birth_date; private Date birth_date;
private String email; private String email;

View File

@ -1,482 +1,60 @@
package fr.titionfire.ffsaf.domain.service; package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.*; import fr.titionfire.ffsaf.data.model.AffiliationRequestModel;
import fr.titionfire.ffsaf.data.repository.*; import fr.titionfire.ffsaf.data.repository.AffiliationRequestRepository;
import fr.titionfire.ffsaf.rest.client.SirenService; import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm; import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm;
import fr.titionfire.ffsaf.utils.Genre;
import fr.titionfire.ffsaf.utils.GradeArbitrage;
import fr.titionfire.ffsaf.utils.SequenceType;
import fr.titionfire.ffsaf.utils.Utils; import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.reactive.ReactiveMailer;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class AffiliationService { public class AffiliationService {
private static final Logger LOGGER = Logger.getLogger(AffiliationService.class);
@Inject @Inject
CombRepository combRepository; CombRepository combRepository;
@Inject @Inject
ClubRepository clubRepository; AffiliationRequestRepository repository;
@Inject
AffiliationRequestRepository repositoryRequest;
@Inject
AffiliationRepository repository;
@Inject
KeycloakService keycloakService;
@Inject
SequenceRepository sequenceRepository;
@Inject
LicenceRepository licenceRepository;
@Inject
ReactiveMailer reactiveMailer;
@Inject
LoggerService ls;
@RestClient
StateIdService stateIdService;
@RestClient
SirenService sirenService;
@ConfigProperty(name = "upload_dir") @ConfigProperty(name = "upload_dir")
String media; String media;
@ConfigProperty(name = "notif.affRequest.mail") public Uni<String> save(AffiliationRequestForm form) {
List<String> mails;
public Uni<List<AffiliationRequestModel>> getAllReq() {
return repositoryRequest.listAll();
}
public Uni<AffiliationRequestModel> pre_save(AffiliationRequestForm form, boolean unique) {
AffiliationRequestModel affModel = form.toModel(); AffiliationRequestModel affModel = form.toModel();
int currentSaison = Utils.getSaison(); affModel.setSaison(Utils.getSaison());
List<String> out = new ArrayList<>();
out.add(affModel.getState_id());
return Uni.createFrom().item(affModel) return Uni.createFrom().item(affModel)
.invoke(Unchecked.consumer(model -> { .call(model -> ((model.getPresident_lincence() != 0) ? combRepository.find("licence",
if (model.getSaison() != currentSaison && model.getSaison() != currentSaison + 1) { model.getPresident_lincence()).count().invoke(count -> {
throw new DBadRequestException("Saison non valid");
}
}))
.chain(() -> ((affModel.getState_id().charAt(0) == 'W') ? stateIdService.get_rna(
affModel.getState_id()) : sirenService.get_unite(affModel.getState_id())
.chain(stateIdService::getAssoDataFromUnit)).onItem().transform(o -> {
if (o.getRna() != null && !o.getRna().isBlank())
out.add(o.getRna());
if (o.getSiren() != null && !o.getSiren().isBlank())
out.add(o.getSiren());
if (o.getIdentite().getSiret_siege() != null && !o.getIdentite().getSiret_siege().isBlank())
out.add(o.getIdentite().getSiret_siege());
return out;
}).onFailure().recoverWithItem(out)
.chain(a -> repositoryRequest.count("state_id IN ?1 and saison = ?2",
out, affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
)
.chain(() -> clubRepository.find("StateId IN ?1", out).firstResult().chain(club ->
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) {
throw new DBadRequestException("Affiliation déjà existante");
}
}))
.map(o -> affModel)
.call(model -> ((model.getM1_lincence() != -1) ? combRepository.find("licence",
model.getM1_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) { if (count == 0) {
throw new DBadRequestException("Licence membre n°1 inconnue"); throw new IllegalArgumentException("Licence président inconnue");
} }
})) : Uni.createFrom().nullItem()) }) : Uni.createFrom().nullItem())
) )
.call(model -> ((model.getM2_lincence() != -1) ? combRepository.find("licence", .call(model -> ((model.getTresorier_lincence() != 0) ? combRepository.find("licence",
model.getM2_lincence()).count().invoke(Unchecked.consumer(count -> { model.getTresorier_lincence()).count().invoke(count -> {
if (count == 0) { if (count == 0) {
throw new DBadRequestException("Licence membre n°2 inconnue"); throw new IllegalArgumentException("Licence trésorier inconnue");
} }
})) : Uni.createFrom().nullItem()) }) : Uni.createFrom().nullItem())
) )
.call(model -> ((model.getM3_lincence() != -1) ? combRepository.find("licence", .call(model -> ((model.getSecretaire_lincence() != 0) ? combRepository.find("licence",
model.getM3_lincence()).count().invoke(Unchecked.consumer(count -> { model.getSecretaire_lincence()).count().invoke(count -> {
if (count == 0) { if (count == 0) {
throw new DBadRequestException("Licence membre n°3 inconnue"); throw new IllegalArgumentException("Licence secrétaire inconnue");
} }
})) : Uni.createFrom().nullItem()) }) : Uni.createFrom().nullItem())
); ).chain(model -> Panache.withTransaction(() -> repository.persist(model)))
} .call(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getLogo(), media,
public Uni<?> saveEdit(AffiliationRequestForm form) {
return pre_save(form, false)
.chain(model -> repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(origine -> {
origine.setName(model.getName());
origine.setAddress(model.getAddress());
origine.setContact(model.getContact());
origine.setM1_lname(model.getM1_lname());
origine.setM1_fname(model.getM1_fname());
origine.setM1_lincence(model.getM1_lincence());
origine.setM1_role(model.getM1_role());
origine.setM1_email(model.getM1_email());
origine.setM2_lname(model.getM2_lname());
origine.setM2_fname(model.getM2_fname());
origine.setM2_lincence(model.getM2_lincence());
origine.setM2_role(model.getM2_role());
origine.setM2_email(model.getM2_email());
origine.setM3_lname(model.getM3_lname());
origine.setM3_fname(model.getM3_fname());
origine.setM3_lincence(model.getM3_lincence());
origine.setM3_role(model.getM3_role());
origine.setM3_email(model.getM3_email());
return Panache.withTransaction(() -> repositoryRequest.persist(origine));
}));
}
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)))
.onItem()
.invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getLogo(), media,
"aff_request/logo"))) "aff_request/logo")))
.onItem() .call(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getStatus(), media,
.invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getStatus(), media,
"aff_request/status")))
.call(model -> reactiveMailer.send(
Mail.withText("no-reply@ffsaf.fr",
"[NOTIF] FFSAF - Nouvelle demande d'affiliation",
String.format(
"""
Une nouvelle demande d'affiliation a été déposée sur l'intranet pour le club: %s.
""", model.getName())
).setFrom("FFSAF <no-reply@ffsaf.fr>")
.addBcc(mails.toArray(String[]::new))
))
.map(__ -> "Ok");
}
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 -> {
model.setName(form.getName());
model.setState_id(form.getState_id());
model.setAddress(form.getAddress());
model.setContact(form.getContact());
if (form.getM1_mode() == 2) {
model.setM1_lname(form.getM1_lname());
model.setM1_fname(form.getM1_fname());
} else {
model.setM1_lincence(
form.getM1_lincence() == null ? 0 : Integer.parseInt(form.getM1_lincence()));
}
model.setM1_role(form.getM1_role());
if (form.getM1_email_mode() == 0)
model.setM1_email(form.getM1_email());
if (form.getM2_mode() == 2) {
model.setM2_lname(form.getM2_lname());
model.setM2_fname(form.getM2_fname());
} else {
model.setM2_lincence(
form.getM2_lincence() == null ? 0 : Integer.parseInt(form.getM2_lincence()));
}
model.setM2_role(form.getM2_role());
if (form.getM2_email_mode() == 0)
model.setM2_email(form.getM2_email());
if (form.getM3_mode() == 2) {
model.setM3_lname(form.getM3_lname());
model.setM3_fname(form.getM3_fname());
} else {
model.setM3_lincence(
form.getM3_lincence() == null ? 0 : Integer.parseInt(form.getM3_lincence()));
}
model.setM3_role(form.getM3_role());
if (form.getM3_email_mode() == 0)
model.setM3_email(form.getM3_email());
return model;
})
.chain(model -> Panache.withTransaction(() -> repositoryRequest.persist(model)))
.onItem()
.invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getLogo(), media,
"aff_request/logo")))
.onItem()
.invoke(model -> Uni.createFrom().future(Utils.replacePhoto(model.getId(), form.getStatus(), media,
"aff_request/status"))) "aff_request/status")))
.map(__ -> "Ok"); .map(__ -> "Ok");
} }
private Uni<?> setMembre(AffiliationRequestSaveForm.Member member, ClubModel club, int saison) {
return Uni.createFrom().nullItem().chain(__ -> {
if (member.getMode() == 2) {
MembreModel membreModel = new MembreModel();
membreModel.setFname(member.getFname());
membreModel.setLname(member.getLname().toUpperCase());
membreModel.setClub(club);
membreModel.setRole(member.getRole());
membreModel.setEmail(member.getEmail());
membreModel.setGrade_arbitrage(GradeArbitrage.NA);
membreModel.setGenre(Genre.NA);
membreModel.setCountry("FR");
return Panache.withTransaction(() ->
combRepository.persist(membreModel)
.chain(m -> sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
.invoke(l -> m.setLicence(Math.toIntExact(l)))
.chain(() -> combRepository.persist(m))));
} else {
return combRepository.find("licence", Integer.parseInt(member.getLicence())).firstResult()
.onItem().ifNull().switchTo(() -> {
MembreModel membreModel = new MembreModel();
membreModel.setFname(member.getFname());
membreModel.setLname(member.getLname().toUpperCase());
return Panache.withTransaction(
() -> sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
.invoke(l -> membreModel.setLicence(Math.toIntExact(l)))
.chain(() -> combRepository.persist(membreModel)));
})
.map(m -> {
m.setClub(club);
m.setRole(member.getRole());
m.setEmail(member.getEmail());
return m;
}).call(m -> Panache.withTransaction(() -> combRepository.persist(m)));
}
})
.call(m -> ((m.getUserId() == null) ? keycloakService.initCompte(m.getId())
.onFailure().invoke(t -> LOGGER.warnf("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull() :
keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId()))
.call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage()))
.call(userId -> keycloakService.setEmail(userId, m.getEmail())))
.call(m -> Mutiny.fetch(m.getLicences())
.call(l1 -> l1 != null && l1.stream().anyMatch(l -> l.getSaison() == saison) ?
Uni.createFrom().nullItem() :
Panache.withTransaction(() -> licenceRepository.persist(
new LicenceModel(null, m, club.getId(), saison, null, true, false)))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, m.getObjectName(),
licenceModel))));
}
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 ->
clubRepository.find("StateId = ?1", form.getState_id()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
.call(club -> setMembre(form.new Member(1), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(__ -> setMembre(form.new Member(2), club, req.getSaison()).onFailure()
.recoverWithNull()
.call(___ -> setMembre(form.new Member(3), club, req.getSaison()))))
.onItem()
.invoke(model -> Uni.createFrom()
.future(Utils.replacePhoto(form.getId(), form.getLogo(), media,
"aff_request/logo")))
.onItem()
.invoke(model -> Uni.createFrom()
.future(Utils.replacePhoto(form.getId(), form.getStatus(), media,
"aff_request/status")))
.call(model -> Utils.moveMedia(form.getId(), model.getId(), media, "aff_request/logo",
"ppClub"))
.call(model -> Utils.moveMedia(form.getId(), model.getId(), media, "aff_request/status",
"clubStatus"))
)
.map(__ -> "Ok");
}
private Uni<ClubModel> acceptNew(AffiliationRequestSaveForm form, AffiliationRequestModel model) {
LOGGER.debug("New Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
ClubModel club = new ClubModel();
club.setName(form.getName());
club.setCountry("FR");
club.setStateId(form.getState_id());
club.setAddress(form.getAddress());
club.setContact_intern(form.getContact());
club.setAffiliations(new ArrayList<>());
return Panache.withTransaction(() -> clubRepository.persist(club)
.chain(c -> sequenceRepository.getNextValueInTransaction(SequenceType.Affiliation)
.invoke(c::setNo_affiliation)
.chain(() -> clubRepository.persist(c))
.chain(() -> repositoryRequest.delete(model))
)
.chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison())))
.map(c -> club));
})
.call(club -> reactiveMailer.send(
Mail.withText(form.getM1_email(),
"FFSAF - Acceptation de votre demande d'affiliation",
String.format(
"""
Bonjour,
Votre demande d'affiliation pour le club %s a été acceptée.
Le numéro d'affiliation de votre club est le %d.
Cordialement,
L'équipe de la FFSAF
""", club.getName(), club.getNo_affiliation())
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
.addTo(form.getM2_email(), form.getM3_email())
));
}
private Uni<ClubModel> acceptOld(AffiliationRequestSaveForm form, AffiliationRequestModel model, ClubModel club) {
AtomicBoolean nameChange = new AtomicBoolean(false);
LOGGER.debug("Old Club Accepted");
return Uni.createFrom().nullItem()
.chain(() -> {
if (!form.getName().equals(club.getName())) {
club.setName(form.getName());
nameChange.set(true);
}
club.setCountry("FR");
club.setStateId(form.getState_id());
club.setAddress(form.getAddress());
club.setContact_intern(form.getContact());
return Panache.withTransaction(() -> clubRepository.persist(club)
.chain(() -> repository.persist(new AffiliationModel(null, club, model.getSaison())))
.chain(() -> repositoryRequest.delete(model)))
.call(() -> nameChange.get() ? keycloakService.updateGroupFromClub(
club) // update group in keycloak
: Uni.createFrom().nullItem());
})
.map(__ -> club);
}
public Uni<SimpleReqAffiliation> getRequest(long id) {
return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel)
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.call(out -> clubRepository.find("StateId = ?1", out.getStateId()).firstResult().invoke(c -> {
if (c != null) {
out.setClub(c.getId());
out.setClub_name(c.getName());
out.setClub_no_aff(c.getNo_affiliation());
}
})
);
}
public Uni<List<SimpleAffiliation>> getCurrentSaisonAffiliation() {
return repositoryRequest.list("saison = ?1 or saison = ?1 + 1", Utils.getSaison())
.map(models -> models.stream()
.map(model -> new SimpleAffiliation(model.getId() * -1, model.getState_id(), model.getSaison(),
false)).toList())
.chain(aff -> repository.list("saison = ?1", Utils.getSaison())
.map(models -> models.stream().map(SimpleAffiliation::fromModel).toList())
.map(aff2 -> Stream.concat(aff2.stream(), aff.stream()).toList())
);
}
public Uni<List<SimpleAffiliation>> getAffiliation(long id) {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.chain(model -> repositoryRequest.list("state_id = ?1", model.getStateId())
.map(reqs -> reqs.stream().map(req ->
new SimpleAffiliation(req.getId() * -1, model.getStateId(), req.getSaison(), false)))
.map(aff2 -> Stream.concat(aff2,
model.getAffiliations().stream().map(SimpleAffiliation::fromModel)).toList())
);
}
public Uni<SimpleAffiliation> setAffiliation(long id, int saison) {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.invoke(Unchecked.consumer(club -> {
if (club.getAffiliations().stream().anyMatch(affiliation -> affiliation.getSaison() == saison)) {
throw new DBadRequestException("Affiliation déjà existante");
}
}))
.chain(club ->
Panache.withTransaction(() -> repository.persist(new AffiliationModel(null, club, saison))
.chain(c -> (club.getNo_affiliation() != null) ? Uni.createFrom().item(c) :
sequenceRepository.getNextValueInTransaction(SequenceType.Affiliation)
.invoke(club::setNo_affiliation)
.chain(() -> clubRepository.persist(club))
.map(o -> c)
)))
.map(SimpleAffiliation::fromModel);
}
public Uni<?> deleteAffiliation(long id) {
return Panache.withTransaction(() -> repository.deleteById(id));
}
public Uni<?> deleteReqAffiliation(long id, String reason, boolean federationAdmin) {
return repositoryRequest.findById(id)
.call(aff -> federationAdmin ? reactiveMailer.send(
Mail.withText(aff.getM1_email(),
"FFSAF - Votre demande d'affiliation a été rejetée.",
String.format(
"""
Bonjour,
Votre demande d'affiliation pour le club %s a été rejetée pour la/les raison(s) suivante(s):
%s
Si vous rencontrez un problème ou si vous avez des questions, n'hésitez pas à nous contacter à l'adresse contact@ffsaf.fr.
Cordialement,
L'équipe de la FFSAF
""", aff.getName(), reason)
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
.addTo(aff.getM2_email(), aff.getM3_email())
) : Uni.createFrom().nullItem())
.chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff)))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/logo"))
.call(__ -> Utils.deleteMedia(id, media, "aff_request/status"));
}
} }

View File

@ -1,266 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.model.CategoryModel;
import fr.titionfire.ffsaf.data.model.TreeModel;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.data.CategoryData;
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
import fr.titionfire.ffsaf.rest.data.TreeData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class CategoryService {
@Inject
CategoryRepository repository;
@Inject
CompetitionRepository competRepository;
@Inject
MatchRepository matchRepository;
@Inject
TreeRepository treeRepository;
@Inject
CombRepository combRepository;
@Inject
CompetPermService permService;
public Uni<CategoryData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system)
.firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
.map(CategoryData::fromModel);
}
public Uni<List<CategoryData>> getAllAdmin(SecurityCtx securityCtx, CompetitionSystem system) {
return permService.getAllHaveAdminAccess(securityCtx)
.chain(ids -> repository.list("system = ?1 AND compet.id IN ?2", system, ids))
.map(pouleModels -> pouleModels.stream().map(CategoryData::fromModel).toList());
}
public Uni<CategoryData> addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, CategoryData data) {
return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult()
.chain(o -> {
if (o == null) {
return competRepository.findById(data.getCompet())
.onItem().ifNull().failWith(() -> new RuntimeException("Competition not found"))
.call(o2 -> permService.hasEditPerm(securityCtx, o2))
.chain(competitionModel -> {
CategoryModel model = new CategoryModel();
model.setId(null);
model.setSystem(system);
model.setSystemId(data.getId());
model.setCompet(competitionModel);
model.setName(data.getName());
model.setMatchs(new ArrayList<>());
model.setTree(new ArrayList<>());
model.setType(data.getType());
return Panache.withTransaction(() -> repository.persist(model));
});
} else {
o.setName(data.getName());
o.setType(data.getType());
return Panache.withTransaction(() -> repository.persist(o));
}
}).map(CategoryData::fromModel);
}
private MatchModel findMatch(List<MatchModel> matchModelList, Long id) {
return matchModelList.stream().filter(m -> m.getSystemId().equals(id))
.findFirst().orElse(null);
}
private TreeModel findNode(List<TreeModel> node, Long match_id) {
return node.stream().filter(m -> m.getMatch().getSystemId().equals(match_id))
.findFirst().orElse(null);
}
private void flatTreeChild(TreeModel current, ArrayList<TreeModel> node) {
if (current != null) {
node.add(current);
flatTreeChild(current.getLeft(), node);
flatTreeChild(current.getRight(), node);
}
}
private void flatTreeChild(TreeData current, ArrayList<TreeData> node) {
if (current != null) {
node.add(current);
flatTreeChild(current.getLeft(), node);
flatTreeChild(current.getRight(), node);
}
}
private Uni<TreeModel> persisteTree(TreeData data, List<TreeModel> node, CategoryModel poule,
List<MatchModel> matchModelList) {
TreeModel mm = findNode(node, data.getMatch());
if (mm == null) {
mm = new TreeModel();
mm.setId(null);
}
mm.setLevel(data.getLevel());
mm.setCategory(poule.getId());
mm.setMatch(findMatch(matchModelList, data.getMatch()));
return Uni.createFrom().item(mm)
.call(o -> (data.getLeft() == null ? Uni.createFrom().nullItem().invoke(o1 -> o.setLeft(null)) :
persisteTree(data.getLeft(), node, poule, matchModelList).invoke(o::setLeft)))
.call(o -> (data.getRight() == null ? Uni.createFrom().nullItem().invoke(o1 -> o.setRight(null)) :
persisteTree(data.getRight(), node, poule, matchModelList).invoke(o::setRight)))
.chain(o -> Panache.withTransaction(() -> treeRepository.persist(o)));
}
public Uni<?> syncCategory(SecurityCtx securityCtx, CompetitionSystem system, CategoryFullData data) {
return repository.find("systemId = ?1 AND system = ?2", data.getId(), system)
.firstResult()
.onItem().ifNotNull().call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet()))
.onItem().ifNull().switchTo(
() -> competRepository.findById(data.getCompet())
.onItem().ifNull().failWith(() -> new RuntimeException("Compet not found"))
.call(o -> permService.hasEditPerm(securityCtx, o))
.map(o -> {
CategoryModel model = new CategoryModel();
model.setId(null);
model.setSystem(system);
model.setSystemId(data.getId());
model.setMatchs(new ArrayList<>());
model.setTree(new ArrayList<>());
model.setCompet(o);
return model;
}))
.call(o -> Mutiny.fetch(o.getMatchs()))
.call(o -> Mutiny.fetch(o.getTree()))
.map(o -> {
o.setName(data.getName());
o.setType(data.getType());
WorkData workData = new WorkData();
workData.category = o;
return workData;
})
.call(o -> Panache.withTransaction(() -> repository.persist(o.category)))
.call(o -> (data.getMatches() == null || data.getMatches().isEmpty()) ? Uni.createFrom().nullItem() :
Uni.createFrom()
.item(data.getMatches().stream().flatMap(m -> Stream.of(m.getC1_id(), m.getC2_id())
.filter(Objects::nonNull)).distinct().toList())
.chain(ids -> ids.isEmpty() ? Uni.createFrom().nullItem()
: combRepository.list("id IN ?1", ids)
.invoke(o2 -> o2.forEach(m -> o.membres.put(m.getId(), m)))
)
)
.invoke(in -> {
ArrayList<TreeModel> node = new ArrayList<>();
for (TreeModel treeModel : in.category.getTree())
flatTreeChild(treeModel, node);
ArrayList<TreeData> new_node = new ArrayList<>();
for (TreeData treeModel : data.getTrees())
flatTreeChild(treeModel, new_node);
in.toRmNode = node.stream().filter(m -> new_node.stream()
.noneMatch(m2 -> m2.getMatch().equals(m.getMatch().getSystemId())))
.map(TreeModel::getId).toList();
in.unlinkNode = node;
in.unlinkNode.forEach(n -> {
n.setRight(null);
n.setLeft(null);
});
in.toRmMatch = in.category.getMatchs().stream()
.filter(m -> data.getMatches().stream().noneMatch(m2 -> m2.getId().equals(m.getSystemId())))
.map(MatchModel::getId).toList();
})
.call(in -> in.unlinkNode.isEmpty() ? Uni.createFrom().nullItem() :
Panache.withTransaction(() -> treeRepository.persist(in.unlinkNode)))
.call(in -> in.toRmNode.isEmpty() ? Uni.createFrom().nullItem() :
Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in.toRmNode)))
.call(in -> in.toRmMatch.isEmpty() ? Uni.createFrom().nullItem() :
Panache.withTransaction(() -> Uni.join().all(
in.toRmMatch.stream().map(l -> matchRepository.deleteById(l)).toList())
.andCollectFailures()))
.call(in -> data.getMatches().isEmpty() ? Uni.createFrom().nullItem() :
Uni.join().all(
data.getMatches().stream().map(m -> {
MatchModel mm = findMatch(in.category.getMatchs(), m.getId());
if (mm == null) {
mm = new MatchModel();
mm.setId(null);
mm.setSystem(system);
mm.setSystemId(m.getId());
}
mm.setCategory(in.category);
mm.setCategory_ord(m.getCategory_ord());
mm.setC1_str(m.getC1_str());
mm.setC2_str(m.getC2_str());
mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null));
mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null));
mm.setEnd(m.isEnd());
mm.setPoule(m.getPoule());
mm.getScores().clear();
mm.getScores().addAll(m.getScores());
MatchModel finalMm = mm;
return Panache.withTransaction(() -> matchRepository.persist(finalMm)
.invoke(o -> in.match.add(o)));
}).toList())
.andCollectFailures())
.call(in -> data.getTrees().isEmpty() ? Uni.createFrom().nullItem() :
Uni.join().all(data.getTrees().stream()
.map(m -> persisteTree(m, in.category.getTree(), in.category, in.match)).toList())
.andCollectFailures())
.map(__ -> "OK");
}
private static class WorkData {
CategoryModel category;
HashMap<Long, MembreModel> membres = new HashMap<>();
List<MatchModel> match = new ArrayList<>();
List<Long> toRmMatch;
List<TreeModel> unlinkNode;
List<Long> toRmNode;
}
public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
.call(o -> permService.hasEditPerm(securityCtx, o.getCompet()))
.call(o -> Mutiny.fetch(o.getTree())
.call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :
Uni.createFrom().item(o2.stream().peek(m -> {
m.setRight(null);
m.setLeft(null);
}).toList())
.call(in -> Panache.withTransaction(() -> treeRepository.persist(in)))
.map(in -> in.stream().map(TreeModel::getId).toList())
.call(in -> in.isEmpty() ? Uni.createFrom().nullItem() :
Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in)))
)
)
.call(o -> matchRepository.delete("poule.id = ?1", o.getId()))
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())));
}
}

View File

@ -1,175 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CheckoutModel;
import fr.titionfire.ffsaf.data.model.LogModel;
import fr.titionfire.ffsaf.data.repository.CheckoutRepository;
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.rest.client.HelloAssoService;
import fr.titionfire.ffsaf.rest.client.dto.CheckoutIntentsRequest;
import fr.titionfire.ffsaf.rest.client.dto.CheckoutIntentsResponse;
import fr.titionfire.ffsaf.rest.client.dto.CheckoutMetadata;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.scheduler.Scheduled;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@WithSession
@ApplicationScoped
public class CheckoutService {
@Inject
CheckoutRepository repository;
@Inject
LicenceRepository licenceRepository;
@Inject
MembreService membreService;
@Inject
LicenceService licenceService;
@Inject
LoggerService ls;
@RestClient
HelloAssoService helloAssoService;
@ConfigProperty(name = "frontRootUrl")
String frontRootUrl;
@ConfigProperty(name = "unitLicencePrice")
int unitLicencePrice;
@ConfigProperty(name = "helloasso.organizationSlug")
String organizationSlug;
public Uni<Boolean> canDeleteLicence(long id) {
return repository.find("?1 IN licenseIds", id).count().map(count -> count == 0);
}
public Uni<String> create(List<Long> ids, SecurityCtx securityCtx) {
return membreService.getByAccountId(securityCtx.getSubject())
.call(membreModel -> Mutiny.fetch(membreModel.getClub()))
.chain(membreModel -> {
CheckoutModel model = new CheckoutModel();
model.setMembre(membreModel);
model.setLicenseIds(ids);
model.setPaymentStatus(CheckoutModel.PaymentStatus.UNKNOW);
return Panache.withTransaction(() -> repository.persist(model));
})
.chain(checkoutModel -> {
CheckoutIntentsRequest request = new CheckoutIntentsRequest();
request.setTotalAmount(unitLicencePrice * checkoutModel.getLicenseIds().size());
request.setInitialAmount(unitLicencePrice * checkoutModel.getLicenseIds().size());
request.setItemName("%d licences %d-%d pour %s".formatted(checkoutModel.getLicenseIds().size(),
Utils.getSaison(), Utils.getSaison() + 1, checkoutModel.getMembre().getClub().getName()));
request.setBackUrl(frontRootUrl + "/club/member/pay");
request.setErrorUrl(frontRootUrl + "/club/member/pay/error");
request.setReturnUrl(frontRootUrl + "/club/member/pay/return");
request.setContainsDonation(false);
request.setPayer(new CheckoutIntentsRequest.Payer(checkoutModel.getMembre().getFname(),
checkoutModel.getMembre().getLname(), checkoutModel.getMembre().getEmail()));
request.setMetadata(new CheckoutMetadata(checkoutModel.getId()));
return helloAssoService.checkout(organizationSlug, request)
.call(response -> {
checkoutModel.setCheckoutId(response.getId());
return Panache.withTransaction(() -> repository.persist(checkoutModel));
});
})
.onFailure().transform(t -> new DInternalError(t.getMessage()))
.map(CheckoutIntentsResponse::getRedirectUrl);
}
public Uni<Response> paymentStatusChange(String state, CheckoutMetadata metadata) {
return repository.findById(metadata.getCheckoutDBId())
.chain(checkoutModel -> {
CheckoutModel.PaymentStatus newStatus = CheckoutModel.PaymentStatus.valueOf(state.toUpperCase());
Uni<?> uni = Uni.createFrom().nullItem();
if (checkoutModel.getPaymentStatus().equals(newStatus))
return uni;
if (newStatus.equals(CheckoutModel.PaymentStatus.AUTHORIZED)) {
for (Long id : checkoutModel.getLicenseIds()) {
uni = uni.chain(__ -> licenceRepository.findById(id)
.onFailure().recoverWithNull()
.call(licenceModel -> {
if (licenceModel == null) {
ls.logAnonymous(LogModel.ActionType.UPDATE, LogModel.ObjectType.Licence,
"Fail to save payment for licence (checkout n°" + checkoutModel.getCheckoutId() + ")",
"", id);
return Uni.createFrom().nullItem();
}
ls.logUpdateAnonymous("Paiement de la licence", licenceModel);
licenceModel.setPay(true);
if (licenceModel.getCertificate() != null && licenceModel.getCertificate()
.length() > 3) {
if (!licenceModel.isValidate())
ls.logUpdateAnonymous("Validation automatique de la licence",
licenceModel);
return licenceService.validateLicences(licenceModel);
} else {
return Panache.withTransaction(
() -> licenceRepository.persist(licenceModel));
}
}));
}
} else if (checkoutModel.getPaymentStatus().equals(CheckoutModel.PaymentStatus.AUTHORIZED)) {
for (Long id : checkoutModel.getLicenseIds()) {
uni = uni.chain(__ -> licenceRepository.findById(id)
.onFailure().recoverWithNull()
.call(licenceModel -> {
if (licenceModel == null)
return Uni.createFrom().nullItem();
ls.logUpdateAnonymous("Annulation automatique du paiement de la licence",
licenceModel);
licenceModel.setPay(false);
if (licenceModel.isValidate())
ls.logUpdateAnonymous(
"Annulation automatique de la validation de la licence",
licenceModel);
licenceModel.setValidate(false);
return Panache.withTransaction(() -> licenceRepository.persist(licenceModel));
}));
}
}
uni = uni.call(__ -> ls.append());
checkoutModel.setPaymentStatus(newStatus);
return uni.chain(__ -> Panache.withTransaction(() -> repository.persist(checkoutModel)));
})
.onFailure().invoke(Throwable::printStackTrace)
.map(__ -> Response.ok().build());
}
@Scheduled(cron = "0 0 * * * ?")
Uni<Void> everyHours() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, -1);
Date dateLimit = calendar.getTime();
return repository.delete("creationDate < ?1 AND (checkoutId IS NULL OR paymentStatus = ?2)", dateLimit,
CheckoutModel.PaymentStatus.UNKNOW)
.map(__ -> null);
}
}

View File

@ -1,41 +1,17 @@
package fr.titionfire.ffsaf.domain.service; package fr.titionfire.ffsaf.domain.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import fr.titionfire.ffsaf.data.model.AffiliationModel;
import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.ClubRepository; import fr.titionfire.ffsaf.data.repository.ClubRepository;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel; import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
import fr.titionfire.ffsaf.net2.request.SReqClub;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.rest.from.FullClubForm;
import fr.titionfire.ffsaf.rest.from.PartClubForm;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.PanacheQuery;
import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.vertx.VertxContextSupport; import io.quarkus.vertx.VertxContextSupport;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.*; import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.List;
import java.util.function.Consumer;
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
@ -44,30 +20,13 @@ public class ClubService {
@Inject @Inject
ClubRepository repository; ClubRepository repository;
@Inject
ServerCustom serverCustom;
@Inject
CombRepository combRepository;
@Inject
KeycloakService keycloakService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
LoggerService ls;
public SimpleClubModel findByIdOptionalClub(long id) throws Throwable { public SimpleClubModel findByIdOptionalClub(long id) throws Throwable {
return VertxContextSupport.subscribeAndAwait( return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
} }
public Collection<SimpleClubModel> findAllClub() throws Throwable { public Collection<SimpleClubModel> findAllClub() throws Throwable {
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction( return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(
() -> repository.findAll().list() () -> repository.findAll().list().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
.map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
} }
public Uni<List<ClubModel>> getAll() { public Uni<List<ClubModel>> getAll() {
@ -76,276 +35,8 @@ public class ClubService {
public Uni<?> setClubId(Long id, String id1) { public Uni<?> setClubId(Long id, String id1) {
return repository.findById(id).chain(clubModel -> { return repository.findById(id).chain(clubModel -> {
ls.logChange("KC UUID", clubModel.getClubId(), id1, clubModel);
clubModel.setClubId(id1); clubModel.setClubId(id1);
return Panache.withTransaction(() -> repository.persist(clubModel)) return Panache.withTransaction(() -> repository.persist(clubModel));
.call(() -> ls.append());
}); });
} }
public Uni<PageResult<SimpleClubList>> search(Integer limit, int page, String search, String country) {
if (search == null)
search = "";
search = search + "%";
PanacheQuery<ClubModel> query;
if (country == null || country.isBlank())
query = repository.find("LOWER(name) LIKE LOWER(?1)",
Sort.ascending("name"), search).page(Page.ofSize(limit));
else
query = repository.find("LOWER(name) LIKE LOWER(?1) AND country LIKE ?2",
Sort.ascending("name"), search, country + "%").page(Page.ofSize(limit));
return getPageResult(query, limit, page);
}
private Uni<PageResult<SimpleClubList>> getPageResult(PanacheQuery<ClubModel> query, int limit, int page) {
return Uni.createFrom().item(new PageResult<SimpleClubList>())
.invoke(result -> result.setPage(page))
.invoke(result -> result.setPage_size(limit))
.call(result -> query.count().invoke(result::setResult_count))
.call(result -> query.pageCount()
.invoke(Unchecked.consumer(pages -> {
if (page > pages) throw new DBadRequestException("Page out of range");
}))
.invoke(result::setPage_count))
.call(result -> query.page(Page.of(page, limit)).list()
.map(membreModels -> membreModels.stream().map(SimpleClubList::fromModel).toList())
.invoke(result::setResult));
}
public Uni<ClubModel> getById(long id) {
return repository.findById(id).call(m -> Mutiny.fetch(m.getContact()));
}
public Uni<ClubModel> getByClubId(String clubId) {
return repository.find("clubId", clubId).firstResult();
}
public Uni<ClubModel> getOfUser(SecurityCtx securityCtx) {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
}))
.map(MembreModel::getClub)
.call(club -> Mutiny.fetch(club.getContact()));
}
public Uni<List<DeskMember>> getClubDesk(Consumer<ClubModel> consumer, long id) {
return repository.findById(id).invoke(consumer)
.chain(club -> combRepository.list("club = ?1", club))
.map(combs -> combs.stream()
.filter(o -> o.getRole() != null && o.getRole().level >= RoleAsso.MEMBREBUREAU.level)
.sorted((o1, o2) -> o2.getRole().level - o1.getRole().level)
.map(DeskMember::fromModel)
.toList());
}
public Uni<List<VerySimpleMembre>> getMembers(SecurityCtx securityCtx) {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
if (!securityCtx.isInClubGroup(m.getClub().getId()))
throw new DForbiddenException();
}))
.chain(m -> combRepository.list("club = ?1", m.getClub()))
.map(membreModels -> membreModels.stream()
.map(m -> new VerySimpleMembre(m.getLname(), m.getFname(), m.getLicence())).toList());
}
public Uni<String> updateOfUser(SecurityCtx securityCtx, PartClubForm form) {
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
};
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
if (!securityCtx.isInClubGroup(m.getClub().getId()))
throw new DForbiddenException();
}))
.map(MembreModel::getClub)
.call(club -> Mutiny.fetch(club.getContact()))
.chain(Unchecked.function(club -> {
ls.logChange("Contact interne", club.getContact_intern(), form.getContact_intern(), club);
club.setContact_intern(form.getContact_intern());
ls.logChange("Adresse administrative", club.getAddress(), form.getAddress(), club);
club.setAddress(form.getAddress());
try {
if (!Objects.equals(club.getContact(), MAPPER.readValue(form.getContact(), typeRef)))
ls.logUpdate("Contact(s)...", club);
club.setContact(MAPPER.readValue(form.getContact(), typeRef));
} catch (JsonProcessingException e) {
throw new DBadRequestException("Erreur de format des contacts");
}
ls.logChange("Lieux d'entrainements", club.getTraining_location(), form.getTraining_location(),
club);
club.setTraining_location(form.getTraining_location());
ls.logChange("Horaires d'entrainements", club.getTraining_day_time(), form.getTraining_day_time(),
club);
club.setTraining_day_time(form.getTraining_day_time());
return Panache.withTransaction(() -> repository.persist(club)).call(() -> ls.append());
}))
.map(__ -> "OK");
}
public Uni<String> update(long id, FullClubForm input) {
AtomicBoolean nameChange = new AtomicBoolean(false);
return repository.findById(id).call(m -> Mutiny.fetch(m.getContact()))
.onItem().transformToUni(Unchecked.function(m -> {
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
};
if (!input.getName().equals(m.getName())) {
m.setName(input.getName());
nameChange.set(true);
}
m.setCountry(input.getCountry());
m.setInternational(input.isInternational());
if (!input.isInternational()) {
ls.logChange("Lieux d'entrainements", m.getTraining_location(), input.getTraining_location(),
m);
m.setTraining_location(input.getTraining_location());
ls.logChange("Horaires d'entrainements", m.getTraining_day_time(), input.getTraining_day_time(),
m);
m.setTraining_day_time(input.getTraining_day_time());
ls.logChange("Contact interne", m.getContact_intern(), input.getContact_intern(), m);
m.setContact_intern(input.getContact_intern());
if (input.getState_id() != null && !input.getState_id().isBlank()) {
ls.logChange("N° SIRET", m.getClubId(), input.getState_id(), m);
m.setStateId(input.getState_id());
}
ls.logChange("Adresse administrative", m.getAddress(), input.getAddress(), m);
m.setAddress(input.getAddress());
try {
if (!Objects.equals(m.getContact(), MAPPER.readValue(input.getContact(), typeRef)))
ls.logUpdate("Contact(s)...", m);
m.setContact(MAPPER.readValue(input.getContact(), typeRef));
} catch (JsonProcessingException e) {
throw new DBadRequestException("Erreur de format des contacts");
}
}
return Panache.withTransaction(() -> repository.persist(m)).call(() -> ls.append());
}))
.call(clubModel -> nameChange.get() ? keycloakService.updateGroupFromClub(clubModel) // update group in keycloak
: Uni.createFrom().nullItem())
.invoke(membreModel -> SReqClub.sendIfNeed(serverCustom.clients,
SimpleClubModel.fromModel(membreModel)))
.map(__ -> "OK");
}
public Uni<Long> add(FullClubForm input) {
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
};
return Uni.createFrom().nullItem()
.chain(() -> {
ClubModel clubModel = new ClubModel();
clubModel.setName(input.getName());
clubModel.setCountry(input.getCountry());
clubModel.setInternational(input.isInternational());
clubModel.setNo_affiliation(null);
if (!input.isInternational()) {
clubModel.setTraining_location(input.getTraining_location());
clubModel.setTraining_day_time(input.getTraining_day_time());
clubModel.setContact_intern(input.getContact_intern());
if (input.getState_id() != null && !input.getState_id().isBlank())
clubModel.setStateId(input.getState_id());
clubModel.setAddress(input.getAddress());
try {
clubModel.setContact(MAPPER.readValue(input.getContact(), typeRef));
} catch (JsonProcessingException ignored) {
}
}
return Panache.withTransaction(() -> repository.persist(clubModel));
})
.call(clubModel -> ls.logAAdd(clubModel))
.call(clubModel -> keycloakService.getGroupFromClub(clubModel)) // create group in keycloak
.invoke(clubModel -> SReqClub.sendAddIfNeed(serverCustom.clients, SimpleClubModel.fromModel(clubModel)))
.map(ClubModel::getId);
}
public Uni<?> delete(long id) {
return repository.findById(id)
.chain(club -> combRepository.list("club = ?1", club)
.map(combModels -> combModels.stream().peek(combModel -> {
combModel.setClub(null);
combModel.setRole(RoleAsso.MEMBRE);
}).toList())
.call(list -> (list.isEmpty()) ? Uni.createFrom().voidItem() :
Uni.join().all(list.stream().filter(m -> m.getUserId() != null)
.map(m -> keycloakService.clearUser(m.getUserId())).toList())
.andCollectFailures())
.chain(list -> Panache.withTransaction(() -> combRepository.persist(list)))
.map(o -> club)
)
.call(clubModel -> (clubModel.getClubId() == null) ? Uni.createFrom()
.voidItem() : keycloakService.removeClubGroup(clubModel.getClubId()))
.invoke(membreModel -> SReqClub.sendRmIfNeed(serverCustom.clients, id))
.call(clubModel -> ls.logADelete(clubModel))
.chain(clubModel -> Panache.withTransaction(() -> repository.delete(clubModel)))
.call(__ -> Utils.deleteMedia(id, media, "ppClub"))
.call(__ -> Utils.deleteMedia(id, media, "clubStatus"));
}
public Uni<RenewAffData> getRenewData(long id, List<Long> mIds) {
RenewAffData data = new RenewAffData();
return repository.findById(id)
.call(clubModel -> Mutiny.fetch(clubModel.getAffiliations()))
.invoke(clubModel -> {
data.setName(clubModel.getName());
data.setState_id(clubModel.getStateId());
data.setAddress(clubModel.getAddress());
data.setContact(clubModel.getContact_intern());
data.setSaison(
clubModel.getAffiliations().stream().max(Comparator.comparing(AffiliationModel::getSaison))
.map(AffiliationModel::getSaison).map(i -> Math.min(i + 1, Utils.getSaison() + 1))
.orElse(Utils.getSaison()));
})
.chain(club -> combRepository.list("id IN ?1", mIds))
.invoke(combs -> data.setMembers(combs.stream()
.filter(o -> o.getRole() != null && o.getRole().level >= RoleAsso.MEMBREBUREAU.level)
.sorted((o1, o2) -> o2.getRole().level - o1.getRole().level)
.map(RenewAffData.RenewMember::new)
.toList()))
.map(o -> data);
}
public Uni<List<ClubMapData>> getMapData() {
return repository.list("international", false).toMulti().flatMap(list -> Multi.createFrom().iterable(list))
.call(clubModel -> Mutiny.fetch(clubModel.getContact()))
.map(clubModel -> {
ClubMapData data = new ClubMapData();
data.setName(clubModel.getName());
data.setUuid(clubModel.getClubId());
if (clubModel.getTraining_location() != null) {
try {
MAPPER.readTree(clubModel.getTraining_location()).forEach(l -> {
ClubMapData.Location loc = new ClubMapData.Location();
loc.setLat(l.get("lat").asDouble());
loc.setLng(l.get("lng").asDouble());
loc.setAddr(l.get("text").asText());
data.training_location.add(loc);
});
} catch (JsonProcessingException ignored) {
}
}
data.setTraining_day_time(clubModel.getTraining_day_time());
data.setContact(clubModel.getContact());
return data;
}).collect().asList();
}
} }

View File

@ -1,256 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.RegisterRepository;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.RegisterMode;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ApplicationScoped
public class CompetPermService {
@Inject
ServerCustom serverCustom;
@Inject
CompetitionRepository competitionRepository;
@Inject
@CacheName("safca-config")
Cache cache;
@Inject
@CacheName("safca-have-access")
Cache cacheAccess;
@Inject
@CacheName("have-access")
Cache cacheNoneAccess;
@Inject
RegisterRepository registerRepository;
public Uni<SimpleCompet> getSafcaConfig(long id) {
return cache.get(id, k -> {
CompletableFuture<SimpleCompet> f = new CompletableFuture<>();
SReqCompet.getConfig(serverCustom.clients, id, f);
try {
return f.get(1500, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
});
}
public Uni<List<Long>> getAllHaveAdminAccess(SecurityCtx securityCtx) {
ArrayList<Long> out = new ArrayList<>();
Uni<HashMap<Long, String>> safca = cacheAccess.getAsync(securityCtx.getSubject(),
k -> competitionRepository.list("system = ?1", CompetitionSystem.SAFCA)
.chain(competitionModels -> {
CompletableFuture<HashMap<String, String>> f = new CompletableFuture<>();
SReqCompet.getAllHaveAccess(serverCustom.clients, securityCtx.getSubject(), f);
return Uni.createFrom().future(f, Duration.ofMillis(1500))
.map(map_ -> {
HashMap<Long, String> map = new HashMap<>();
map_.forEach((key, value) -> map.put(Long.parseLong(key), value));
for (CompetitionModel model : competitionModels) {
if (model.getOwner().equals(securityCtx.getSubject()))
map.putIfAbsent(model.getId(), "owner");
else if (securityCtx.roleHas("federation_admin")
|| securityCtx.roleHas("safca_super_admin"))
map.putIfAbsent(model.getId(), "admin");
}
return map;
});
}))
.onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));
Uni<HashMap<Long, String>> none = cacheNoneAccess.getAsync(securityCtx.getSubject(),
k -> competitionRepository.list("system = ?1", CompetitionSystem.NONE)
.map(competitionModels -> {
HashMap<Long, String> map = new HashMap<>();
for (CompetitionModel model : competitionModels) {
if (model.getOwner().equals(securityCtx.getSubject()))
map.putIfAbsent(model.getId(), "owner");
else if (securityCtx.roleHas("federation_admin"))
map.putIfAbsent(model.getId(), "admin");
else if (securityCtx.isInClubGroup(model.getClub().getId()) && (securityCtx.roleHas(
"club_president")
|| securityCtx.roleHas("club_respo_intra") || securityCtx.roleHas(
"club_secretaire")
|| securityCtx.roleHas("club_tresorier")))
map.putIfAbsent(model.getId(), "admin");
}
return map;
}));
return safca.invoke(map ->
map.forEach((k, v) -> {
if (v.equals("owner") || v.equals("admin"))
out.add(k);
})
)
.call(__ -> none.invoke(map ->
map.forEach((k, v) -> {
if (v.equals("owner") || v.equals("admin"))
out.add(k);
})
))
.map(__ -> out.stream().distinct().toList());
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm
*/
public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
return hasViewPerm(securityCtx, Uni.createFrom().item(competitionModel));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm
*/
public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, long id) {
return hasViewPerm(securityCtx, competitionRepository.findById(id));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has view perm
*/
public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
return in.call(cm -> (cm.isPublicVisible() || cm.getRegisterMode() == RegisterMode.FREE
|| cm.getRegisterMode() == RegisterMode.HELLOASSO
|| (cm.getRegisterMode() == RegisterMode.CLUB_ADMIN && securityCtx.isClubAdmin())) ?
Uni.createFrom().nullItem() :
hasAdminViewPerm(securityCtx, cm).onFailure()
.recoverWithUni(__ ->
registerRepository.count("membre.userId = ?1 AND competition = ?2",
securityCtx.getSubject(), cm).map(Unchecked.function(c -> {
if (c == 0)
throw new DForbiddenException();
return cm;
}))
));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm
*/
public Uni<CompetitionModel> hasAdminViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
return hasAdminViewPerm(securityCtx, Uni.createFrom().item(competitionModel));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm
*/
public Uni<CompetitionModel> hasAdminViewPerm(SecurityCtx securityCtx, long id) {
return hasAdminViewPerm(securityCtx, competitionRepository.findById(id));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has admin view perm
*/
public Uni<CompetitionModel> hasAdminViewPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
return in.call(Unchecked.function(o -> {
if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin"))
return Uni.createFrom().nullItem();
if (o.getSystem() == CompetitionSystem.SAFCA)
return hasSafcaViewPerm(securityCtx, o.getId());
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
throw new DForbiddenException();
if (o.getSystem() == CompetitionSystem.NONE)
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
return Uni.createFrom().nullItem();
throw new DForbiddenException();
})
);
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
*/
public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
return hasEditPerm(securityCtx, Uni.createFrom().item(competitionModel));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
*/
public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, long id) {
return hasEditPerm(securityCtx, competitionRepository.findById(id));
}
/**
* @return {@link fr.titionfire.ffsaf.data.model.CompetitionModel} if securityCtx has edit perm
*/
public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
return in.call(Unchecked.function(o -> {
if (securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin"))
return Uni.createFrom().nullItem();
if (o.getSystem() == CompetitionSystem.SAFCA)
return hasSafcaEditPerm(securityCtx, o.getId());
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
throw new DForbiddenException();
if (o.getSystem() == CompetitionSystem.NONE)
if (securityCtx.isClubAdmin())
return Uni.createFrom().nullItem();
throw new DForbiddenException();
})
);
}
private Uni<?> hasSafcaViewPerm(SecurityCtx securityCtx, long id) {
return securityCtx.roleHas("safca_super_admin") ?
Uni.createFrom().nullItem()
:
getSafcaConfig(id).chain(Unchecked.function(o -> {
if (!o.admin().contains(UUID.fromString(securityCtx.getSubject())) && !o.table()
.contains(UUID.fromString(securityCtx.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}));
}
private Uni<?> hasSafcaEditPerm(SecurityCtx securityCtx, long id) {
return securityCtx.roleHas("safca_super_admin") ?
Uni.createFrom().nullItem()
:
getSafcaConfig(id).chain(Unchecked.function(o -> {
if (!o.admin().contains(UUID.fromString(securityCtx.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}));
}
}

View File

@ -1,611 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.HelloAssoRegisterModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.net2.request.SReqRegister;
import fr.titionfire.ffsaf.rest.client.dto.NotificationData;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.RegisterMode;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.reactive.ReactiveMailer;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import io.vertx.mutiny.core.Vertx;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.*;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class CompetitionService {
private static final Logger LOGGER = Logger.getLogger(CompetitionService.class);
@Inject
CompetitionRepository repository;
@Inject
CategoryRepository categoryRepository;
@Inject
MatchRepository matchRepository;
@Inject
RegisterRepository registerRepository;
@Inject
KeycloakService keycloakService;
@Inject
CombRepository combRepository;
@Inject
ServerCustom serverCustom;
@Inject
MembreService membreService;
@Inject
CompetPermService permService;
@Inject
HelloAssoRegisterRepository helloAssoRepository;
@SuppressWarnings("CdiInjectionPointsInspection")
@Inject
ReactiveMailer reactiveMailer;
@Inject
Vertx vertx;
@Inject
@CacheName("safca-config")
Cache cache;
@Inject
@CacheName("safca-have-access")
Cache cacheAccess;
@Inject
@CacheName("have-access")
Cache cacheNoneAccess;
public Uni<CompetitionData> getById(SecurityCtx securityCtx, Long id) {
return permService.hasViewPerm(securityCtx, id).map(CompetitionData::fromModelLight);
}
public Uni<CompetitionData> getByIdAdmin(SecurityCtx securityCtx, Long id) {
if (id == 0) {
return Uni.createFrom()
.item(new CompetitionData(null, "", "", "", "", new Date(), new Date(),
CompetitionSystem.NONE, RegisterMode.FREE, new Date(), new Date(), true,
null, "", "", null, true, "", "", "", ""));
}
return permService.hasAdminViewPerm(securityCtx, id)
.chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc())
.map(insc -> CompetitionData.fromModel(competitionModel).addInsc(insc)))
.chain(data ->
vertx.getOrCreateContext().executeBlocking(() -> {
keycloakService.getUser(UUID.fromString(data.getOwner()))
.ifPresent(user -> data.setOwner(user.getUsername()));
return data;
})
);
}
public Uni<List<CompetitionData>> getAll(SecurityCtx securityCtx) {
List<CompetitionData> out = new ArrayList<>();
return permService.getAllHaveAdminAccess(securityCtx)
.call(ids -> repository.list("id IN ?1", ids)
.invoke(cm -> {
out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList());
out.forEach(competition -> competition.setCanEdit(true));
}))
.call(ids ->
repository.list("id NOT IN ?1 AND (publicVisible = TRUE OR registerMode IN ?2)", ids,
securityCtx.isClubAdmin() ? List.of(RegisterMode.FREE, RegisterMode.HELLOASSO,
RegisterMode.CLUB_ADMIN) : List.of(RegisterMode.FREE, RegisterMode.HELLOASSO))
.invoke(cm -> out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList()))
.call(cm -> registerRepository.list(
"membre.userId = ?1 AND competition.id NOT IN ?2 AND competition NOT IN ?3",
securityCtx.getSubject(), ids, cm)
.chain(registerModels -> {
Uni<Void> uni = Uni.createFrom().nullItem();
for (RegisterModel registerModel : registerModels) {
uni = uni.call(__ -> Mutiny.fetch(registerModel.getCompetition())
.invoke(cm2 -> out.add(CompetitionData.fromModelLight(cm2))));
}
return uni;
})
))
.map(__ -> out);
}
public Uni<List<CompetitionData>> getAllAdmin(SecurityCtx securityCtx) {
return permService.getAllHaveAdminAccess(securityCtx)
.chain(ids -> repository.list("id IN ?1", ids))
.map(pouleModels -> pouleModels.stream().map(CompetitionData::fromModel).toList());
}
public Uni<List<CompetitionData>> getAllSystemAdmin(SecurityCtx securityCtx,
CompetitionSystem system) {
return permService.getAllHaveAdminAccess(securityCtx)
.chain(ids -> repository.list("system = ?1 AND id IN ?2", system, ids))
.map(pouleModels -> pouleModels.stream().map(CompetitionData::fromModel).toList());
}
public Uni<CompetitionData> addOrUpdate(SecurityCtx securityCtx, CompetitionData data) {
if (data.getId() == null) {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(combModel -> {
if (!securityCtx.getRoles().contains("create_compet") && !securityCtx.getRoles()
.contains("federation_admin"))
throw new DForbiddenException("Vous ne pouvez pas créer de compétition");
}))
.map(MembreModel::getClub)
.chain(clubModel -> {
CompetitionModel model = new CompetitionModel();
model.setId(null);
model.setSystem(data.getSystem());
model.setClub(clubModel);
model.setInsc(new ArrayList<>());
model.setUuid(UUID.randomUUID().toString());
model.setOwner(securityCtx.getSubject());
copyData(data, model);
return Panache.withTransaction(() -> repository.persist(model));
}).map(CompetitionData::fromModel)
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem())
.call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem());
} else {
return permService.hasEditPerm(securityCtx, data.getId())
.chain(model -> {
copyData(data, model);
return vertx.getOrCreateContext().executeBlocking(() -> // Update owner
keycloakService.getUser(data.getOwner()).map(UserRepresentation::getId).orElse(null))
.invoke(Unchecked.consumer(newOwner -> {
if (newOwner == null)
throw new DBadRequestException("User " + data.getOwner() + " not found");
if (!newOwner.equals(model.getOwner())) {
if (!securityCtx.roleHas("federation_admin")
&& !securityCtx.roleHas("safca_super_admin")
&& !securityCtx.getSubject().equals(model.getOwner()))
throw new DForbiddenException();
model.setOwner(newOwner);
}
}))
.chain(__ -> Panache.withTransaction(() -> repository.persist(model)));
}).map(CompetitionData::fromModel)
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem())
.call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem());
}
}
private void copyData(CompetitionData data, CompetitionModel model) {
if (model.getBanMembre() == null)
model.setBanMembre(new ArrayList<>());
model.setName(data.getName());
model.setAdresse(data.getAdresse());
model.setDescription(data.getDescription());
model.setDate(data.getDate());
model.setTodate(data.getDate());
model.setPublicVisible(data.isPublicVisible());
model.setStartRegister(data.getStartRegister());
model.setEndRegister(data.getEndRegister());
model.setRegisterMode(data.getRegisterMode());
model.setData1(data.getData1());
model.setData2(data.getData2());
model.setData3(data.getData3());
model.setData4(data.getData4());
}
public Uni<List<SimpleRegisterComb>> getRegister(SecurityCtx securityCtx, Long id, String source) {
if ("admin".equals(source))
return permService.hasEditPerm(securityCtx, id)
.chain(c -> Mutiny.fetch(c.getInsc()))
.onItem().transformToMulti(Multi.createFrom()::iterable)
.onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
.map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences()))
.collect().asList();
if ("club".equals(source))
return Uni.createFrom().nullItem()
.invoke(Unchecked.consumer(__ -> {
if (!securityCtx.isClubAdmin())
throw new DForbiddenException();
}))
.chain(__ -> membreService.getByAccountId(securityCtx.getSubject()))
.chain(model -> registerRepository.list("competition.id = ?1 AND membre.club = ?2", id,
model.getClub()))
.onItem().transformToMulti(Multi.createFrom()::iterable)
.onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
.map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences()))
.collect().asList();
return membreService.getByAccountId(securityCtx.getSubject())
.chain(model -> registerRepository.find("competition.id = ?1 AND membre = ?2", id, model).firstResult()
.map(rm -> rm == null ? List.of() : List.of(SimpleRegisterComb.fromModel(rm, List.of()))));
}
public Uni<SimpleRegisterComb> addRegisterComb(SecurityCtx securityCtx, Long id, RegisterRequestData data,
String source) {
if ("admin".equals(source))
return permService.hasEditPerm(securityCtx, id)
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
.call(combModel -> {
if (c.getBanMembre() == null)
c.setBanMembre(new ArrayList<>());
c.getBanMembre().remove(combModel.getId());
return Panache.withTransaction(() -> repository.persist(c));
})
.chain(combModel -> updateRegister(data, c, combModel, true)))
.chain(r -> Mutiny.fetch(r.getMembre().getLicences())
.map(licences -> SimpleRegisterComb.fromModel(r, licences)));
if ("club".equals(source))
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
if (!(cm.getRegisterMode() == RegisterMode.CLUB_ADMIN || cm.getRegisterMode() == RegisterMode.FREE)
|| !securityCtx.isClubAdmin())
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
}))
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
.invoke(Unchecked.consumer(model -> {
if (!securityCtx.isInClubGroup(model.getClub().getId()))
throw new DForbiddenException();
if (c.getBanMembre().contains(model.getId()))
throw new DForbiddenException(
"Vous n'avez pas le droit d'inscrire ce membre (par décision de l'administrateur de la compétition)");
}))
.chain(combModel -> updateRegister(data, c, combModel, false)))
.chain(r -> Mutiny.fetch(r.getMembre().getLicences())
.map(licences -> SimpleRegisterComb.fromModel(r, licences)));
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
if (cm.getRegisterMode() != RegisterMode.FREE)
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
}))
.chain(c -> membreService.getByAccountId(securityCtx.getSubject())
.invoke(Unchecked.consumer(model -> {
if (c.getBanMembre().contains(model.getId()))
throw new DForbiddenException(
"Vous n'avez pas le droit de vous inscrire (par décision de l'administrateur de la compétition)");
}))
.chain(combModel -> updateRegister(data, c, combModel, false)))
.map(r -> SimpleRegisterComb.fromModel(r, List.of()));
}
private Uni<RegisterModel> updateRegister(RegisterRequestData data, CompetitionModel c,
MembreModel combModel, boolean admin) {
return registerRepository.find("competition = ?1 AND membre = ?2", c, combModel).firstResult()
.onFailure().recoverWithNull()
.map(Unchecked.function(r -> {
if (r != null) {
if (!admin && r.isLockEdit())
throw new DForbiddenException(
"Modification bloquée par l'administrateur de la compétition");
r.setWeight(data.getWeight());
r.setOverCategory(data.getOverCategory());
r.setCategorie(
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
c.getDate()));
int days = Utils.getDaysBeforeCompetition(c.getDate());
if (days > -7)
r.setClub(combModel.getClub());
if (admin)
r.setLockEdit(data.isLockEdit());
} else {
r = new RegisterModel(c, combModel, data.getWeight(), data.getOverCategory(),
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
c.getDate()),
(combModel.getClub() == null) ? null : combModel.getClub());
if (admin)
r.setLockEdit(data.isLockEdit());
else
r.setLockEdit(false);
}
if (c.getSystem() == CompetitionSystem.SAFCA) {
SReqRegister.sendIfNeed(serverCustom.clients,
new CompetitionData.SimpleRegister(r.getMembre().getId(),
r.getOverCategory(), r.getWeight(), r.getCategorie(),
(r.getClub() == null) ? null : r.getClub().getId()), c.getId());
}
return r;
}))
.chain(r -> Panache.withTransaction(() -> registerRepository.persist(r)));
}
private Uni<MembreModel> findComb(Long licence, String fname, String lname) {
if (licence != null && licence != 0) {
return combRepository.find("licence = ?1", licence).firstResult()
.invoke(Unchecked.consumer(combModel -> {
if (combModel == null)
throw new DForbiddenException("Licence " + licence + " non trouvé");
}));
} else {
if (fname == null || lname == null)
return Uni.createFrom().failure(new DBadRequestException("Nom et prénom requis"));
return combRepository.find("unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2)",
lname,
fname).firstResult()
.invoke(Unchecked.consumer(combModel -> {
if (combModel == null)
throw new DForbiddenException("Combattant " + fname + " " + lname + " non trouvé");
}));
}
}
public Uni<Void> removeRegisterComb(SecurityCtx securityCtx, Long id, Long combId, String source, boolean ban) {
if ("admin".equals(source))
return permService.hasEditPerm(securityCtx, id)
.chain(cm -> {
if (cm.getBanMembre() == null)
cm.setBanMembre(new ArrayList<>());
if (ban) {
if (!cm.getBanMembre().contains(combId))
cm.getBanMembre().add(combId);
} else {
cm.getBanMembre().remove(combId);
}
return Panache.withTransaction(() -> repository.persist(cm));
})
.chain(c -> deleteRegister(combId, c, true));
if ("club".equals(source))
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
if (!(cm.getRegisterMode() == RegisterMode.CLUB_ADMIN || cm.getRegisterMode() == RegisterMode.FREE)
|| !securityCtx.isClubAdmin())
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
}))
.call(cm -> membreService.getByAccountId(securityCtx.getSubject())
.invoke(Unchecked.consumer(model -> {
if (!securityCtx.isInClubGroup(model.getClub().getId()))
throw new DForbiddenException();
})))
.chain(c -> deleteRegister(combId, c, false));
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
if (cm.getRegisterMode() != RegisterMode.FREE)
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
}))
.chain(c -> deleteRegister(combId, c, false));
}
private Uni<Void> deleteRegister(Long combId, CompetitionModel c, boolean admin) {
return registerRepository.find("competition = ?1 AND membre.id = ?2", c, combId).firstResult()
.onFailure().transform(t -> new DBadRequestException("Combattant non inscrit"))
.call(Unchecked.function(registerModel -> {
if (!admin && registerModel.isLockEdit())
throw new DForbiddenException("Modification bloquée par l'administrateur de la compétition");
return Panache.withTransaction(() -> registerRepository.delete(registerModel));
}))
.replaceWithVoid();
}
public Uni<?> delete(SecurityCtx securityCtx, Long id) {
return repository.findById(id).invoke(Unchecked.consumer(c -> {
if (!(securityCtx.getSubject().equals(c.getOwner()) || securityCtx.roleHas("federation_admin")))
throw new DForbiddenException();
}))
.call(competitionModel -> categoryRepository.list("compet = ?1", competitionModel)
.call(pouleModels -> pouleModels.isEmpty() ? Uni.createFrom().nullItem() :
Uni.join().all(pouleModels.stream()
.map(pouleModel -> Panache.withTransaction(
() -> matchRepository.delete("poule = ?1", pouleModel.getId())))
.toList())
.andCollectFailures()))
.call(competitionModel -> Panache.withTransaction(
() -> categoryRepository.delete("compet = ?1", competitionModel)))
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())))
.invoke(o -> SReqCompet.rmCompet(serverCustom.clients, id))
.call(__ -> cache.invalidate(id));
}
public Uni<SimpleCompetData> getSafcaData(SecurityCtx securityCtx, Long id) {
return permService.getSafcaConfig(id)
.call(Unchecked.function(o -> {
if (!securityCtx.getSubject().equals(o.owner())
&& !securityCtx.roleHas("federation_admin")
&& !securityCtx.roleHas("safca_super_admin")
&& !o.admin().contains(UUID.fromString(securityCtx.getSubject()))
&& !o.table().contains(UUID.fromString(securityCtx.getSubject())))
throw new DForbiddenException();
return Uni.createFrom().nullItem();
}))
.chain(simpleCompet -> {
SimpleCompetData data = SimpleCompetData.fromModel(simpleCompet);
return vertx.getOrCreateContext().executeBlocking(() -> {
data.setAdmin(simpleCompet.admin().stream().map(uuid -> keycloakService.getUser(uuid))
.filter(Optional::isPresent)
.map(user -> user.get().getUsername())
.toList());
data.setTable(simpleCompet.table().stream().map(uuid -> keycloakService.getUser(uuid))
.filter(Optional::isPresent)
.map(user -> user.get().getUsername())
.toList());
return data;
});
});
}
public Uni<?> setSafcaData(SecurityCtx securityCtx, SimpleCompetData data) {
return permService.hasEditPerm(securityCtx, data.getId())
.chain(__ -> vertx.getOrCreateContext().executeBlocking(() -> {
ArrayList<UUID> admin = new ArrayList<>();
ArrayList<UUID> table = new ArrayList<>();
for (String username : data.getAdmin()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException("User " + username + " not found");
admin.add(UUID.fromString(opt.get().getId()));
}
for (String username : data.getTable()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException("User " + username + " not found");
table.add(UUID.fromString(opt.get().getId()));
}
return new SimpleCompet(data.getId(), "", data.isShow_blason(),
data.isShow_flag(), admin, table);
}))
.invoke(simpleCompet -> SReqCompet.sendUpdate(serverCustom.clients, simpleCompet))
.call(simpleCompet -> permService.getSafcaConfig(data.getId())
.call(c -> {
List<Uni<Void>> list = Stream.concat(
Stream.concat(
c.admin().stream().filter(uuid -> !simpleCompet.admin().contains(uuid)),
simpleCompet.admin().stream().filter(uuid -> !c.admin().contains(uuid))),
Stream.concat(
c.table().stream().filter(uuid -> !simpleCompet.table().contains(uuid)),
simpleCompet.table().stream().filter(uuid -> !c.table().contains(uuid)))
).map(uuid -> cacheAccess.invalidate(uuid.toString())).toList();
if (list.isEmpty())
return Uni.createFrom().nullItem();
return Uni.join().all(list).andCollectFailures();
}))
.call(__ -> cache.invalidate(data.getId()));
}
public Uni<Response> unregisterHelloAsso(NotificationData data) {
if (!data.getState().equals("Refunded"))
return Uni.createFrom().item(Response.ok().build());
return helloAssoRepository.list("orderId = ?1", data.getOrder().getId())
.chain(regs -> {
Uni<?> uni = Uni.createFrom().nullItem();
for (HelloAssoRegisterModel reg : regs) {
if (reg.getCompetition().getRegisterMode() != RegisterMode.HELLOASSO)
continue;
if (!data.getOrder().getOrganizationSlug().equalsIgnoreCase(reg.getCompetition().getData1()))
continue;
uni = uni.call(__ -> Panache.withTransaction(
() -> registerRepository.delete("competition = ?1 AND membre = ?2",
reg.getCompetition(), reg.getMembre())));
}
return uni;
})
.onFailure().invoke(Throwable::printStackTrace)
.map(__ -> Response.ok().build());
}
public Uni<Response> registerHelloAsso(NotificationData data) {
String organizationSlug = data.getOrganizationSlug();
String formSlug = data.getFormSlug();
RegisterRequestData req = new RegisterRequestData(null, "", "", null, 0, false);
return repository.find("data1 = ?1 AND data2 = ?2", organizationSlug, formSlug).firstResult()
.onFailure().recoverWithNull()
.chain(cm -> {
Uni<?> uni = Uni.createFrom().nullItem();
if (cm == null || cm.getRegisterMode() != RegisterMode.HELLOASSO)
return uni;
List<String> place = List.of(cm.getData3().toLowerCase().split(";"));
List<String> fail = new ArrayList<>();
for (NotificationData.Item item : data.getItems()) {
if (!place.contains(item.getName().toLowerCase()))
continue;
if (item.getCustomFields() == null || item.getCustomFields().isEmpty()) {
fail.add("%s %s - licence n°???".formatted(item.getUser().getLastName(),
item.getUser().getFirstName()));
continue;
}
Optional<Long> optional = item.getCustomFields().stream()
.filter(cf -> cf.getName().equalsIgnoreCase("Numéro de licence")).findAny().map(
NotificationData.CustomField::getAnswer).map(Long::valueOf);
if (optional.isPresent()) {
uni = uni.call(__ -> membreService.getByLicence(optional.get())
.invoke(Unchecked.consumer(m -> {
if (m == null)
throw new NotFoundException();
}))
.call(m -> Panache.withTransaction(() ->
helloAssoRepository.persist(
new HelloAssoRegisterModel(cm, m, data.getId()))))
.chain(m -> updateRegister(req, cm, m, true)))
.onFailure().recoverWithItem(throwable -> {
fail.add("%s %s - licence n°%d".formatted(item.getUser().getLastName(),
item.getUser().getFirstName(), optional.get()));
return null;
})
.replaceWithVoid();
} else {
fail.add("%s %s - licence n°???".formatted(item.getUser().getLastName(),
item.getUser().getFirstName()));
}
}
return uni.call(__ -> fail.isEmpty() ? Uni.createFrom().nullItem() :
reactiveMailer.send(
Mail.withText(cm.getData4(),
"FFSAF - Compétition - Erreur HelloAsso",
String.format(
"""
Bonjour,
Une erreur a été rencontrée lors de l'enregistrement d'une inscription à votre compétition %s pour les combattants suivants:
%s
Cordialement,
L'intranet de la FFSAF
""", cm.getName(), String.join("\r\n", fail))
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
).onFailure().invoke(e -> LOGGER.error("Fail to send email", e)));
})
.onFailure().invoke(Throwable::printStackTrace)
.map(__ -> Response.ok().build());
}
}

View File

@ -1,68 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.rest.client.HelloAssoAuthClient;
import fr.titionfire.ffsaf.rest.client.dto.TokenResponse;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.logging.Logger;
@ApplicationScoped
public class HelloAssoTokenService {
private static final Logger LOG = Logger.getLogger(HelloAssoTokenService.class);
@Inject
@RestClient
HelloAssoAuthClient authClient;
@ConfigProperty(name = "helloasso.client-id")
String clientId;
@ConfigProperty(name = "helloasso.client-secret")
String clientSecret;
private TokenResponse currentToken; // Stockage en mémoire (pour un seul pod)
// Récupère un token valide (en le rafraîchissant si nécessaire)
public Uni<String> getValidAccessToken() {
if (currentToken == null || currentToken.isExpired()) {
return fetchNewToken(clientId, clientSecret);
}
return Uni.createFrom().item(currentToken.accessToken);
}
// Récupère un nouveau token (via client_credentials ou refresh_token)
private Uni<String> fetchNewToken(String clientId, String clientSecret) {
if (currentToken != null && currentToken.refreshToken != null) {
// On utilise le refresh_token si disponible
return authClient.refreshToken("refresh_token", clientId, currentToken.refreshToken)
.onItem().invoke(token -> {
LOG.info("Token rafraîchi avec succès");
currentToken = token;
})
.onFailure().recoverWithItem(e -> {
LOG.warn("Échec du rafraîchissement, utilisation des credentials", e);
return null; // Force l'utilisation des credentials
})
.flatMap(token -> token != null ?
Uni.createFrom().item(token.accessToken) :
getTokenWithCredentials(clientId, clientSecret)
);
} else {
return getTokenWithCredentials(clientId, clientSecret);
}
}
// Récupère un token avec client_id/client_secret
private Uni<String> getTokenWithCredentials(String clientId, String clientSecret) {
return authClient.getToken("client_credentials", clientId, clientSecret)
.onItem().invoke(token -> {
LOG.info("Nouveau token obtenu");
currentToken = token;
})
.onFailure().invoke(e -> LOG.error("Erreur lors de l'obtention du token", e))
.map(token -> token.accessToken);
}
}

View File

@ -2,10 +2,7 @@ package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.utils.*; import fr.titionfire.ffsaf.utils.*;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.reactive.ReactiveMailer;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked; import io.smallrye.mutiny.unchecked.Unchecked;
@ -26,7 +23,6 @@ import java.text.Normalizer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
@ApplicationScoped @ApplicationScoped
public class KeycloakService { public class KeycloakService {
@ -44,12 +40,6 @@ public class KeycloakService {
@ConfigProperty(name = "keycloak.realm") @ConfigProperty(name = "keycloak.realm")
String realm; String realm;
@ConfigProperty(name = "email.enabled")
boolean enabled_email;
@Inject
ReactiveMailer reactiveMailer;
@Inject @Inject
Vertx vertx; Vertx vertx;
@ -59,134 +49,68 @@ public class KeycloakService {
return vertx.getOrCreateContext().executeBlocking(() -> { return vertx.getOrCreateContext().executeBlocking(() -> {
GroupRepresentation clubGroup = GroupRepresentation clubGroup =
keycloak.realm(realm).groups().groups().stream().filter(g -> g.getName().equals("club")) keycloak.realm(realm).groups().groups().stream().filter(g -> g.getName().equals("club"))
.findAny() .findAny().orElseThrow(() -> new KeycloakException("Fail to fetch group %s".formatted("club")));
.orElseThrow(() -> new KeycloakException("Fail to fetch group %s".formatted("club")));
GroupRepresentation groupRepresentation = new GroupRepresentation(); GroupRepresentation groupRepresentation = new GroupRepresentation();
groupRepresentation.setName(club.getId() + "-" + club.getName()); groupRepresentation.setName(club.getId() + "-" + club.getName());
try (Response response = try (Response response =
keycloak.realm(realm).groups().group(clubGroup.getId()).subGroup(groupRepresentation)) { keycloak.realm(realm).groups().group(clubGroup.getId()).subGroup(groupRepresentation)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo() if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo().equals(Response.Status.CONFLICT))
.equals(Response.Status.CONFLICT)) throw new KeycloakException("Fail to set group parent for club: %s (reason=%s)".formatted(club.getName(),
throw new KeycloakException( response.getStatusInfo().getReasonPhrase()));
"Fail to set group parent for club: %s (reason=%s)".formatted(club.getName(),
response.getStatusInfo().getReasonPhrase()));
} }
return keycloak.realm(realm).groups().group(clubGroup.getId()).getSubGroups(0, 1000, true).stream() return keycloak.realm(realm).groups().group(clubGroup.getId()).getSubGroups(0, 1000, true).stream()
.filter(g -> g.getName().startsWith(club.getId() + "-")).findAny() .filter(g -> g.getName().startsWith(club.getId() + "-")).findAny().map(GroupRepresentation::getId)
.map(GroupRepresentation::getId) .orElseThrow(() -> new KeycloakException("Fail to fetch group %s*".formatted(club.getId() + "-")));
.orElseThrow(
() -> new KeycloakException("Fail to fetch group %s*".formatted(club.getId() + "-")));
} }
).call(id -> clubService.setClubId(club.getId(), id)); ).call(id -> clubService.setClubId(club.getId(), id));
} }
return Uni.createFrom().item(club::getClubId); return Uni.createFrom().item(club::getClubId);
} }
public Uni<String> updateGroupFromClub(ClubModel club) {
if (club.getClubId() == null) {
return getGroupFromClub(club);
} else {
LOGGER.infof("Updating name of club group %d-%s...", club.getId(), club.getName());
return vertx.getOrCreateContext().executeBlocking(() -> {
GroupRepresentation clubGroup =
keycloak.realm(realm).groups().groups().stream().filter(g -> g.getName().equals("club"))
.findAny()
.orElseThrow(() -> new KeycloakException("Fail to fetch group %s".formatted("club")));
keycloak.realm(realm).groups().group(clubGroup.getId()).getSubGroups(0, 1000, true).stream()
.filter(g -> g.getName().startsWith(club.getId() + "-")).findAny()
.ifPresent(groupRepresentation -> {
groupRepresentation.setName(club.getId() + "-" + club.getName());
keycloak.realm(realm).groups().group(groupRepresentation.getId())
.update(groupRepresentation);
});
return club.getClubId();
}
);
}
}
public Uni<String> getUserFromMember(MembreModel membreModel) { public Uni<String> getUserFromMember(MembreModel membreModel) {
if (membreModel.getUserId() == null) { if (membreModel.getUserId() == null) {
return Uni.createFrom() return Uni.createFrom().failure(new NullPointerException("No keycloak user linked to the user id=" + membreModel.getId()));
.failure(new DInternalError("No keycloak user linked to the user id=" + membreModel.getId()));
} }
return Uni.createFrom().item(membreModel::getUserId); return Uni.createFrom().item(membreModel::getUserId);
} }
public Uni<String> setClubGroupMembre(MembreModel membreModel, ClubModel club) { public Uni<String> setClubGroupMembre(MembreModel membreModel, ClubModel club) {
if (club == null) return getGroupFromClub(club).chain(
return getUserFromMember(membreModel).chain( clubId -> getUserFromMember(membreModel).chain(userId -> vertx.getOrCreateContext().executeBlocking(() -> {
userId -> vertx.getOrCreateContext().executeBlocking(() -> { UserResource user = keycloak.realm(realm).users().get(userId);
UserResource user = keycloak.realm(realm).users().get(userId); user.groups().stream().filter(g -> g.getPath().startsWith("/club")).forEach(g -> user.leaveGroup(g.getId()));
user.groups().stream().filter(g -> g.getPath().startsWith("/club")) user.joinGroup(clubId);
.forEach(g -> user.leaveGroup(g.getId())); LOGGER.infof("Set club \"%s\" to user %s (%s)", club.getName(), userId,
return "OK"; user.toRepresentation().getUsername());
})); return "OK";
else })));
return getGroupFromClub(club).chain(
clubId -> getUserFromMember(membreModel).chain(
userId -> vertx.getOrCreateContext().executeBlocking(() -> {
UserResource user = keycloak.realm(realm).users().get(userId);
user.groups().stream().filter(g -> g.getPath().startsWith("/club"))
.forEach(g -> user.leaveGroup(g.getId()));
user.joinGroup(clubId);
LOGGER.infof("Set club \"%s\" to user %s (%s)", club.getName(), userId,
user.toRepresentation().getUsername());
return "OK";
})));
} }
public Uni<?> setEmail(String userId, String email) { public Uni<?> setEmail(String userId, String email) {
return vertx.getOrCreateContext().executeBlocking(() -> { return vertx.getOrCreateContext().executeBlocking(() -> {
UserResource user = keycloak.realm(realm).users().get(userId); UserResource user = keycloak.realm(realm).users().get(userId);
UserRepresentation user2 = user.toRepresentation(); UserRepresentation user2 = user.toRepresentation();
String oldEmail = user2.getEmail();
if (email.equals(user2.getEmail())) if (email.equals(user2.getEmail()))
return null; return "";
user2.setEmail(email); user2.setEmail(email);
user2.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name())); user2.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name()));
user.update(user2); user.update(user2);
if (enabled_email) return "";
user.sendVerifyEmail(); });
return oldEmail;
}).call(oldEmail -> oldEmail == null || !enabled_email ? Uni.createFrom().item("") :
reactiveMailer.send(
Mail.withText(oldEmail,
"FFSAF - Changement de votre adresse email",
String.format(
"""
Bonjour,
Suite à la modification de votre adresse email fournie lors de votre ()inscription à la FFSAF,
vous allez recevoir dans les prochaines minutes un email de vérification de votre nouvelle adresse sur celle-ci.
Ancienne adresse email : %s
Nouvelle adresse email : %s
Si vous n'avez pas demandé cette modification, veuillez contacter le support à l'adresse support@ffsaf.fr.
Cordialement,
L'équipe de la FFSAF
""", oldEmail, email)
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
).onFailure().invoke(e -> LOGGER.error("Fail to send email", e)));
} }
public Uni<?> setAutoRoleMembre(String id, RoleAsso role, GradeArbitrage gradeArbitrage) { public Uni<?> setAutoRoleMembre(String id, RoleAsso role, GradeArbitrage gradeArbitrage) {
List<String> toRemove = new ArrayList<>(List.of("club_president", "club_tresorier", "club_secretaire", List<String> toRemove = new ArrayList<>(List.of("club_president", "club_tresorier", "club_secretaire",
"club_respo_intra", "asseseur", "arbitre")); "asseseur", "arbitre"));
List<String> toAdd = new ArrayList<>(); List<String> toAdd = new ArrayList<>();
switch (role) { switch (role) {
case PRESIDENT, VPRESIDENT -> toAdd.add("club_president"); case PRESIDENT -> toAdd.add("club_president");
case TRESORIER, VTRESORIER -> toAdd.add("club_tresorier"); case TRESORIER -> toAdd.add("club_tresorier");
case SECRETAIRE, VSECRETAIRE -> toAdd.add("club_secretaire"); case SECRETAIRE -> toAdd.add("club_secretaire");
case MEMBREBUREAU -> toAdd.add("club_respo_intra");
} }
switch (gradeArbitrage) { switch (gradeArbitrage) {
case ARBITRE -> toAdd.addAll(List.of("asseseur", "arbitre")); case ARBITRE -> toAdd.addAll(List.of("asseseur", "arbitre"));
@ -208,8 +132,7 @@ public class KeycloakService {
public Uni<List<String>> fetchRole(String id) { public Uni<List<String>> fetchRole(String id) {
return vertx.getOrCreateContext().executeBlocking(() -> return vertx.getOrCreateContext().executeBlocking(() ->
keycloak.realm(realm).users().get(id).roles().realmLevel().listEffective().stream() keycloak.realm(realm).users().get(id).roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).toList());
.map(RoleRepresentation::getName).toList());
} }
public Uni<?> updateRole(String id, List<String> toAdd, List<String> toRemove) { public Uni<?> updateRole(String id, List<String> toAdd, List<String> toRemove) {
@ -224,16 +147,17 @@ public class KeycloakService {
public Uni<String> initCompte(long id) { public Uni<String> initCompte(long id) {
return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> { return membreService.getById(id).invoke(Unchecked.consumer(membreModel -> {
if (membreModel.getUserId() != null) if (membreModel.getUserId() != null)
throw new KeycloakException("User already linked to the user id=" + id); throw new KeycloakException("User already linked to the user id=" + id);
if (membreModel.getEmail() == null) if (membreModel.getEmail() == null)
throw new KeycloakException("User email is null"); throw new KeycloakException("User email is null");
if (membreModel.getFname() == null || membreModel.getLname() == null) if (membreModel.getFname() == null || membreModel.getLname() == null)
throw new KeycloakException("User name is null"); throw new KeycloakException("User name is null");
})).chain(membreModel -> creatUser(membreModel).chain(user -> { })).chain(membreModel -> creatUser(membreModel).chain(user -> {
LOGGER.infof("Set user id %s to membre %s", user.getId(), membreModel.getId()); LOGGER.infof("Set user id %s to membre %s", user.getId(), membreModel.getId());
return membreService.setUserId(membreModel.getId(), user.getId()).map(__ -> user.getId()); return membreService.setUserId(membreModel.getId(), user.getId());
})); }))
.map(__ -> "OK");
} }
private Uni<UserRepresentation> creatUser(MembreModel membreModel) { private Uni<UserRepresentation> creatUser(MembreModel membreModel) {
@ -256,44 +180,22 @@ public class KeycloakService {
user.setEmail(membreModel.getEmail()); user.setEmail(membreModel.getEmail());
user.setEnabled(true); user.setEnabled(true);
user.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
try (Response response = keycloak.realm(realm).users().create(user)) { try (Response response = keycloak.realm(realm).users().create(user)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo() if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo().equals(Response.Status.CONFLICT))
.equals(Response.Status.CONFLICT))
throw new KeycloakException("Fail to creat user %s (reason=%s)".formatted(login, throw new KeycloakException("Fail to creat user %s (reason=%s)".formatted(login,
response.getStatusInfo().getReasonPhrase())); response.getStatusInfo().getReasonPhrase()));
} }
String finalLogin = login; String finalLogin = login;
return getUser(login).orElseThrow( return getUser(login).orElseThrow(() -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin)));
() -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin)));
}) })
.invoke(user -> keycloak.realm(realm).users().get(user.getId())
.executeActionsEmail(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name())))
.invoke(user -> membreModel.setUserId(user.getId())) .invoke(user -> membreModel.setUserId(user.getId()))
.call(user -> updateRole(user.getId(), List.of("safca_user"), List.of()))
.call(user -> enabled_email ? reactiveMailer.send(
Mail.withText(user.getEmail(),
"FFSAF - Creation de votre compte sur l'intranet",
String.format(
"""
Bonjour,
Suite à votre première inscription % la Fédération Française de Soft Armored Fighting (FFSAF), votre compte intranet a été créé.
Ce compte vous permettra de consulter vos informations et, dans un futur proche, de vous inscrire aux compétitions ainsi que d'en consulter les résultats.
L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr
Votre nom d'utilisateur est : %s
Pour définir votre mot de passe, rendez-vous sur l'intranet > "Connexion" > "Mot de passe oublié ?"
Si vous n'avez pas demandé cette inscription, veuillez contacter le support à l'adresse support@ffsaf.fr.
(Pas de panique, nous ne vous enverrons pas de message autre que ce concernant votre compte)
Cordialement,
L'équipe de la FFSAF
""",
membreModel.getRole() == RoleAsso.MEMBRE ? "par votre club (" + membreModel.getClub()
.getName() + ") " : "", user.getUsername())
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
) : Uni.createFrom().nullItem())
.call(user -> membreService.setUserId(membreModel.getId(), user.getId())) .call(user -> membreService.setUserId(membreModel.getId(), user.getId()))
.call(user -> setClubGroupMembre(membreModel, membreModel.getClub())); .call(user -> setClubGroupMembre(membreModel, membreModel.getClub()));
} }
@ -314,31 +216,7 @@ public class KeycloakService {
}); });
} }
public Uni<?> removeClubGroup(String clubId) { private Optional<UserRepresentation> getUser(String username) {
return vertx.getOrCreateContext().executeBlocking(() -> {
keycloak.realm(realm).groups().group(clubId).remove();
return null;
});
}
public Uni<?> clearUser(String userId) {
List<String> toRemove = new ArrayList<>(
List.of("club_president", "club_tresorier", "club_secretaire", "club_respo_intra"));
return vertx.getOrCreateContext().executeBlocking(() -> {
UserResource user = keycloak.realm(realm).users().get(userId);
RoleScopeResource resource = user.roles().realmLevel();
List<RoleRepresentation> roles = keycloak.realm(realm).roles().list();
resource.remove(roles.stream().filter(r -> toRemove.contains(r.getName())).toList());
user.groups().stream().filter(g -> g.getPath().startsWith("/club"))
.forEach(g -> user.leaveGroup(g.getId()));
return "OK";
});
}
public Optional<UserRepresentation> getUser(String username) {
List<UserRepresentation> users = keycloak.realm(realm).users().searchByUsername(username, true); List<UserRepresentation> users = keycloak.realm(realm).users().searchByUsername(username, true);
if (users.isEmpty()) if (users.isEmpty())
@ -347,19 +225,8 @@ public class KeycloakService {
return Optional.of(users.get(0)); return Optional.of(users.get(0));
} }
public Optional<UserRepresentation> getUser(UUID userId) {
UserResource user = keycloak.realm(realm).users().get(userId.toString());
if (user == null)
return Optional.empty();
else
return Optional.of(user.toRepresentation());
}
private String makeLogin(MembreModel model) { private String makeLogin(MembreModel model) {
return Normalizer.normalize( return Normalizer.normalize((model.getFname().toLowerCase() + "." + model.getLname().toLowerCase()).replace(' ', '_'), Normalizer.Form.NFD)
(model.getFname().toLowerCase() + "." + model.getLname().toLowerCase()).replace(' ', '_'),
Normalizer.Form.NFD)
.replaceAll("\\p{M}", ""); .replaceAll("\\p{M}", "");
} }

View File

@ -1,15 +1,10 @@
package fr.titionfire.ffsaf.domain.service; package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.LicenceModel; import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.LogModel;
import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.CombRepository; import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.LicenceRepository; import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.data.repository.SequenceRepository;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.from.LicenceForm; import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.SequenceType;
import fr.titionfire.ffsaf.utils.Utils; import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.hibernate.reactive.panache.common.WithSession;
@ -17,17 +12,16 @@ import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked; import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class LicenceService { public class LicenceService {
private static final Logger LOGGER = Logger.getLogger(LicenceService.class);
@Inject @Inject
LicenceRepository repository; LicenceRepository repository;
@ -35,155 +29,60 @@ public class LicenceService {
@Inject @Inject
CombRepository combRepository; CombRepository combRepository;
@Inject
SequenceRepository sequenceRepository;
@Inject
KeycloakService keycloakService;
@Inject
LoggerService ls;
@Inject
CheckoutService checkoutService;
public Uni<List<LicenceModel>> getLicence(long id, Consumer<MembreModel> checkPerm) { public Uni<List<LicenceModel>> getLicence(long id, Consumer<MembreModel> checkPerm) {
return combRepository.findById(id).invoke(checkPerm) return combRepository.findById(id).invoke(checkPerm).chain(combRepository -> Mutiny.fetch(combRepository.getLicences()));
.chain(combRepository -> Mutiny.fetch(combRepository.getLicences()));
} }
public Uni<List<LicenceModel>> getCurrentSaisonLicence(SecurityCtx securityCtx) { public Uni<List<LicenceModel>> getCurrentSaisonLicence(JsonWebToken idToken) {
if (securityCtx == null || securityCtx.getSubject() == null) if (idToken == null)
return repository.find("saison = ?1", Utils.getSaison()).list(); return repository.find("saison = ?1", Utils.getSaison()).list();
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult().map(MembreModel::getClub) return combRepository.find("userId = ?1", idToken.getSubject()).firstResult().map(MembreModel::getClub)
.chain(clubModel -> combRepository.find("club = ?1", clubModel).list()) .chain(clubModel -> combRepository.find("club = ?1", clubModel).list())
.chain(membres -> repository.find("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres).list()); .chain(membres -> repository.find("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres).list());
} }
public Uni<?> valideLicences(List<Long> ids) {
Uni<String> uni = Uni.createFrom().nullItem();
for (Long id : ids) {
uni = uni.chain(__ -> repository.find("membre.id = ?1 AND saison = ?2", id, Utils.getSaison()).firstResult()
.chain(model -> {
if (!model.isValidate())
ls.logUpdate("validation de la licence", model);
return validateLicences(model);
}))
.map(__ -> "OK");
}
return uni.call(__ -> ls.append());
}
protected Uni<LicenceModel> validateLicences(LicenceModel model) {
model.setValidate(true);
return Panache.withTransaction(() -> repository.persist(model)
.call(m -> Mutiny.fetch(m.getMembre())
.call(genLicenceNumberAndAccountIfNeed())
));
}
public Uni<LicenceModel> setLicence(long id, LicenceForm form) { public Uni<LicenceModel> setLicence(long id, LicenceForm form) {
if (form.getId() == -1) { if (form.getId() == -1) {
return combRepository.findById(id).chain(membreModel -> { return combRepository.findById(id).chain(combRepository -> {
LicenceModel model = new LicenceModel(); LicenceModel model = new LicenceModel();
model.setMembre(membreModel); model.setMembre(combRepository);
model.setClub_id((membreModel.getClub() == null) ? null : membreModel.getClub().getId());
model.setSaison(form.getSaison()); model.setSaison(form.getSaison());
model.setCertificate(form.getCertificate()); model.setCertificate(form.isCertificate());
model.setValidate(form.isValidate()); model.setValidate(form.isValidate());
model.setPay(form.isPay()); return Panache.withTransaction(() -> repository.persist(model));
return Panache.withTransaction(() -> repository.persist(model)
.call(m -> m.isValidate() ? Uni.createFrom().item(membreModel)
.call(genLicenceNumberAndAccountIfNeed())
: Uni.createFrom().nullItem()
))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
licenceModel));
}); });
} else { } else {
return repository.findById(form.getId()).chain(model -> { return repository.findById(form.getId()).chain(model -> {
ls.logChange("Certificate", model.getCertificate(), form.getCertificate(), model); model.setCertificate(form.isCertificate());
ls.logChange("Validate", model.isValidate(), form.isValidate(), model);
ls.logChange("Pay", model.isPay(), form.isPay(), model);
model.setCertificate(form.getCertificate());
model.setValidate(form.isValidate()); model.setValidate(form.isValidate());
model.setPay(form.isPay()); return Panache.withTransaction(() -> repository.persist(model));
return Panache.withTransaction(() -> repository.persist(model)
.call(m -> m.isValidate() ? Mutiny.fetch(m.getMembre())
.call(genLicenceNumberAndAccountIfNeed())
: Uni.createFrom().nullItem()
))
.call(__ -> ls.append());
}); });
} }
} }
private Function<MembreModel, Uni<?>> genLicenceNumberAndAccountIfNeed() {
return membreModel -> ((membreModel.getLicence() <= 0) ?
sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
.invoke(i -> membreModel.setLicence(Math.toIntExact(i)))
.chain(() -> combRepository.persist(membreModel))
: Uni.createFrom().nullItem())
.call(__ -> (membreModel.getUserId() == null) ?
keycloakService.initCompte(membreModel.getId()).onFailure()
.invoke(t -> LOGGER.infof("Failed to init account: %s", t.getMessage())).onFailure()
.recoverWithNull()
: Uni.createFrom().nullItem());
}
public Uni<String> payLicences(List<Long> ids, Consumer<MembreModel> checkPerm, SecurityCtx securityCtx) {
return repository.list("membre.id IN ?1 AND saison = ?2 AND pay = FALSE", ids, Utils.getSaison())
.invoke(Unchecked.consumer(models -> {
if (models.size() != ids.size())
throw new DBadRequestException("Erreur lors de la sélection des membres");
}))
.call(models -> {
Uni<?> uni = Uni.createFrom().nullItem();
for (LicenceModel model : models)
uni = uni.chain(__ -> Mutiny.fetch(model.getMembre()).invoke(checkPerm));
return uni;
})
.chain(models -> checkoutService.create(models.stream().map(LicenceModel::getId).toList(),
securityCtx));
}
public Uni<?> deleteLicence(long id) { public Uni<?> deleteLicence(long id) {
return repository.findById(id) return Panache.withTransaction(() -> repository.deleteById(id));
.call(__ -> checkoutService.canDeleteLicence(id)
.invoke(Unchecked.consumer(b -> {
if (!b) throw new DBadRequestException(
"Impossible de supprimer une licence pour laquelle un paiement est en cours");
})))
.call(model -> ls.logADelete(model))
.chain(model -> repository.delete(model));
} }
public Uni<LicenceModel> askLicence(long id, LicenceForm form, Consumer<MembreModel> checkPerm) { public Uni<LicenceModel> askLicence(long id, LicenceForm form, Consumer<MembreModel> checkPerm) {
return combRepository.findById(id).invoke(checkPerm).chain(membreModel -> { return combRepository.findById(id).invoke(checkPerm).chain(membreModel -> {
if (form.getId() == -1) { if (form.getId() == -1) {
return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count() return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count().invoke(Unchecked.consumer(count -> {
.invoke(Unchecked.consumer(count -> { if (count > 0)
if (count > 0) throw new BadRequestException();
throw new DBadRequestException("Licence déjà demandée"); })).chain(__ -> combRepository.findById(id).chain(combRepository -> {
})).chain(__ -> combRepository.findById(id).chain(membreModel2 -> { LicenceModel model = new LicenceModel();
LicenceModel model = new LicenceModel(); model.setMembre(combRepository);
model.setClub_id((membreModel2.getClub() == null) ? null : membreModel2.getClub().getId()); model.setSaison(Utils.getSaison());
model.setMembre(membreModel2); model.setCertificate(form.isCertificate());
model.setSaison(Utils.getSaison()); model.setValidate(false);
model.setCertificate(form.getCertificate()); return Panache.withTransaction(() -> repository.persist(model));
model.setValidate(false); }));
return Panache.withTransaction(() -> repository.persist(model));
}))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
licenceModel));
} else { } else {
return repository.findById(form.getId()).chain(model -> { return repository.findById(form.getId()).chain(model -> {
ls.logChange("Certificate", model.getCertificate(), form.getCertificate(), model); model.setCertificate(form.isCertificate());
model.setCertificate(form.getCertificate()); return Panache.withTransaction(() -> repository.persist(model));
return Panache.withTransaction(() -> repository.persist(model))
.call(__ -> ls.append());
}); });
} }
}); });
@ -192,44 +91,6 @@ public class LicenceService {
public Uni<?> deleteAskLicence(long id, Consumer<MembreModel> checkPerm) { public Uni<?> deleteAskLicence(long id, Consumer<MembreModel> checkPerm) {
return repository.findById(id) return repository.findById(id)
.call(licenceModel -> Mutiny.fetch(licenceModel.getMembre()).invoke(checkPerm)) .call(licenceModel -> Mutiny.fetch(licenceModel.getMembre()).invoke(checkPerm))
.call(__ -> checkoutService.canDeleteLicence(id)
.invoke(Unchecked.consumer(b -> {
if (!b) throw new DBadRequestException(
"Impossible de supprimer une licence pour laquelle un paiement est en cours");
})))
.invoke(Unchecked.consumer(licenceModel -> {
if (licenceModel.isValidate())
throw new DBadRequestException("Impossible de supprimer une licence déjà validée");
if (licenceModel.isPay())
throw new DBadRequestException("Impossible de supprimer une licence déjà payée");
}))
.call(model -> ls.logADelete(model))
.chain(__ -> Panache.withTransaction(() -> repository.deleteById(id))); .chain(__ -> Panache.withTransaction(() -> repository.deleteById(id)));
} }
public Uni<?> setImport(int licence, int saison, boolean valid) {
return combRepository.find("licence = ?1", licence).firstResult()
.chain(membreModel ->
repository.find("saison = ?1 AND membre = ?2", saison, membreModel).firstResult()
.chain(licenceModel -> {
if (licenceModel != null) {
if (licenceModel.getClub_id() == null)
licenceModel.setClub_id(
(membreModel.getClub() == null) ? null : membreModel.getClub()
.getId());
licenceModel.setValidate(valid);
return Panache.withTransaction(() -> repository.persist(licenceModel));
} else {
LicenceModel model = new LicenceModel();
model.setClub_id(
(membreModel.getClub() == null) ? null : membreModel.getClub().getId());
model.setMembre(membreModel);
model.setSaison(saison);
model.setCertificate("¤");
model.setValidate(valid);
return Panache.withTransaction(() -> repository.persist(model));
}
}))
.map(__ -> "OK");
}
} }

View File

@ -1,109 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.LogModel;
import fr.titionfire.ffsaf.data.model.LogModel.ActionType;
import fr.titionfire.ffsaf.data.model.LogModel.ObjectType;
import fr.titionfire.ffsaf.data.model.LoggableModel;
import fr.titionfire.ffsaf.data.repository.LogRepository;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@WithSession
@RequestScoped
public class LoggerService {
@Inject
LogRepository repository;
@Inject
SecurityCtx securityCtx;
private final List<LogModel> buffer = new ArrayList<>();
public Uni<?> logA(ActionType action, ObjectType object, String message, String target_name, Long target_id) {
return Panache.withTransaction(() -> repository.persist(
new LogModel(null, securityCtx.getSubject(), new Date(), action, object, target_id, target_name,
message)));
}
public Uni<?> logA(ActionType action, String message, LoggableModel model) {
return logA(action, model.getObjectType(), message, model.getObjectName(), model.getId());
}
public Uni<?> logAAdd(LoggableModel model) {
return logA(ActionType.ADD, "", model);
}
public Uni<?> logAUpdate(String message, LoggableModel model) {
return logA(ActionType.UPDATE, message, model);
}
public Uni<?> logAChange(String champ, Object o1, Object o2, LoggableModel model) {
if (Objects.equals(o1, o2))
return Uni.createFrom().nullItem();
return logA(ActionType.UPDATE, champ + ": " + o1.toString() + " -> " + o2.toString(), model);
}
public Uni<?> logADelete(LoggableModel model) {
return logA(ActionType.REMOVE, "", model);
}
public Uni<?> append() {
return Panache.withTransaction(() -> repository.persist(buffer))
.invoke(__ -> buffer.clear());
}
public void clear() {
buffer.clear();
}
public void log(ActionType action, ObjectType object, String message, String target_name, Long target_id) {
buffer.add(new LogModel(null, securityCtx.getSubject(), new Date(), action, object, target_id, target_name,
message));
}
public void logAnonymous(ActionType action, ObjectType object, String message, String target_name, Long target_id) {
buffer.add(new LogModel(null, null, new Date(), action, object, target_id, target_name, message));
}
public void log(ActionType action, String message, LoggableModel model) {
log(action, model.getObjectType(), message, model.getObjectName(), model.getId());
}
public void logAnonymous(ActionType action, String message, LoggableModel model) {
logAnonymous(action, model.getObjectType(), message, model.getObjectName(), model.getId());
}
public void logAdd(LoggableModel model) {
log(ActionType.ADD, "", model);
}
public void logUpdate(String message, LoggableModel model) {
log(ActionType.UPDATE, message, model);
}
public void logUpdateAnonymous(String message, LoggableModel model) {
logAnonymous(ActionType.UPDATE, message, model);
}
public void logChange(String champ, Object o1, Object o2, LoggableModel model) {
if (Objects.equals(o1, o2))
return;
log(ActionType.UPDATE,
champ + ": " + (o1 == null ? "null" : o1.toString()) + " -> " + (o2 == null ? "null" : o2.toString()),
model);
}
public void logDelete(LoggableModel model) {
log(ActionType.REMOVE, "", model);
}
}

View File

@ -1,115 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
import fr.titionfire.ffsaf.rest.data.MatchData;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;
@WithSession
@ApplicationScoped
public class MatchService {
@Inject
MatchRepository repository;
@Inject
CategoryRepository categoryRepository;
@Inject
CombRepository combRepository;
@Inject
CompetPermService permService;
public Uni<MatchData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCategory().getCompet()))
.map(MatchData::fromModel);
}
public Uni<List<MatchData>> getAllByPouleAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return categoryRepository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found"))
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
.chain(data -> repository.list("poule = ?1", data.getId())
.map(o -> o.stream().map(MatchData::fromModel).toList()));
}
public Uni<MatchData> addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, MatchData data) {
return repository.find("systemId = ?1 AND system = ?2", data.getId(), system).firstResult()
.chain(o -> {
if (o == null) {
return categoryRepository.find("systemId = ?1 AND system = ?2", data.getCategory(), system)
.firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found"))
.call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet()))
.map(categoryModel -> {
MatchModel model = new MatchModel();
model.setId(null);
model.setSystem(system);
model.setSystemId(data.getId());
model.setCategory(categoryModel);
return model;
});
} else {
return categoryRepository.find("systemId = ?1 AND system = ?2", data.getCategory(), system)
.firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found"))
.call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet()))
.map(__ -> o);
}
}
)
.chain(o -> {
o.setC1_str(data.getC1_str());
o.setC2_str(data.getC2_str());
o.setCategory_ord(data.getCategory_ord());
o.getScores().clear();
o.getScores().addAll(data.getScores());
return Uni.createFrom().nullItem()
.chain(() -> (data.getC1_id() == null) ?
Uni.createFrom().nullItem() : combRepository.findById(data.getC1_id()))
.invoke(o::setC1_id)
.chain(() -> (data.getC1_id() == null) ?
Uni.createFrom().nullItem() : combRepository.findById(data.getC2_id()))
.invoke(o::setC2_id)
.chain(() -> Panache.withTransaction(() -> repository.persist(o)));
})
.map(MatchData::fromModel);
}
public Uni<?> updateScore(SecurityCtx securityCtx, CompetitionSystem system, Long id,
List<ScoreEmbeddable> scores) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
.call(o2 -> permService.hasEditPerm(securityCtx, o2.getCategory().getCompet()))
.invoke(data -> {
data.getScores().clear();
data.getScores().addAll(scores);
})
.chain(data -> Panache.withTransaction(() -> repository.persist(data)))
.map(o -> "OK");
}
public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
.call(o2 -> permService.hasEditPerm(securityCtx, o2.getCategory().getCompet()))
.chain(data -> Panache.withTransaction(() -> repository.delete(data)));
}
}

View File

@ -1,19 +1,15 @@
package fr.titionfire.ffsaf.domain.service; package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel; import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.*; import fr.titionfire.ffsaf.data.repository.ClubRepository;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.net2.ServerCustom; import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCombModel; import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
import fr.titionfire.ffsaf.net2.request.SReqComb; import fr.titionfire.ffsaf.net2.request.SReqComb;
import fr.titionfire.ffsaf.rest.data.MeData;
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
import fr.titionfire.ffsaf.rest.data.SimpleMembre; import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData; import fr.titionfire.ffsaf.rest.from.ClubMemberForm;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullMemberForm; import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.*; import fr.titionfire.ffsaf.utils.*;
import io.quarkus.hibernate.reactive.panache.Panache; import io.quarkus.hibernate.reactive.panache.Panache;
@ -21,24 +17,20 @@ import io.quarkus.hibernate.reactive.panache.PanacheQuery;
import io.quarkus.hibernate.reactive.panache.common.WithSession; import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort; import io.quarkus.panache.common.Sort;
import io.quarkus.scheduler.Scheduled; import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.VertxContextSupport; import io.quarkus.vertx.VertxContextSupport;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked; import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty; import jakarta.ws.rs.BadRequestException;
import org.hibernate.reactive.mutiny.Mutiny; import jakarta.ws.rs.ForbiddenException;
import org.jboss.logging.Logger; import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@WithSession @WithSession
@ApplicationScoped @ApplicationScoped
public class MembreService { public class MembreService {
private static final Logger LOGGER = Logger.getLogger(MembreService.class);
@Inject @Inject
CombRepository repository; CombRepository repository;
@ -47,167 +39,50 @@ public class MembreService {
@Inject @Inject
LicenceRepository licenceRepository; LicenceRepository licenceRepository;
@Inject
CompetitionRepository competitionRepository;
@Inject @Inject
ServerCustom serverCustom; ServerCustom serverCustom;
@Inject @Inject
KeycloakService keycloakService; KeycloakService keycloakService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
RegisterRepository registerRepository;
@Inject
LoggerService ls;
public SimpleCombModel find(int licence, String np) throws Throwable { public SimpleCombModel find(int licence, String np) throws Throwable {
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() ->
repository.find( repository.find("licence = ?1 AND (lname ILIKE ?2 OR fname ILIKE ?2)",
"licence = ?1 AND (unaccent(lname) ILIKE unaccent(?2) OR unaccent(fname) ILIKE unaccent(?2))",
licence, np).firstResult().map(SimpleCombModel::fromModel))); licence, np).firstResult().map(SimpleCombModel::fromModel)));
} }
public SimpleCombModel findByIdOptionalComb(long id) throws Throwable { public SimpleCombModel findByIdOptionalComb(long id) throws Throwable {
return VertxContextSupport.subscribeAndAwait( return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleCombModel::fromModel)));
() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleCombModel::fromModel)));
} }
final static String FIND_NAME_REQUEST = "unaccent(fname) ILIKE unaccent(?1) OR unaccent(lname) ILIKE unaccent(?1) " + public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club) {
"OR unaccent(fname || ' ' || lname) ILIKE unaccent(?1) OR unaccent(lname || ' ' || fname) ILIKE unaccent(?1)";
private Uni<List<LicenceModel>> getLicenceListe(int licenceRequest, int payState) {
Uni<List<LicenceModel>> baseUni;
String queryStr = "saison = ?1";
if (payState == 0)
queryStr += " AND pay = FALSE";
if (payState == 1)
queryStr += " AND pay = TRUE";
if (licenceRequest == 0 || licenceRequest == 1)
baseUni = licenceRepository.list(queryStr, Utils.getSaison());
else if (licenceRequest == 2)
baseUni = licenceRepository.list(queryStr + " AND validate = FALSE", Utils.getSaison());
else if (licenceRequest == 5)
baseUni = licenceRepository.list(queryStr + " AND validate = FALSE AND LENGTH(certificate) >= 3",
Utils.getSaison());
else if (licenceRequest == 6)
baseUni = licenceRepository.list(queryStr + " AND validate = FALSE AND LENGTH(certificate) <= 2",
Utils.getSaison());
else if (licenceRequest == 3)
baseUni = licenceRepository.list(queryStr + " AND validate = TRUE", Utils.getSaison());
else
baseUni = Uni.createFrom().item(new ArrayList<>());
return baseUni;
}
private Sort getSort(String order) {
Sort sort;
if (order == null || order.isBlank()) {
sort = Sort.ascending("fname", "lname");
} else {
sort = Sort.empty();
for (String e : order.split(",")) {
String[] split = e.split(" ");
if (split.length == 2) {
sort = sort.and(split[0],
split[1].equals("n") ? Sort.Direction.Ascending : Sort.Direction.Descending);
} else {
return null;
}
}
}
return sort;
}
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club,
int licenceRequest, int payState, String order, String categorie) {
if (search == null) if (search == null)
search = ""; search = "";
search = "%" + search.replaceAll(" ", "% %") + "%"; search = search + "%";
String categorieFilter; PanacheQuery<MembreModel> query;
if (categorie == null || categorie.isBlank())
categorieFilter = " True"; if (club == null || club.isBlank())
query = repository.find("(lname LIKE ?1 OR fname LIKE ?1)",
Sort.ascending("fname", "lname"), search).page(Page.ofSize(limit));
else else
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal(); query = repository.find("club.name LIKE ?2 AND (lname LIKE ?1 OR fname LIKE ?1)",
Sort.ascending("fname", "lname"), search, club + "%").page(Page.ofSize(limit));
return getPageResult(query, limit, page);
}
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, String subject) {
if (search == null)
search = "";
search = search + "%";
String finalSearch = search; String finalSearch = search;
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState); return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
Sort sort = getSort(order); PanacheQuery<MembreModel> query = repository.find("club = ?1 AND (lname LIKE ?2 OR fname LIKE ?2)",
if (sort == null) Sort.ascending("fname", "lname"), membreModel.getClub(), finalSearch).page(Page.ofSize(limit));
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return baseUni
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
PanacheQuery<MembreModel> query;
String idf = ((licenceRequest == 0 || licenceRequest == 4) ? "NOT IN" : "IN");
if (club == null || club.isBlank()) {
query = repository.find(
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, ids)
.page(Page.ofSize(limit));
} else {
if (club.equals("null")) {
query = repository.find(
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, ids).page(Page.ofSize(limit));
} else {
query = repository.find(
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, club, ids)
.page(Page.ofSize(limit));
}
}
return getPageResult(query, limit, page); return getPageResult(query, limit, page);
}); });
} }
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
String order, String categorie, String subject) {
if (search == null)
search = "";
search = "%" + search.replaceAll(" ", "% %") + "%";
String finalSearch = search;
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
String categorieFilter;
if (categorie == null || categorie.isBlank())
categorieFilter = " True";
else
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
Sort sort = getSort(order);
if (sort == null)
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return baseUni
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
String idf = ((licenceRequest == 0 || licenceRequest == 4) ? "NOT IN" : "IN");
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
PanacheQuery<MembreModel> query = repository.find(
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
sort, finalSearch, membreModel.getClub(), ids)
.page(Page.ofSize(limit));
return getPageResult(query, limit, page);
});
});
}
private Uni<PageResult<SimpleMembre>> getPageResult(PanacheQuery<MembreModel> query, int limit, int page) { private Uni<PageResult<SimpleMembre>> getPageResult(PanacheQuery<MembreModel> query, int limit, int page) {
return Uni.createFrom().item(new PageResult<SimpleMembre>()) return Uni.createFrom().item(new PageResult<SimpleMembre>())
.invoke(result -> result.setPage(page)) .invoke(result -> result.setPage(page))
@ -215,7 +90,7 @@ public class MembreService {
.call(result -> query.count().invoke(result::setResult_count)) .call(result -> query.count().invoke(result::setResult_count))
.call(result -> query.pageCount() .call(result -> query.pageCount()
.invoke(Unchecked.consumer(pages -> { .invoke(Unchecked.consumer(pages -> {
if (page > pages) throw new DBadRequestException("Page out of range"); if (page > pages) throw new BadRequestException();
})) }))
.invoke(result::setPage_count)) .invoke(result::setPage_count))
.call(result -> query.page(Page.of(page, limit)).list() .call(result -> query.page(Page.of(page, limit)).list()
@ -223,310 +98,92 @@ public class MembreService {
.invoke(result::setResult)); .invoke(result::setResult));
} }
public Uni<List<SimpleMembreInOutData>> getAllExport(String subject) {
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> repository.list("club = ?1", membreModel.getClub()))
.chain(membres -> licenceRepository.list("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres)
.map(l -> membres.stream().map(m -> SimpleMembreInOutData.fromModel(m, l)).toList()));
}
public Uni<String> allImporte(String subject, List<SimpleMembreInOutData> data) {
if (data == null)
return Uni.createFrom().nullItem();
final List<SimpleMembreInOutData> data2 = data.stream()
.filter(dataIn -> dataIn.getNom() != null && !dataIn.getNom()
.isBlank() && dataIn.getPrenom() != null && !dataIn.getPrenom().isBlank()).toList();
if (data2.isEmpty())
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());
if (data2.stream().noneMatch(d -> d.getLicence() != null))
return Uni.createFrom().item(new ArrayList<MembreModel>());
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(o -> o != null && !o.isBlank())
.toList());
})
.call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) {
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) {
MembreModel model = membres.stream()
.filter(m -> (dataIn.getLicence() != null && Objects.equals(m.getLicence(),
dataIn.getLicence())) || m.getLname().equals(dataIn.getNom()) && m.getFname()
.equals(dataIn.getPrenom()) || (dataIn.getEmail() != null && !dataIn.getEmail()
.isBlank() && Objects.equals(m.getFname(), dataIn.getEmail()))).findFirst()
.orElseGet(() -> {
MembreModel mm = new MembreModel();
mm.setClub(clubModel.get());
mm.setLicences(new ArrayList<>());
mm.setCountry("FR");
return mm;
});
if (model.getId() != null) {
LOGGER.debugf("updating -> %s", dataIn.toString());
} else {
LOGGER.debugf("creating -> %s", dataIn.toString());
}
if (model.getEmail() != null && !model.getEmail().isBlank()) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' 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 '" + model.getEmail() + "' déja utiliser");
}
}
boolean add = model.getId() == null;
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.");
}
ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model);
ls.logChange("Prénom", model.getFname(),
dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1), model);
model.setLname(dataIn.getNom().toUpperCase());
model.setFname(dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1));
if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank()) {
ls.logChange("Email", model.getEmail(), dataIn.getEmail(), model);
model.setEmail(dataIn.getEmail());
}
model.setGenre(Genre.fromString(dataIn.getGenre()));
if (dataIn.getBirthdate() != null) {
if (model.getBirth_date() == null || !Objects.equals(model.getBirth_date().getTime(),
dataIn.getBirthdate().getTime()))
ls.logChange("Date de naissance", model.getBirth_date(), dataIn.getBirthdate(), model);
model.setBirth_date(dataIn.getBirthdate());
model.setCategorie(Utils.getCategoryFormBirthDate(model.getBirth_date(), new Date()));
}
uniResult = uniResult
.call(() -> Panache.withTransaction(() -> repository.persist(model)
.chain(membreModel1 -> dataIn.isLicenceCurrent() ? licenceRepository.find(
"membre.id = ?1 AND saison = ?2", membreModel1.getId(),
Utils.getSaison())
.firstResult()
.call(l -> {
if (l == null) {
l = new LicenceModel();
l.setMembre(membreModel1);
l.setClub_id(clubModel.get().getId());
l.setValidate(false);
l.setSaison(Utils.getSaison());
}
l.setCertificate(dataIn.getCertif());
return licenceRepository.persist(l);
}) : licenceRepository.delete(
"membre = ?1 AND saison = ?2 AND validate = false", membreModel1,
Utils.getSaison()))));
if (add)
uniResult = uniResult.call(() -> ls.logAAdd(model));
else
uniResult = uniResult.call(() -> ls.append());
}
return uniResult;
}))
.map(__ -> "OK");
}
public Uni<MembreModel> getById(long id) { public Uni<MembreModel> getById(long id) {
return repository.findById(id); return repository.findById(id);
} }
public Uni<MembreModel> getByIdWithLicence(long id) {
return repository.findById(id)
.call(m -> Mutiny.fetch(m.getLicences()));
}
public Uni<MembreModel> getByAccountId(String subject) {
return repository.find("userId = ?1", subject).firstResult();
}
public Uni<MembreModel> getByLicence(long licence) {
return repository.find("licence = ?1", licence).firstResult();
}
public Uni<String> update(long id, FullMemberForm membre) { public Uni<String> update(long id, FullMemberForm membre) {
return update(repository.findById(id) return repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id) .chain(membreModel -> clubRepository.findById(membre.getClub()).map(club -> new Pair<>(membreModel, club)))
.invoke(Unchecked.consumer(c -> { .onItem().transformToUni(pair -> {
if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utiliser");
})))
.chain(membreModel -> clubRepository.findById(membre.getClub())
.map(club -> new Pair<>(membreModel, club)))
.onItem().transform(pair -> {
MembreModel m = pair.getKey(); MembreModel m = pair.getKey();
m.setFname(membre.getFname());
ls.logChange("Rôle", m.getRole(), membre.getRole(), m); m.setLname(membre.getLname());
m.setRole(membre.getRole());
ls.logChange("Club", m.getClub(), pair.getValue(), m);
m.setClub(pair.getValue()); m.setClub(pair.getValue());
ls.logChange("Grade d'arbitrage", m.getGrade_arbitrage(), membre.getGrade_arbitrage(), m); 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.setGrade_arbitrage(membre.getGrade_arbitrage());
return m; m.setEmail(membre.getEmail());
}), membre, true); return Panache.withTransaction(() -> repository.persist(m));
}
public Uni<String> update(long id, FullMemberForm membre, SecurityCtx securityCtx) {
return update(repository.findById(id)
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utiliser");
})))
.invoke(Unchecked.consumer(membreModel -> {
if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
throw new DForbiddenException();
if (StringSimilarity.similarity(membreModel.getLname().toUpperCase(),
membre.getLname().toUpperCase()) > 3 || StringSimilarity.similarity(
membreModel.getFname().toUpperCase(), membre.getFname().toUpperCase()) > 3) {
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez utilisez le bouton prévue a cette effet.");
}
}))
.invoke(Unchecked.consumer(membreModel -> {
RoleAsso source = RoleAsso.MEMBRE;
if (securityCtx.roleHas("club_president")) source = RoleAsso.PRESIDENT;
else if (securityCtx.roleHas("club_secretaire")) source = RoleAsso.SECRETAIRE;
else if (securityCtx.roleHas("club_respo_intra")) source = RoleAsso.MEMBREBUREAU;
if (!membre.getRole().equals(membreModel.getRole()) && membre.getRole().level >= source.level)
throw new DForbiddenException("Permission insuffisante");
}))
.onItem().transform(target -> {
if (!securityCtx.getSubject().equals(target.getUserId())) {
ls.logChange("Rôle", target.getRole(), membre.getRole(), target);
target.setRole(membre.getRole());
}
return target;
}), membre, false);
}
private Uni<String> update(Uni<MembreModel> uni, FullMemberForm membre, boolean admin) {
return uni.chain(target -> {
ls.logChange("Prénom", target.getFname(), membre.getFname(), target);
target.setFname(membre.getFname());
ls.logChange("Nom", target.getLname(), membre.getLname(), target);
target.setLname(membre.getLname().toUpperCase());
ls.logChange("Pays", target.getCountry(), membre.getCountry(), target);
target.setCountry(membre.getCountry());
if (membre.getBirth_date() != null && (target.getBirth_date() == null || !Objects.equals(
target.getBirth_date().getTime(), membre.getBirth_date().getTime()))) {
ls.logChange("Date de naissance", target.getBirth_date(), membre.getBirth_date(), target);
target.setBirth_date(membre.getBirth_date());
target.setCategorie(Utils.getCategoryFormBirthDate(membre.getBirth_date(), new Date()));
}
ls.logChange("Genre", target.getGenre(), membre.getGenre(), target);
target.setGenre(membre.getGenre());
ls.logChange("Email", target.getEmail(), membre.getEmail(), target);
target.setEmail(membre.getEmail());
return Panache.withTransaction(() -> repository.persist(target)).call(() -> ls.append());
}) })
.invoke(membreModel -> SReqComb.sendIfNeed(serverCustom.clients, .invoke(membreModel -> SReqComb.sendIfNeed(serverCustom.clients, SimpleCombModel.fromModel(membreModel)))
SimpleCombModel.fromModel(membreModel))) .call(membreModel -> (membreModel.getUserId() != null) ?
.call(membreModel -> (admin && membreModel.getUserId() != null) ? keycloakService.setClubGroupMembre(membreModel, membreModel.getClub()) : Uni.createFrom().nullItem())
((membreModel.getClub() != null) ?
keycloakService.setClubGroupMembre(membreModel, membreModel.getClub()) :
keycloakService.clearUser(membreModel.getUserId()))
: Uni.createFrom().nullItem())
.call(membreModel -> (membreModel.getUserId() != null) ? .call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.setAutoRoleMembre(membreModel.getUserId(), membreModel.getRole(), keycloakService.setAutoRoleMembre(membreModel.getUserId(), membreModel.getRole(),
membreModel.getGrade_arbitrage()) : Uni.createFrom().nullItem()) membreModel.getGrade_arbitrage()) : Uni.createFrom().nullItem())
.call(membreModel -> (membreModel.getUserId() != null) ? .call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.setEmail(membreModel.getUserId(), membreModel.getEmail()) : Uni.createFrom() keycloakService.setEmail(membreModel.getUserId(), membreModel.getEmail()) : Uni.createFrom().nullItem())
.nullItem())
.call(membreModel -> {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, -7);
Date dateLimit = calendar.getTime();
return competitionRepository.list("date > ?1", dateLimit)
.call(l -> l.isEmpty() ? Uni.createFrom().nullItem() :
Uni.join().all(l.stream().map(competitionModel ->
registerRepository.update(
"categorie = ?1, club = ?2 where competition = ?3 AND membre = ?4",
membreModel.getCategorie(), membreModel.getClub(), competitionModel,
membreModel)
).toList()).andFailFast());
})
.call(membreModel -> licenceRepository.update("club_id = ?1 where membre = ?2 AND saison = ?3",
(membreModel.getClub() == null) ? null : membreModel.getClub().getId(), membreModel,
Utils.getSaison()))
.call(membreModel -> membre.getPhoto_data().length > 0 ? ls.logAUpdate("Photo",
membreModel) : Uni.createFrom().nullItem())
.map(__ -> "OK"); .map(__ -> "OK");
} }
public Uni<String> update(long id, ClubMemberForm membre, JsonWebToken idToken, SecurityIdentity securityIdentity) {
return repository.findById(id)
.invoke(Unchecked.consumer(membreModel -> {
if (!GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new ForbiddenException();
}))
.invoke(Unchecked.consumer(membreModel -> {
RoleAsso source = RoleAsso.MEMBRE;
if (securityIdentity.getRoles().contains("club_president")) source = RoleAsso.PRESIDENT;
else if (securityIdentity.getRoles().contains("club_secretaire")) source = RoleAsso.SECRETAIRE;
else if (securityIdentity.getRoles().contains("club_respo_intra")) source = RoleAsso.SECRETAIRE;
if (!membre.getRole().equals(membreModel.getRole()) && membre.getRole().level > source.level)
throw new ForbiddenException();
}))
.onItem().transformToUni(target -> {
target.setFname(membre.getFname());
target.setLname(membre.getLname());
target.setCountry(membre.getCountry());
target.setBirth_date(membre.getBirth_date());
target.setGenre(membre.getGenre());
target.setCategorie(membre.getCategorie());
target.setEmail(membre.getEmail());
if (!idToken.getSubject().equals(target.getUserId()))
target.setRole(membre.getRole());
return Panache.withTransaction(() -> repository.persist(target));
})
.invoke(membreModel -> SReqComb.sendIfNeed(serverCustom.clients, SimpleCombModel.fromModel(membreModel)))
.call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.setAutoRoleMembre(membreModel.getUserId(), membreModel.getRole(),
membreModel.getGrade_arbitrage()) : Uni.createFrom().nullItem())
.call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.setEmail(membreModel.getUserId(), membreModel.getEmail()) : Uni.createFrom().nullItem())
.map(__ -> "OK");
}
public Uni<Long> add(FullMemberForm input) { public Uni<Long> add(FullMemberForm input) {
return clubRepository.findById(input.getClub()) return clubRepository.findById(input.getClub())
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
.invoke(Unchecked.consumer(c -> {
if (c > 0) throw new DBadRequestException("Email déjà utiliser");
})))
.chain(clubModel -> { .chain(clubModel -> {
MembreModel model = getMembreModel(input, clubModel); MembreModel model = getMembreModel(input, clubModel);
return Panache.withTransaction(() -> repository.persist(model)); return Panache.withTransaction(() -> repository.persist(model));
}) })
.call(membreModel -> ls.logAAdd(membreModel)) .invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients, SimpleCombModel.fromModel(membreModel)))
.invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients,
SimpleCombModel.fromModel(membreModel)))
.map(MembreModel::getId); .map(MembreModel::getId);
} }
public Uni<Long> add(FullMemberForm input, String subject) { public Uni<Long> add(FullMemberForm input, String subject) {
return repository.find("userId = ?1", subject).firstResult() return repository.find("userId = ?1", subject).firstResult()
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
.invoke(Unchecked.consumer(c -> {
if (c > 0) throw new DBadRequestException("Email déjà utiliser");
})))
.call(membreModel ->
repository.count(
"unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2) AND club = ?3",
input.getLname(), input.getFname(), membreModel.getClub())
.invoke(Unchecked.consumer(c -> {
if (c > 0)
throw new DBadRequestException("Membre déjà existent");
})))
.chain(membreModel -> { .chain(membreModel -> {
MembreModel model = getMembreModel(input, membreModel.getClub()); MembreModel model = getMembreModel(input, membreModel.getClub());
model.setRole(RoleAsso.MEMBRE); model.setRole(RoleAsso.MEMBRE);
model.setGrade_arbitrage(GradeArbitrage.NA); model.setGrade_arbitrage(GradeArbitrage.NA);
return Panache.withTransaction(() -> repository.persist(model)); return Panache.withTransaction(() -> repository.persist(model));
}) })
.call(membreModel -> ls.logAAdd(membreModel)) .invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients, SimpleCombModel.fromModel(membreModel)))
.invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients,
SimpleCombModel.fromModel(membreModel)))
.map(MembreModel::getId); .map(MembreModel::getId);
} }
@ -534,44 +191,33 @@ public class MembreService {
return repository.findById(id) return repository.findById(id)
.call(membreModel -> (membreModel.getUserId() != null) ? .call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem()) keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
.call(membreModel -> ls.logADelete(membreModel))
.call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel))) .call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
.invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id)) .invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
.map(__ -> "Ok"); .map(__ -> "Ok");
} }
public Uni<String> delete(long id, SecurityCtx securityCtx) { public Uni<String> delete(long id, JsonWebToken idToken) {
return repository.findById(id) return repository.findById(id)
.invoke(Unchecked.consumer(membreModel -> { .invoke(Unchecked.consumer(membreModel -> {
if (!securityCtx.isInClubGroup(membreModel.getClub().getId())) if (!GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new DForbiddenException(); throw new ForbiddenException();
}))
.invoke(Unchecked.consumer(membreModel -> {
if (membreModel.getLicence() != null) {
throw new DBadRequestException(
"Impossible de supprimer un membre qui a déjà un numéro de licence");
}
})) }))
.call(membreModel -> licenceRepository.find("membre = ?1", membreModel).count() .call(membreModel -> licenceRepository.find("membre = ?1", membreModel).count()
.invoke(Unchecked.consumer(l -> { .invoke(Unchecked.consumer(l -> {
if (l > 0) if (l > 0)
throw new DBadRequestException("Impossible de supprimer un membre avec des licences"); throw new BadRequestException();
}))) })))
.call(membreModel -> (membreModel.getUserId() != null) ? .call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem()) keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
.call(membreModel -> ls.logADelete(membreModel))
.call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel))) .call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
.invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id)) .invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
.call(__ -> Utils.deleteMedia(id, media, "ppMembre"))
.map(__ -> "Ok"); .map(__ -> "Ok");
} }
public Uni<?> setUserId(Long id, String id1) { public Uni<?> setUserId(Long id, String id1) {
return repository.findById(id).chain(membreModel -> { return repository.findById(id).chain(membreModel -> {
ls.logChange("KC UUID", membreModel.getUserId(), id1, membreModel);
membreModel.setUserId(id1); membreModel.setUserId(id1);
return Panache.withTransaction(() -> repository.persist(membreModel)) return Panache.withTransaction(() -> repository.persist(membreModel));
.call(() -> ls.append());
}); });
} }
@ -580,42 +226,13 @@ public class MembreService {
model.setFname(input.getFname()); model.setFname(input.getFname());
model.setLname(input.getLname()); model.setLname(input.getLname());
model.setEmail(input.getEmail()); model.setEmail(input.getEmail());
model.setLicence(null);
model.setGenre(input.getGenre()); model.setGenre(input.getGenre());
model.setCountry(input.getCountry()); model.setCountry(input.getCountry());
model.setBirth_date(input.getBirth_date()); model.setBirth_date(input.getBirth_date());
model.setCategorie(Utils.getCategoryFormBirthDate(input.getBirth_date(), new Date())); model.setCategorie(input.getCategorie());
model.setClub(clubModel); model.setClub(clubModel);
model.setRole(input.getRole()); model.setRole(input.getRole());
model.setGrade_arbitrage(input.getGrade_arbitrage()); model.setGrade_arbitrage(input.getGrade_arbitrage());
return model; return model;
} }
public Uni<List<SimpleMembre>> getSimilar(String fname, String lname) {
return repository.listAll().map(membreModels -> membreModels.stream()
.filter(m -> StringSimilarity.similarity(m.getFname(), fname) <= 3 &&
StringSimilarity.similarity(m.getLname(), lname) <= 3)
.map(SimpleMembre::fromModel).toList());
}
public Uni<MeData> getMembre(String subject) {
MeData meData = new MeData();
return repository.find("userId = ?1", subject).firstResult()
.invoke(meData::setMembre)
.chain(membreModel -> Mutiny.fetch(membreModel.getLicences()))
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
.invoke(meData::setLicences)
.map(__ -> meData);
}
@Scheduled(cron = "0 0 1 1 9 ?")
Uni<Void> everySeason() {
return repository.list("birth_date IS NOT NULL")
.chain(l -> Uni.join().all(l.stream().map(m -> {
m.setCategorie(Utils.getCategoryFormBirthDate(m.getBirth_date(), new Date()));
return Panache.withTransaction(() -> repository.persist(m));
}).toList()).andCollectFailures())
.map(__ -> null);
}
} }

View File

@ -1,232 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.LicenceModel;
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.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@WithSession
@ApplicationScoped
public class PDFService {
private static final Logger LOGGER = Logger.getLogger(PDFService.class);
@Inject
CombRepository combRepository;
@Inject
ClubRepository clubRepository;
@ConfigProperty(name = "upload_dir")
String media;
@ConfigProperty(name = "pdf-maker.jar-path")
String pdfMakerJarPath;
@ConfigProperty(name = "pdf-maker.sign-file")
String sign_file;
public Uni<Response> getLicencePdf(String subject) {
return getLicencePdf(combRepository.find("userId = ?1", subject).firstResult()
.call(m -> Mutiny.fetch(m.getLicences())));
}
public Uni<Response> getLicencePdf(Uni<MembreModel> uniBase) {
return uniBase
.map(Unchecked.function(m -> {
LicenceModel licence = m.getLicences().stream()
.filter(licenceModel -> licenceModel.getSaison() == Utils.getSaison() && licenceModel.isValidate())
.findFirst()
.orElseThrow(() -> new DNotFoundException("Pas de licence pour la saison en cours"));
try {
byte[] buff = make_pdf(m, licence);
if (buff == null)
throw new IOException("Error making pdf");
String mimeType = "application/pdf";
Response.ResponseBuilder resp = Response.ok(buff);
resp.type(MediaType.APPLICATION_OCTET_STREAM);
resp.header(HttpHeaders.CONTENT_LENGTH, buff.length);
resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
resp.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; " + "filename=\"Attestation d'adhésion " + Utils.getSaison() + "-" +
(Utils.getSaison() + 1) + " de " + m.getLname() + " " + m.getFname() + ".pdf\"");
return resp.build();
} catch (Exception e) {
throw new IOException(e);
}
}));
}
private byte[] make_pdf(MembreModel m, LicenceModel licence) throws IOException, InterruptedException {
List<String> cmd = new ArrayList<>();
cmd.add("java");
cmd.add("-jar");
cmd.add(pdfMakerJarPath);
UUID uuid = UUID.randomUUID();
cmd.add("/tmp/" + uuid + ".pdf");
cmd.add("membre");
cmd.add(m.getFname());
cmd.add(m.getLname());
cmd.add(m.getGenre().str);
cmd.add(m.getCategorie().getName());
cmd.add(licence.getCertificate() == null ? "" : licence.getCertificate());
cmd.add(Utils.getSaison() + "");
cmd.add(m.getLicence() + "");
cmd.add(m.getClub().getName());
cmd.add(m.getClub().getNo_affiliation() + "");
cmd.add(m.getBirth_date() == null ? "--" : new SimpleDateFormat("dd/MM/yyyy").format(m.getBirth_date()));
FilenameFilter filter = (directory, filename) -> filename.startsWith(m.getId() + ".");
File[] files = new File(media, "ppMembre").listFiles(filter);
if (files != null && files.length > 0) {
File file = files[0];
cmd.add(file.getAbsolutePath());
} else {
cmd.add("/dev/null");
}
return getPdf(cmd, uuid);
}
public Uni<Response> getAffiliationPdf(String subject) {
return getAffiliationPdf(
combRepository.find("userId = ?1", subject).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
}))
.map(MembreModel::getClub)
.call(m -> Mutiny.fetch(m.getAffiliations())));
}
public Uni<Response> getAffiliationPdf(long id) {
return getAffiliationPdf(
clubRepository.findById(id)
.invoke(Unchecked.consumer(m -> {
if (m == null)
throw new DNotFoundException("Club non trouvé");
}))
.call(m -> Mutiny.fetch(m.getAffiliations())));
}
private Uni<Response> getAffiliationPdf(Uni<ClubModel> uniBase) {
return uniBase
.map(Unchecked.function(m -> {
if (m.getAffiliations().stream()
.noneMatch(licenceModel -> licenceModel.getSaison() == Utils.getSaison()))
throw new DNotFoundException("Pas d'affiliation pour la saison en cours");
try {
byte[] buff = make_pdf(m);
if (buff == null)
throw new IOException("Error making pdf");
String mimeType = "application/pdf";
Response.ResponseBuilder resp = Response.ok(buff);
resp.type(MediaType.APPLICATION_OCTET_STREAM);
resp.header(HttpHeaders.CONTENT_LENGTH, buff.length);
resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
resp.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; " + "filename=\"Attestation d'affiliation " + Utils.getSaison() + "-" +
(Utils.getSaison() + 1) + " de " + m.getName() + ".pdf\"");
return resp.build();
} catch (Exception e) {
throw new IOException(e);
}
}));
}
private byte[] make_pdf(ClubModel m) throws IOException, InterruptedException {
List<String> cmd = new ArrayList<>();
cmd.add("java");
cmd.add("-jar");
cmd.add(pdfMakerJarPath);
UUID uuid = UUID.randomUUID();
cmd.add("/tmp/" + uuid + ".pdf");
cmd.add("club");
cmd.add(m.getName());
cmd.add(Utils.getSaison() + "");
cmd.add(m.getNo_affiliation() + "");
cmd.add(new File(sign_file).getAbsolutePath());
return getPdf(cmd, uuid);
}
static byte[] getPdf(List<String> cmd, UUID uuid) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(cmd);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder builder = new StringBuilder();
Thread t = new Thread(() -> {
try {
String line;
while ((line = reader.readLine()) != null)
builder.append(line).append("\n");
} catch (Exception ignored) {
}
});
t.start();
int code = -1;
if (!process.waitFor(30, TimeUnit.SECONDS)) {
process.destroy();
builder.append("Timeout...");
} else {
code = process.exitValue();
}
if (t.isAlive())
t.interrupt();
PDFService.LOGGER.debug("PDF maker: " + builder);
if (code != 0) {
throw new IOException("Error code: " + code);
} else {
File file = new File("/tmp/" + uuid + ".pdf");
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buff = fis.readAllBytes();
//noinspection ResultOfMethodCallIgnored
file.delete();
return buff;
} catch (IOException e) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
return null;
}
}
}

View File

@ -1,71 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.rest.data.LicenceStats;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.Genre;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.concurrent.ConcurrentHashMap;
@WithSession
@ApplicationScoped
public class StatsService {
@Inject
LicenceRepository licenceRepository;
public Uni<LicenceStats> getStats() {
ConcurrentHashMap<Categorie, Integer> categories = new ConcurrentHashMap<>();
LicenceStats stats = new LicenceStats();
int currentSaison = Utils.getSaison();
//noinspection ReactiveStreamsUnusedPublisher
return licenceRepository.listAll()
.onItem().transformToMulti(licences -> Multi.createFrom().iterable(licences))
.call(licence -> Mutiny.fetch(licence.getMembre()))
.invoke(licence -> {
if (!stats.getLicences().containsKey(licence.getSaison()))
stats.getLicences().put(licence.getSaison(), new LicenceStats.YearStats());
LicenceStats.YearStats yearStats = stats.getLicences().get(licence.getSaison());
System.out.println("stats: " + licence.getMembre().getFname());
if (licence.isValidate()) {
if (licence.getMembre().getGenre() == Genre.H)
yearStats.addH();
else if (licence.getMembre().getGenre() == Genre.F)
yearStats.addF();
else
yearStats.addNa();
if (licence.getSaison() == currentSaison && licence.getMembre().getCategorie() != null) {
categories.put(licence.getMembre().getCategorie(),
categories.getOrDefault(licence.getMembre().getCategorie(), 0) + 1);
}
} else {
yearStats.addNotValid();
}
})
.collect().asList()
.map(__ -> {
for (Categorie c : Categorie.values()) {
LicenceStats.CategoriesStats o = new LicenceStats.CategoriesStats();
o.setName(c.getName());
o.setCount(categories.getOrDefault(c, 0));
stats.getCategories().add(o);
}
return stats;
});
}
}

View File

@ -1,40 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.rest.client.dto.HelloassoNotification;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class WebhookService {
@Inject
CheckoutService checkoutService;
@Inject
CompetitionService competitionService;
@ConfigProperty(name = "helloasso.organizationSlug")
String organizationSlug;
public Uni<Response> helloAssoNotification(HelloassoNotification notification) {
if (notification.getEventType().equals("Payment")) {
if (notification.getData().getOrder().getFormType().equals("Checkout")) {
if (notification.getData().getOrder().getOrganizationSlug().equalsIgnoreCase(organizationSlug)) {
return checkoutService.paymentStatusChange(notification.getData().getState(),
notification.getMetadata());
}
} else if (notification.getData().getOrder().getFormType().equals("Event")) {
return competitionService.unregisterHelloAsso(notification.getData());
}
}else if (notification.getEventType().equals("Order")){
if (notification.getData().getFormType().equals("Event")) {
return competitionService.registerHelloAsso(notification.getData());
}
}
return Uni.createFrom().item(Response.ok().build());
}
}

View File

@ -40,7 +40,7 @@ public class Client_Thread extends Thread {
private boolean isAuth; private boolean isAuth;
private final HashMap<UUID, JsonConsumer<?>> waitResult = new HashMap<>(); private final HashMap<UUID, JsonConsumer<Object>> waitResult = new HashMap<>();
public Client_Thread(ServerCustom serv, Socket s, PublicKey publicKey) throws IOException { public Client_Thread(ServerCustom serv, Socket s, PublicKey publicKey) throws IOException {
this.serv = serv; this.serv = serv;
@ -162,7 +162,7 @@ public class Client_Thread extends Thread {
sendReq(object, type, null); sendReq(object, type, null);
} }
public void sendReq(Object object, String code, JsonConsumer<?> consumer) { public void sendReq(Object object, String code, JsonConsumer<Object> consumer) {
UUID uuid; UUID uuid;
do { do {
uuid = UUID.randomUUID(); uuid = UUID.randomUUID();

View File

@ -22,7 +22,6 @@ public class SimpleClubModel {
if (model == null) if (model == null)
return null; return null;
return new SimpleClubModel(model.getId(), model.getName(), model.getCountry(), return new SimpleClubModel(model.getId(), model.getName(), model.getCountry(), model.getShieldURL());
"/api/club/" + model.getClubId() + "/logo");
} }
} }

View File

@ -8,14 +8,12 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@RegisterForReflection @RegisterForReflection
@Schema(hidden = true)
public class SimpleCombModel { public class SimpleCombModel {
Long id; Long id;
String lname = ""; String lname = "";
@ -26,12 +24,12 @@ public class SimpleCombModel {
int licence = 0; int licence = 0;
String country = "fr"; String country = "fr";
public static SimpleCombModel fromModel(MembreModel model) { public static SimpleCombModel fromModel(MembreModel model) {
if (model == null) if (model == null)
return null; return null;
return new SimpleCombModel(model.getId(), model.getLname(), model.getFname(), model.getCategorie(), return new SimpleCombModel(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
(model.getClub() == null) ? null : SimpleClubModel.fromModel(model.getClub()), (model.getClub() == null) ? null : SimpleClubModel.fromModel(model.getClub()),
model.getGenre(), (model.getLicence() == null) ? -1 : model.getLicence(), model.getCountry()); model.getGenre(), model.getLicence(), model.getCountry());
} }
} }

View File

@ -1,10 +0,0 @@
package fr.titionfire.ffsaf.net2.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import java.util.List;
import java.util.UUID;
@RegisterForReflection
public record SimpleCompet(long id, String owner, boolean show_blason, boolean show_flag, List<UUID> admin, List<UUID> table) {
}

View File

@ -11,6 +11,16 @@ import java.util.HashMap;
public class RComb { public class RComb {
private static final Logger LOGGER = Logger.getLogger(RComb.class); 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) -> { final CIA<Long> findByIdOptionalComb = new CIA<>(Long.class, (client_Thread, message) -> {
try { try {
SimpleCombModel combModel = ServerCustom.getInstance().membreService.findByIdOptionalComb(message.data()); SimpleCombModel combModel = ServerCustom.getInstance().membreService.findByIdOptionalComb(message.data());
@ -24,6 +34,7 @@ public class RComb {
public static void register(HashMap<String, IAction> iMap) { public static void register(HashMap<String, IAction> iMap) {
RComb rComb = new RComb(); RComb rComb = new RComb();
iMap.put("findComb", rComb.findComb);
iMap.put("findByIdOptionalComb", rComb.findByIdOptionalComb); iMap.put("findByIdOptionalComb", rComb.findByIdOptionalComb);
} }
} }

View File

@ -0,0 +1,42 @@
package fr.titionfire.ffsaf.net2.packet;
import fr.titionfire.ffsaf.ws.FileSocket;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.UUID;
@ApplicationScoped
public class RFile {
private static final Logger LOGGER = Logger.getLogger(RFile.class);
final IAction requestSend = (client_Thread, message) -> {
try {
switch (message.data().get("type").asText()) {
case "match":
String code = UUID.randomUUID() + "-" + UUID.randomUUID();
FileSocket.FileRecv fileRecv = new FileSocket.FileRecv(null, message.data().get("name").asText(), null, null,
System.currentTimeMillis());
FileSocket.sessions.put(code, fileRecv);
client_Thread.sendRepTo(code, message);
break;
default:
client_Thread.sendErrTo("", message);
break;
}
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
client_Thread.sendErrTo(e.getMessage(), message);
}
};
public static void register(HashMap<String, IAction> iMap) {
RFile rFile = new RFile();
iMap.put("requestSend", rFile.requestSend);
}
}

View File

@ -9,5 +9,6 @@ public class RegisterAction {
RComb.register(iMap); RComb.register(iMap);
RClub.register(iMap); RClub.register(iMap);
RFile.register(iMap);
} }
} }

View File

@ -1,40 +0,0 @@
package fr.titionfire.ffsaf.net2.request;
import fr.titionfire.ffsaf.net2.Client_Thread;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.utils.JsonConsumer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
public class SReqCompet {
public static void sendUpdate(ArrayList<Client_Thread> client_Thread, SimpleCompet compet) {
for (Client_Thread client : client_Thread) {
client.sendNotify(compet, "sendConfig");
}
}
public static void getConfig(ArrayList<Client_Thread> client_Thread, long id_compet,
CompletableFuture<SimpleCompet> future) {
if (client_Thread.isEmpty()) return;
client_Thread.get(0).sendReq(id_compet, "getConfig",
new JsonConsumer<>(SimpleCompet.class, future::complete));
}
public static void getAllHaveAccess(ArrayList<Client_Thread> client_Thread, String userId,
CompletableFuture<HashMap<String, String>> future) {
if (client_Thread.isEmpty()) return;
client_Thread.get(0).sendReq(userId, "getAllHaveAccess",
new JsonConsumer<>(HashMap.class, future::complete));
}
public static void rmCompet(ArrayList<Client_Thread> client_Thread, long id_compet) {
for (Client_Thread client : client_Thread) {
client.sendNotify(id_compet, "rmCompet");
}
}
}

View File

@ -1,29 +0,0 @@
package fr.titionfire.ffsaf.net2.request;
import fr.titionfire.ffsaf.net2.Client_Thread;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import java.util.ArrayList;
import java.util.HashMap;
public class SReqRegister {
public static void sendIfNeed(ArrayList<Client_Thread> client_Thread, CompetitionData.SimpleRegister simpleRegister, Long competitionId) {
HashMap<String, Object> map = new HashMap<>();
map.put("simpleRegister", simpleRegister);
map.put("competitionId", competitionId);
for (Client_Thread client : client_Thread) {
client.sendNotify(map, "sendRegister");
}
}
public static void sendRmIfNeed(ArrayList<Client_Thread> clients, Long combId, Long id) {
HashMap<String, Object> map = new HashMap<>();
map.put("combId", combId);
map.put("competitionId", id);
for (Client_Thread client : clients) {
client.sendNotify(map, "sendRmRegister");
}
}
}

View File

@ -1,94 +1,29 @@
package fr.titionfire.ffsaf.rest; package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.AffiliationService; import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.smallrye.mutiny.Uni; 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.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.function.Consumer;
@Tag(name = "Affiliation API", description = "API pour gérer les affiliations")
@Path("api/affiliation") @Path("api/affiliation")
public class AffiliationEndpoints { public class AffiliationEndpoints {
@Inject
AffiliationService service;
@Inject
SecurityCtx securityCtx;
Consumer<Long> checkPerm = Unchecked.consumer(id -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(id))
throw new DForbiddenException();
});
@GET
@Path("/current")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les affiliations pour la saison en cours", description = "Cette méthode renvoie les affiliations pour la saison en cours. Seuls les administrateurs de la fédération peuvent accéder à cette méthode.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<List<SimpleAffiliation>> getCurrentSaisonAffiliationAdmin() {
return service.getCurrentSaisonAffiliation();
}
@GET
@Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les affiliations pour un club", description = "Cette méthode renvoie les affiliations pour un club donné. Seuls les administrateurs de la fédération et les présidents, secrétaires et responsables intranet du club peuvent accéder à cette méthode.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Club non trouvé")
})
public Uni<List<SimpleAffiliation>> getAffiliation(
@Parameter(description = "L'identifiant du club") @PathParam("id") long id) {
return Uni.createFrom().item(id).invoke(checkPerm).chain(__ -> service.getAffiliation(id));
}
@POST @POST
@Path("{id}") @Path("save")
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Ajoute une affiliation pour un club", description = "Cette méthode ajoute une affiliation pour un club et une saison donné. Seuls les administrateurs de la fédération peuvent accéder à cette méthode.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Club non trouvé")
})
public Uni<SimpleAffiliation> setAffiliation(
@Parameter(description = "L'identifiant du club") @PathParam("id") long id,
@Parameter(description = "La saison à pour la quelle ajoute l'affiliation") @QueryParam("saison") int saison) {
return service.setAffiliation(id, saison);
}
@DELETE
@Path("{id}")
@RolesAllowed("federation_admin")
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime une affiliation", description = "Cette méthode supprime l'affiliation {id}. Seuls les administrateurs de la fédération peuvent accéder à cette méthode.") @Consumes(MediaType.MULTIPART_FORM_DATA)
@APIResponses(value = { public Uni<String> saveAffRequest(AffiliationRequestForm form) {
@APIResponse(responseCode = "204", description = "Réussite"), System.out.println(form);
@APIResponse(responseCode = "403", description = "Accès refusé") return Uni.createFrom().item("OK");
})
public Uni<?> deleteAffiliation(
@Parameter(description = "L'identifiant de l'affiliation") @PathParam("id") long id) {
return service.deleteAffiliation(id);
} }
/*@POST
@Path("affiliation")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<String> saveAffRequest(AffiliationRequestForm form) {
System.out.println(form);
return service.save(form);
}*/
} }

View File

@ -1,191 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.AffiliationService;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliationResume;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
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.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import java.net.URISyntaxException;
import java.util.List;
import java.util.function.Consumer;
@Path("api/affiliation/request")
public class AffiliationRequestEndpoints {
@Inject
AffiliationService service;
@Inject
SecurityCtx securityCtx;
@ConfigProperty(name = "upload_dir")
String media;
Consumer<Long> checkPerm = Unchecked.consumer(id -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(id))
throw new DForbiddenException();
});
@GET
@Path("")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie toutes les demandes d'affiliation", description = "Cette méthode renvoie toutes les " +
"demandes d'affiliation sous forme de résumés.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<List<SimpleReqAffiliationResume>> getAllAffRequest() {
return service.getAllReq().map(o -> o.stream().map(SimpleReqAffiliationResume::fromModel).toList());
}
@POST
@Path("")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Enregistre une nouvelle demande d'affiliation", description = "Cette méthode enregistre une " +
"nouvelle demande d'affiliation à partir des données soumises dans le formulaire. Ne nécessite pas d'authentification.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<String> saveAffRequest(AffiliationRequestForm form) {
return service.save(form);
}
@GET
@Path("/{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie une demande d'affiliation", description = "Cette méthode renvoie une demande d'affiliation " +
"pour l'identifiant spécifié.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Demande d'affiliation non trouvée")
})
public Uni<SimpleReqAffiliation> getAffRequest(
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) {
return service.getRequest(id).invoke(Unchecked.consumer(o -> {
if (o.getClub() == null && !securityCtx.roleHas("federation_admin"))
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()));
}
@DELETE
@Path("/{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " +
"d'affiliation pour l'identifiant spécifié.")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Demande d'affiliation non trouvée")
})
public Uni<?> getDelAffRequest(
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id, @QueryParam("reason") String reason) {
return service.getRequest(id).invoke(Unchecked.consumer(o -> {
if (o.getClub() == null && !securityCtx.roleHas("federation_admin"))
throw new DForbiddenException();
})).invoke(o -> checkPerm.accept(o.getClub()))
.chain(o -> service.deleteReqAffiliation(id, reason, securityCtx.roleHas("federation_admin")));
}
@PUT
@Path("/save")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Enregistre une demande d'affiliation en tant qu'admin", description = "Cette méthode " +
"enregistre une demande d'affiliation en tant qu'admin à partir des données soumises dans le formulaire.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<?> saveAdminAffRequest(AffiliationRequestSaveForm form) {
return service.saveAdmin(form);
}
@PUT
@Path("/edit")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Modifie une demande d'affiliation", description = "Cette méthode modifie une demande " +
"d'affiliation à partir des données soumises dans le formulaire.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<?> saveEditAffRequest(AffiliationRequestForm form) {
return service.saveEdit(form);
}
@PUT
@Path("/apply")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Accepte une demande d'affiliation", description = "Cette méthode accepte une demande " +
"d'affiliation à partir des données soumises dans le formulaire.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "400", description = "Données invalides"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<?> acceptAffRequest(AffiliationRequestSaveForm form) {
return service.accept(form);
}
@GET
@Path("/{id}/logo")
@RolesAllowed({"federation_admin"})
@Operation(summary = "Renvoie le logo d'une demande d'affiliation", description = "Cette méthode renvoie le logo" +
" d'une demande d'affiliation pour l'identifiant spécifié.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Logo non trouvé")
})
public Uni<Response> getLogo(
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) throws URISyntaxException {
return Utils.getMediaFile(id, media, "aff_request/logo", Uni.createFrom().nullItem());
}
@GET
@Path("/{id}/status")
@RolesAllowed({"federation_admin"})
@Operation(summary = "Renvoie le statut d'une demande d'affiliation", description = "Cette méthode renvoie le statut" +
" d'une demande d'affiliation pour l'identifiant spécifié.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Statut non trouvé")
})
public Uni<Response> getStatus(
@Parameter(description = "L'identifiant de la demande d'affiliation") @PathParam("id") long id) throws URISyntaxException {
return Utils.getMediaFile(id, media, "aff_request/status", "affiliation_request_" + id,
Uni.createFrom().nullItem());
}
}

View File

@ -1,38 +1,53 @@
package fr.titionfire.ffsaf.rest; package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.AffiliationService;
import fr.titionfire.ffsaf.rest.client.SirenService; import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.client.StateIdService; import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import fr.titionfire.ffsaf.rest.data.AssoData; import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation; import jodd.net.MimeTypes;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.io.*;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@Path("api/asso") @Path("api/asso")
public class AssoEndpoints { public class AssoEndpoints {
@RestClient
StateIdService stateIdService;
@RestClient @RestClient
SirenService sirenService; SirenService sirenService;
@Inject
AffiliationService service;
@ConfigProperty(name = "upload_dir")
String media;
@GET @GET
@Path("state_id/{stateId}") @Path("siren/{siren}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true) public Uni<UniteLegaleRoot> getInfoSiren(@PathParam("siren") String siren) {
public Uni<AssoData> getAssoInfo(@PathParam("stateId") String stateId) { return sirenService.get_unite(siren).onFailure().transform(throwable -> {
return ((stateId.charAt(0) == 'W') ? stateIdService.get_rna(stateId) : sirenService.get_unite(
stateId).chain(stateIdService::getAssoDataFromUnit)).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) { if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 404)
return new DNotFoundException("Service momentanément indisponible");
if (exception.getResponse().getStatus() == 400) if (exception.getResponse().getStatus() == 400)
return new DNotFoundException("Asso introuvable"); return new BadRequestException("Not found");
} }
return throwable; return throwable;
}); });
} }
@POST
@Path("affiliation")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<String> saveAffRequest(AffiliationRequestForm form) {
return service.save(form);
}
} }

View File

@ -11,9 +11,6 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -28,16 +25,10 @@ public class AuthEndpoints {
SecurityIdentity securityIdentity; SecurityIdentity securityIdentity;
@Inject @Inject
JsonWebToken IdToken; JsonWebToken accessToken;
@GET @GET
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Vérifie si l'utilisateur est authentifié", description = "Cette méthode renvoie true si " +
"l'utilisateur est authentifié et false sinon.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite")
})
public Boolean auth() { public Boolean auth() {
return !securityIdentity.isAnonymous(); return !securityIdentity.isAnonymous();
} }
@ -46,21 +37,14 @@ public class AuthEndpoints {
@Path("/userinfo") @Path("/userinfo")
@Authenticated @Authenticated
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les informations de l'utilisateur authentifié", description = "Cette méthode renvoie les" +
" informations de l'utilisateur authentifié sous forme d'objet JSON.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Réussite"),
@APIResponse(responseCode = "401", description = "Utilisateur non authentifié")
})
public UserInfo userinfo() { public UserInfo userinfo() {
return UserInfo.makeUserInfo(IdToken, securityIdentity); return UserInfo.makeUserInfo(accessToken, securityIdentity);
} }
@GET @GET
@Path("/login") @Path("/login")
@Authenticated @Authenticated
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
@Operation(hidden = true)
public Response login() throws URISyntaxException { public Response login() throws URISyntaxException {
return Response.temporaryRedirect(new URI(redirect)).build(); return Response.temporaryRedirect(new URI(redirect)).build();
} }

View File

@ -1,63 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.CategoryService;
import fr.titionfire.ffsaf.rest.data.CategoryData;
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@Authenticated
@Path("api/poule/{system}/admin/")
public class CategoryAdminEndpoints {
@PathParam("system")
private CompetitionSystem system;
@Inject
CategoryService service;
@Inject
SecurityCtx securityCtx;
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<CategoryData> getByIdAdmin(@PathParam("id") Long id) {
return service.getByIdAdmin(securityCtx, system, id);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CategoryData>> getAllAdmin() {
return service.getAllAdmin(securityCtx, system);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<CategoryData> addOrUpdate(CategoryData data) {
return service.addOrUpdate(securityCtx, system, data);
}
@POST
@Path("sync")
@Consumes(MediaType.APPLICATION_JSON)
public Uni<?> syncCategory(CategoryFullData data) {
return service.syncCategory(securityCtx, system, data);
}
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> delete(@PathParam("id") Long id) {
return service.delete(securityCtx, system, id);
}
}

View File

@ -1,374 +1,28 @@
package fr.titionfire.ffsaf.rest; package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.domain.service.ClubService; import fr.titionfire.ffsaf.domain.service.ClubService;
import fr.titionfire.ffsaf.domain.service.PDFService;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel; import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullClubForm;
import fr.titionfire.ffsaf.rest.from.PartClubForm;
import fr.titionfire.ffsaf.utils.Contact;
import fr.titionfire.ffsaf.utils.PageResult;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.security.Authenticated; import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.*; 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.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
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.List;
import java.util.function.Consumer;
@Tag(name = "Club", description = "Gestion des clubs")
@Path("api/club") @Path("api/club")
public class ClubEndpoints { public class ClubEndpoints {
@Inject @Inject
ClubService clubService; ClubService clubService;
@Inject
PDFService pdfService;
@Inject
SecurityCtx securityCtx;
@ConfigProperty(name = "upload_dir")
String media;
Consumer<ClubModel> checkPerm = Unchecked.consumer(clubModel -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(clubModel.getId()))
throw new DForbiddenException();
});
Consumer<Long> checkPerm2 = Unchecked.consumer(id -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(id))
throw new DForbiddenException();
});
@GET @GET
@Path("/no_detail") @Path("/no_detail")
@Authenticated @Authenticated
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie la liste de tous les clubs sans détails", description = "Renvoie la liste de tous les " +
"clubs sans les détails des membres et des affiliations")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste de tous les clubs sans détails"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleClubModel>> getAll() { public Uni<List<SimpleClubModel>> getAll() {
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).sorted( return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList());
Comparator.comparing(SimpleClubModel::getName)).toList());
} }
@GET
@Path("/contact_type")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les types de contacts pour les clubs", description = "Renvoie la liste des types de " +
"contacts possibles pour les clubs")
public Uni<HashMap<String, String>> getConcatType() {
return Uni.createFrom().item(Contact.toSite());
}
@GET
@Path("/find")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Recherche des clubs en fonction de critères de recherche", description = "Recherche des clubs " +
"en fonction de critères de recherche tels que le nom, le pays, etc.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des clubs correspondant aux critères de recherche"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<PageResult<SimpleClubList>> getFindAdmin(
@Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit,
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Pays à filter") @QueryParam("country") String country) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return clubService.search(limit, page - 1, search, country);
}
@GET
@Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les détails d'un club en fonction de son identifiant", description = "Renvoie les " +
"détails d'un club en fonction de son identifiant, y compris les informations sur les membres et les affiliations")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les détails du club"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<SimpleClub> getById(
@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
return clubService.getById(id).onItem().invoke(checkPerm).map(SimpleClub::fromModel).invoke(m -> {
m.setContactMap(Contact.toSite());
});
}
@PUT
@Path("{id}")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Met à jour les informations d'un club en fonction de son identifiant", description = "Met à " +
"jour les informations d'un club en fonction de son identifiant, y compris les informations sur les membres" +
" et les affiliations")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le club a été mis à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> setAdminClub(
@Parameter(description = "Identifiant de club") @PathParam("id") long id, FullClubForm input) {
return clubService.update(id, input)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
})).chain(() -> {
if (input.getLogo().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getLogo(), media, "ppClub"
)).invoke(Unchecked.consumer(out -> {
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
})); // TODO log
else
return Uni.createFrom().nullItem();
}).chain(() -> {
if (input.getStatus().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getStatus(), media, "clubStatus"
)).invoke(Unchecked.consumer(out -> {
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
})); // TODO log
else
return Uni.createFrom().nullItem();
});
}
@PUT
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Ajoute un nouveau club", description = "Ajoute un nouveau club avec les informations fournies" +
" dans le formulaire")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le club a été ajouté avec succès"),
@APIResponse(responseCode = "400", description = "Les données envoyées sont invalides"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Long> addAdminClub(FullClubForm input) {
return clubService.add(input)
.invoke(Unchecked.consumer(id -> {
if (id == null) throw new InternalError("Fail to create club data");
})).call(id -> {
if (input.getLogo().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getLogo(), media, "ppClub"
)); // TODO log
else
return Uni.createFrom().nullItem();
}).call(id -> {
if (input.getStatus().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getStatus(), media, "clubStatus"
)); // TODO log
else
return Uni.createFrom().nullItem();
});
}
@DELETE
@Path("{id}")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime un club en fonction de son identifiant", description = "Supprime un club en fonction" +
" de son identifiant, ainsi que toutes les informations associées")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Le club a été supprimé avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> deleteAdminClub(
@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
return clubService.delete(id);
}
@GET
@Path("/{id}/affiliation")
@RolesAllowed({"federation_admin"})
@Operation(summary = "Renvoie l'attestation d'affiliation du club en fonction de son identifiant", description =
"Renvoie l'attestation d'affiliation du club en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "L'attestation d'affiliation"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas ou n'a pas d'affiliation active"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getAffiliation(@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
return pdfService.getAffiliationPdf(id);
}
@GET
@Path("/me")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les informations du club de l'utilisateur connecté", description = "Renvoie les " +
"informations du club de l'utilisateur connecté")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les informations du club de l'utilisateur connecté"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<SimpleClub> getOfUser() {
return clubService.getOfUser(securityCtx).map(SimpleClub::fromModel)
.invoke(m -> m.setContactMap(Contact.toSite()));
}
@PUT
@Path("/me")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Met à jour les informations du club de l'utilisateur connecté", description = "Met à jour les" +
" informations du club de l'utilisateur connecté")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les informations du club de l'utilisateur connecté ont été mises à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> setClubOfUser(PartClubForm form) {
return clubService.updateOfUser(securityCtx, form);
}
@GET
@Path("/me/affiliation")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Operation(summary = "Renvoie l'attestation d'affiliation du club de l'utilisateur connecté", description =
"Renvoie l'attestation d'affiliation du club de l'utilisateur connecté")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "L'attestation d'affiliation"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'a pas d'affiliation active"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getMeAffiliation() {
return pdfService.getAffiliationPdf(securityCtx.getSubject());
}
@GET
@Path("/members")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra", "club_tresorier"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Revoie tout les membres de votre club")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "List des membres"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "L'utilisateur n'est pas membre d'un club"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<VerySimpleMembre>> getMembers() {
return clubService.getMembers(securityCtx);
}
@GET
@Path("/renew/{id}")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<RenewAffData> getRenew(@PathParam("id") long id, @QueryParam("m1") long m1_id,
@QueryParam("m2") long m2_id, @QueryParam("m3") long m3_id) {
return Uni.createFrom().item(id).invoke(checkPerm2)
.chain(__ -> clubService.getRenewData(id, List.of(m1_id, m2_id, m3_id)));
}
@GET
@Path("/desk/{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie la liste des membres du bureau du club", description = "Renvoie la liste des membres " +
"du bureau du club spécifié")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des membres du bureau du club"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<DeskMember>> getClubDesk(
@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
return clubService.getClubDesk(checkPerm, id);
}
@GET
@Path("{clubId}/logo")
@Operation(summary = "Renvoie le logo du club", description = "Renvoie le logo du club spécifié")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le logo du club"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getLogo(
@Parameter(description = "Identifiant long (clubId) de club") @PathParam("clubId") String clubId) {
return clubService.getByClubId(clubId).chain(Unchecked.function(clubModel -> {
try {
return Utils.getMediaFile((clubModel != null) ? clubModel.getId() : -1, media, "ppClub",
Uni.createFrom().nullItem());
} catch (URISyntaxException e) {
throw new InternalError();
}
}));
}
@GET
@Path("{id}/status")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Operation(summary = "Renvoie le statut du club", description = "Renvoie le statut du club spécifié")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le statut du club"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le club n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getStatus(
@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
return clubService.getById(id).onItem().invoke(checkPerm).chain(Unchecked.function(clubModel -> {
try {
return Utils.getMediaFile(clubModel.getId(), media, "clubStatus",
"statue-" + clubModel.getName(), Uni.createFrom().nullItem());
} catch (URISyntaxException e) {
throw new InternalError();
}
}));
}
@GET
@Path("get_map_data")
public Uni<List<ClubMapData>> getMapData() {
return clubService.getMapData();
}
} }

View File

@ -0,0 +1,249 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.MembreService;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.from.ClubMemberForm;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import fr.titionfire.ffsaf.utils.PageResult;
import fr.titionfire.ffsaf.utils.Pair;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
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 jodd.net.MimeTypes;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Consumer;
@Authenticated
@Path("api/member")
public class CombEndpoints {
@Inject
MembreService membreService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
@IdToken
JsonWebToken idToken;
@Inject
SecurityIdentity securityIdentity;
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new ForbiddenException();
});
@GET
@Path("/find/admin")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON)
public Uni<PageResult<SimpleMembre>> getFindAdmin(@QueryParam("limit") Integer limit,
@QueryParam("page") Integer page,
@QueryParam("search") String search,
@QueryParam("club") String club) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.searchAdmin(limit, page - 1, search, club);
}
@GET
@Path("/find/club")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
public Uni<PageResult<SimpleMembre>> getFindClub(@QueryParam("limit") Integer limit,
@QueryParam("page") Integer page,
@QueryParam("search") String search) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.search(limit, page - 1, search, idToken.getSubject());
}
@GET
@Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
public Uni<SimpleMembre> getById(@PathParam("id") long id) {
return membreService.getById(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel);
}
@PUT
@Path("{id}")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<String> setAdminMembre(@PathParam("id") long id, FullMemberForm input) {
return membreService.update(id, input)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
})).chain(() -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
)).invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out);
}));
else
return Uni.createFrom().nullItem();
});
}
@POST
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<Long> addAdminMembre(FullMemberForm input) {
return membreService.add(input)
.invoke(Unchecked.consumer(id -> {
if (id == null) throw new InternalError("Fail to creat member data");
})).call(id -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
));
else
return Uni.createFrom().nullItem();
});
}
@DELETE
@Path("{id}")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.TEXT_PLAIN)
public Uni<String> deleteAdminMembre(@PathParam("id") long id) {
return membreService.delete(id);
}
@PUT
@Path("club/{id}")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<String> setMembre(@PathParam("id") long id, ClubMemberForm input) {
return membreService.update(id, input, idToken, securityIdentity)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
})).chain(() -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
)).invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out);
}));
else
return Uni.createFrom().nullItem();
});
}
@POST
@Path("club")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<Long> addMembre(FullMemberForm input) {
return membreService.add(input, idToken.getSubject())
.invoke(Unchecked.consumer(id -> {
if (id == null) throw new InternalError("Fail to creat member data");
})).call(id -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
));
else
return Uni.createFrom().nullItem();
});
}
@DELETE
@Path("club/{id}")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.TEXT_PLAIN)
public Uni<String> deleteMembre(@PathParam("id") long id) {
return membreService.delete(id, idToken);
}
private Future<String> replacePhoto(long id, byte[] input) {
return CompletableFuture.supplyAsync(() -> {
try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input))) {
String mimeType = URLConnection.guessContentTypeFromStream(is);
String[] detectedExtensions = MimeTypes.findExtensionsByMimeTypes(mimeType, false);
if (detectedExtensions.length == 0)
throw new IOException("Fail to detect file extension for MIME type " + mimeType);
FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id));
File[] files = new File(media, "ppMembre").listFiles(filter);
if (files != null) {
for (File file : files) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
String extension = "." + detectedExtensions[0];
Files.write(new File(media, "ppMembre/" + id + extension).toPath(), input);
return "OK";
} catch (IOException e) {
return e.getMessage();
}
});
}
@GET
@Path("{id}/photo")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
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 membreService.getById(id).onItem().invoke(checkPerm).chain(__ -> 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();
}));
}
}

View File

@ -1,50 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.CompetitionService;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@Path("api/competition/admin")
public class CompetitionAdminEndpoints {
@Inject
CompetitionService service;
@Inject
SecurityCtx securityCtx;
@GET
@Path("{id}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<CompetitionData> getByIdAdmin(@PathParam("id") Long id) {
return service.getByIdAdmin(securityCtx, id);
}
@GET
@Path("all")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CompetitionData>> getAllAdmin() {
return service.getAllAdmin(securityCtx);
}
@GET
@Path("all/{system}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CompetitionData>> getAllSystemAdmin(@PathParam("system") CompetitionSystem system) {
return service.getAllSystemAdmin(securityCtx, system);
}
}

View File

@ -1,105 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.CompetitionService;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import java.util.List;
@Path("api/competition/")
public class CompetitionEndpoints {
@Inject
CompetitionService service;
@Inject
SecurityCtx securityCtx;
@GET
@Path("{id}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<CompetitionData> getById(@PathParam("id") Long id, @QueryParam("light") boolean light) {
if (light)
return service.getById(securityCtx, id);
else
return service.getByIdAdmin(securityCtx, id);
}
@GET
@Path("{id}/register/{source}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<SimpleRegisterComb>> getRegister(@PathParam("id") Long id, @PathParam("source") String source) {
return service.getRegister(securityCtx, id, source);
}
@POST
@Path("{id}/register/{source}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<SimpleRegisterComb> addRegisterComb(@PathParam("id") Long id, @PathParam("source") String source,
RegisterRequestData data) {
return service.addRegisterComb(securityCtx, id, data, source);
}
@DELETE
@Path("{id}/register/{comb_id}/{source}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<Void> removeRegisterComb(@PathParam("id") Long id, @PathParam("comb_id") Long combId,
@PathParam("source") String source, @QueryParam("ban") boolean ban) {
return service.removeRegisterComb(securityCtx, id, combId, source, ban);
}
@GET
@Path("{id}/safcaData")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<SimpleCompetData> getSafcaData(@PathParam("id") Long id) {
return service.getSafcaData(securityCtx, id);
}
@GET
@Path("all")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CompetitionData>> getAll() {
return service.getAll(securityCtx);
}
@POST
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<CompetitionData> addOrUpdate(CompetitionData data) {
return service.addOrUpdate(securityCtx, data);
}
@POST
@Path("/safcaData")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> setSafcaData(SimpleCompetData data) {
return service.setSafcaData(securityCtx, data);
}
@DELETE
@Path("{id}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> delete(@PathParam("id") Long id) {
return service.delete(securityCtx, id);
}
}

View File

@ -1,28 +1,21 @@
package fr.titionfire.ffsaf.rest; package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.KeycloakService; import fr.titionfire.ffsaf.domain.service.KeycloakService;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.MemberPermForm; import fr.titionfire.ffsaf.rest.from.MemberPermForm;
import fr.titionfire.ffsaf.utils.GroupeUtils;
import fr.titionfire.ffsaf.utils.Pair; import fr.titionfire.ffsaf.utils.Pair;
import fr.titionfire.ffsaf.utils.SecurityCtx; import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Vertx; import io.vertx.mutiny.core.Vertx;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.GET; import jakarta.ws.rs.*;
import jakarta.ws.rs.PUT; import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Tag(name = "Compte", description = "Gestion des comptes utilisateurs")
@Path("api/compte") @Path("api/compte")
public class CompteEndpoints { public class CompteEndpoints {
@ -30,7 +23,10 @@ public class CompteEndpoints {
KeycloakService service; KeycloakService service;
@Inject @Inject
SecurityCtx securityCtx; JsonWebToken accessToken;
@Inject
SecurityIdentity securityIdentity;
@Inject @Inject
Vertx vertx; Vertx vertx;
@ -38,20 +34,11 @@ public class CompteEndpoints {
@GET @GET
@Path("{id}") @Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Operation(summary = "Renvoie les informations d'un compte utilisateur", description = "Renvoie les informations d'un" +
" compte utilisateur en fonction de son identifiant long (UUID)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les informations du compte utilisateur"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le compte utilisateur n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<KeycloakService.UserCompteState> getCompte(@PathParam("id") String id) { public Uni<KeycloakService.UserCompteState> getCompte(@PathParam("id") String id) {
return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> { return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> {
if (!securityCtx.roleHas("federation_admin") && pair.getKey().groups().stream() if (!securityIdentity.getRoles().contains("federation_admin") && pair.getKey().groups().stream().map(GroupRepresentation::getPath)
.map(GroupRepresentation::getPath) .noneMatch(s -> s.startsWith("/club/") && GroupeUtils.contains(s, accessToken)))
.noneMatch(s -> s.startsWith("/club/") && securityCtx.contains(s))) throw new ForbiddenException();
throw new DForbiddenException();
return pair; return pair;
})).map(Pair::getValue); })).map(Pair::getValue);
} }
@ -59,14 +46,6 @@ public class CompteEndpoints {
@PUT @PUT
@Path("{id}/init") @Path("{id}/init")
@RolesAllowed("federation_admin") @RolesAllowed("federation_admin")
@Operation(summary = "Initialise un compte utilisateur", description = "Initialise un compte utilisateur en fonction" +
" de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Le compte utilisateur a été initialisé avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> initCompte(@PathParam("id") long id) { public Uni<?> initCompte(@PathParam("id") long id) {
return service.initCompte(id); return service.initCompte(id);
} }
@ -74,7 +53,6 @@ public class CompteEndpoints {
@PUT @PUT
@Path("{id}/setUUID/{nid}") @Path("{id}/setUUID/{nid}")
@RolesAllowed("federation_admin") @RolesAllowed("federation_admin")
@Operation(hidden = true)
public Uni<?> initCompte(@PathParam("id") long id, @PathParam("nid") String nid) { public Uni<?> initCompte(@PathParam("id") long id, @PathParam("nid") String nid) {
return service.setId(id, nid); return service.setId(id, nid);
} }
@ -82,29 +60,13 @@ public class CompteEndpoints {
@GET @GET
@Path("{id}/roles") @Path("{id}/roles")
@RolesAllowed("federation_admin") @RolesAllowed("federation_admin")
@Operation(summary = "Renvoie les rôles d'un compte utilisateur", description = "Renvoie les rôles d'un compte" + public Uni<?> getRole(@PathParam("id") String id) {
" utilisateur en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les rôles du compte utilisateur"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le compte utilisateur n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<String>> getRole(@PathParam("id") String id) {
return service.fetchRole(id); return service.fetchRole(id);
} }
@PUT @PUT
@Path("{id}/roles") @Path("{id}/roles")
@RolesAllowed("federation_admin") @RolesAllowed("federation_admin")
@Operation(summary = "Met à jour les rôles d'un compte utilisateur", description = "Met à jour les rôles d'un compte" +
" utilisateur en fonction de son identifiant et des rôles fournis dans le formulaire")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Les rôles du compte utilisateur ont été mis à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le compte utilisateur n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> updateRole(@PathParam("id") String id, MemberPermForm form) { public Uni<?> updateRole(@PathParam("id") String id, MemberPermForm form) {
List<String> toAdd = new ArrayList<>(); List<String> toAdd = new ArrayList<>();
List<String> toRemove = new ArrayList<>(); List<String> toRemove = new ArrayList<>();
@ -115,8 +77,8 @@ public class CompteEndpoints {
else toRemove.add("safca_super_admin"); else toRemove.add("safca_super_admin");
if (form.isSafca_user()) toAdd.add("safca_user"); if (form.isSafca_user()) toAdd.add("safca_user");
else toRemove.add("safca_user"); else toRemove.add("safca_user");
if (form.isCreate_compet()) toAdd.add("create_compet"); if (form.isSafca_create_compet()) toAdd.add("safca_create_compet");
else toRemove.add("create_compet"); else toRemove.add("safca_create_compet");
return service.updateRole(id, toAdd, toRemove); return service.updateRole(id, toAdd, toRemove);
} }

View File

@ -1,34 +0,0 @@
package fr.titionfire.ffsaf.rest;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import java.util.HashMap;
import java.util.Locale;
@Path("api/countries")
public class CountriesEndpoints {
@GET
@Path("/{lang}/{code}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<HashMap<String, String>> getCountries(@PathParam("lang") String lang, @PathParam("code") String code) {
Locale locale = new Locale(lang, code);
return Uni.createFrom().item(new HashMap<String, String>())
.invoke(map -> {
String[] locales = Locale.getISOCountries();
for (String countryCode : locales) {
if (countryCode.equals("AN"))
continue;
Locale obj = new Locale("", countryCode);
map.put(countryCode, obj.getDisplayName(locale));
}
});
}
}

View File

@ -3,19 +3,17 @@ package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.LicenceService; import fr.titionfire.ffsaf.domain.service.LicenceService;
import fr.titionfire.ffsaf.rest.data.SimpleLicence; import fr.titionfire.ffsaf.rest.data.SimpleLicence;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.LicenceForm; import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.SecurityCtx; import fr.titionfire.ffsaf.utils.GroupeUtils;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked; import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.ws.rs.*; import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -27,73 +25,39 @@ public class LicenceEndpoints {
LicenceService licenceService; LicenceService licenceService;
@Inject @Inject
SecurityCtx securityCtx; @IdToken
JsonWebToken idToken;
@Inject
SecurityIdentity securityIdentity;
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> { Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(membreModel.getClub().getId())) if (!securityIdentity.getRoles().contains("federation_admin") && !GroupeUtils.isInClubGroup(membreModel.getClub().getId(), idToken))
throw new DForbiddenException(); throw new ForbiddenException();
}); });
@GET @GET
@Path("{id}") @Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les licences d'un membre", description = "Renvoie les licences d'un membre en fonction " +
"de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des licences du membre"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleLicence>> getLicence(@PathParam("id") long id) { public Uni<List<SimpleLicence>> getLicence(@PathParam("id") long id) {
return licenceService.getLicence(id, checkPerm) return licenceService.getLicence(id, checkPerm).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
.map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
} }
@GET @GET
@Path("current/admin") @Path("current/admin")
@RolesAllowed({"federation_admin"}) @RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les licences de la saison en cours (pour les administrateurs)", description = "Renvoie" +
" les licences de la saison en cours (pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des licences de la saison en cours"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleLicence>> getCurrentSaisonLicenceAdmin() { public Uni<List<SimpleLicence>> getCurrentSaisonLicenceAdmin() {
return licenceService.getCurrentSaisonLicence(null) return licenceService.getCurrentSaisonLicence(null).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
.map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
} }
@GET @GET
@Path("current/club") @Path("current/club")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les licences de la saison en cours (pour les clubs)", description = "Renvoie les " +
"licences de la saison en cours (pour les clubs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des licences de la saison en cours"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleLicence>> getCurrentSaisonLicenceClub() { public Uni<List<SimpleLicence>> getCurrentSaisonLicenceClub() {
return licenceService.getCurrentSaisonLicence(securityCtx) return licenceService.getCurrentSaisonLicence(idToken).map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
.map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
}
@POST
@Path("pay")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Paiement des licence", description = "Retourne le lien de paiement pour les licence des membre fournie")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Commande avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> payLicences(@Parameter(description = "Id des membres") List<Long> ids) {
return licenceService.payLicences(ids, checkPerm, securityCtx);
} }
@POST @POST
@ -101,45 +65,14 @@ public class LicenceEndpoints {
@RolesAllowed("federation_admin") @RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA) @Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Créer une licence", description = "Créer unr licence en fonction de son identifiant et des " +
"informations fournies dans le formulaire (pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La licence a été mise à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "La licence n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<SimpleLicence> setLicence(@PathParam("id") long id, LicenceForm form) { public Uni<SimpleLicence> setLicence(@PathParam("id") long id, LicenceForm form) {
return licenceService.setLicence(id, form).map(SimpleLicence::fromModel); return licenceService.setLicence(id, form).map(SimpleLicence::fromModel);
} }
@POST
@Path("validate")
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Validation licence", description = "Valide en masse les licence de l'année en cours (pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les licences ont été mise à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> valideLicences(@Parameter(description = "Id des membres a valider") List<Long> ids) {
return licenceService.valideLicences(ids);
}
@DELETE @DELETE
@Path("{id}") @Path("{id}")
@RolesAllowed("federation_admin") @RolesAllowed("federation_admin")
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime une licence", description = "Supprime une licence en fonction de son identifiant " +
"(pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "La licence a été supprimée avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "La licence n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> deleteLicence(@PathParam("id") long id) { public Uni<?> deleteLicence(@PathParam("id") long id) {
return licenceService.deleteLicence(id); return licenceService.deleteLicence(id);
} }
@ -149,14 +82,6 @@ public class LicenceEndpoints {
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA) @Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Demande une nouvelle licence", description = "Demande une nouvelle licence en fonction de" +
" l'identifiant du membre et des informations fournies dans le formulaire (pour les clubs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La demande de licence a été envoyée avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<SimpleLicence> askLicence(@PathParam("id") long id, LicenceForm form) { public Uni<SimpleLicence> askLicence(@PathParam("id") long id, LicenceForm form) {
return licenceService.askLicence(id, form, checkPerm).map(SimpleLicence::fromModel); return licenceService.askLicence(id, form, checkPerm).map(SimpleLicence::fromModel);
} }
@ -165,25 +90,7 @@ public class LicenceEndpoints {
@Path("club/{id}") @Path("club/{id}")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"}) @RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.TEXT_PLAIN) @Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime une demande de licence", description = "Supprime une demande de licence en fonction " +
"de son identifiant (pour les clubs)")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "La demande de licence a été supprimée avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "La demande de licence n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> deleteAskLicence(@PathParam("id") long id) { public Uni<?> deleteAskLicence(@PathParam("id") long id) {
return licenceService.deleteAskLicence(id, checkPerm); return licenceService.deleteAskLicence(id, checkPerm);
} }
// TODO remove after importe all data
@GET
@Path("import")
@RolesAllowed({"federation_admin"})
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> setImport(@QueryParam("licence") int licence, @QueryParam("saison") int saison,
@QueryParam("valid") int valid) {
return licenceService.setImport(licence, saison, valid != 0);
}
} }

View File

@ -1,63 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.MatchService;
import fr.titionfire.ffsaf.rest.data.MatchData;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@Authenticated
@Path("api/match/{system}/admin")
public class MatchAdminEndpoints {
@PathParam("system")
private CompetitionSystem system;
@Inject
MatchService service;
@Inject
SecurityCtx securityCtx;
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<MatchData> getByIdAdmin(@PathParam("id") Long id) {
return service.getByIdAdmin(securityCtx, system, id);
}
@GET
@Path("getAllByPoule/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<MatchData>> getAllByPouleAdmin(@PathParam("id") Long id) {
return service.getAllByPouleAdmin(securityCtx, system, id);
}
@POST
@Produces(MediaType.APPLICATION_JSON)
public Uni<MatchData> addOrUpdate(MatchData data) {
return service.addOrUpdate(securityCtx, system, data);
}
@POST
@Path("score/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> updateScore(@PathParam("id") Long id, List<ScoreEmbeddable> scores) {
return service.updateScore(securityCtx, system, id, scores);
}
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> delete(@PathParam("id") Long id) {
return service.delete(securityCtx, system, id);
}
}

View File

@ -1,148 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.MembreService;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.PageResult;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
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.MediaType;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
import java.util.function.Consumer;
@Tag(name = "Membre admin", description = "Gestion des membres (pour les administrateurs)")
@Path("api/member")
@RolesAllowed({"federation_admin"})
public class MembreAdminEndpoints {
@Inject
MembreService membreService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
SecurityCtx securityCtx;
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(membreModel.getClub().getId()))
throw new DForbiddenException();
});
@GET
@Path("/find/admin")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Recherche des membres par critères ", description = "Recherche des membres en fonction de " +
"critères tels que le nom, le prénom, le club, etc. ")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des membres correspondant aux critères de recherche"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<PageResult<SimpleMembre>> getFindAdmin(
@Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit,
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Club à filter") @QueryParam("club") String club,
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
@Parameter(description = "État de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "État du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
@Parameter(description = "Ordre") @QueryParam("order") String order) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment, order, categorie);
}
@GET
@Path("/find/similar")
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<List<SimpleMembre>> getSimilar(@QueryParam("fname") String fname, @QueryParam("lname") String lname) {
return membreService.getSimilar(fname, lname);
}
@PUT
@Path("{id}")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Met à jour les informations d'un membre en fonction de son identifiant", description = "Met à " +
"jour les informations d'un membre en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le membre a été mis à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> setAdminMembre(
@Parameter(description = "Identifiant de membre") @PathParam("id") long id, FullMemberForm input) {
return membreService.update(id, input)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
})).chain(() -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
)).invoke(Unchecked.consumer(out -> {
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
}));
else
return Uni.createFrom().nullItem();
});
}
@POST
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Ajoute un nouveau membre", description = "Ajoute un nouveau membre avec les informations " +
"fournies dans le formulaire")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le membre a été ajouté avec succès"),
@APIResponse(responseCode = "400", description = "Les données envoyées sont invalides"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Long> addAdminMembre(FullMemberForm input) {
return membreService.add(input)
.invoke(Unchecked.consumer(id -> {
if (id == null) throw new InternalError("Fail to creat member data");
})).call(id -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
));
else
return Uni.createFrom().nullItem();
});
}
@DELETE
@Path("{id}")
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime un membre en fonction de son identifiant", description = "Supprime un membre en " +
"fonction de son identifiant, ainsi que toutes les informations associées")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Le membre a été supprimé avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> deleteAdminMembre(
@Parameter(description = "Identifiant de membre") @PathParam("id") long id) {
return membreService.delete(id);
}
}

View File

@ -1,160 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.MembreService;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.PageResult;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
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.MediaType;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.util.List;
@Tag(name = "Membre club", description = "Gestion des membres (pour les clubs)")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Path("api/member")
public class MembreClubEndpoints {
@Inject
MembreService membreService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
SecurityCtx securityCtx;
@GET
@Path("/find/club")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Recherche des membres par critères", description = "Recherche des membres en " +
"fonction de critères tels que le nom, le prénom, etc.")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des membres correspondant aux critères de recherche"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<PageResult<SimpleMembre>> getFindClub(
@Parameter(description = "Nombre max de résulta (max 50)") @QueryParam("limit") Integer limit,
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
@Parameter(description = "Ordre") @QueryParam("order") String order) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.search(limit, page - 1, search, licenceRequest, payment, order, categorie, securityCtx.getSubject());
}
@GET
@Path("club/export")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Exporte les membres du club", description = "Exporte les membres du club")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les membres du club ont été exportés avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleMembreInOutData>> exportMembre() {
return membreService.getAllExport(securityCtx.getSubject());
}
@PUT
@Path("club/import")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Importer les membres du club", description = "Importer tout ou en partie les membres du club")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les membres du club ont été importés avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> importMembre(List<SimpleMembreInOutData> dataIn) {
System.out.println("importMembre");
return membreService.allImporte(securityCtx.getSubject(), dataIn);
}
@PUT
@Path("club/{id}")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Met à jour les informations d'un membre en fonction de son identifiant",
description = "Met à jour les informations d'un membre en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le membre a été mis à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> setMembre(
@Parameter(description = "Identifiant de membre") @PathParam("id") long id, FullMemberForm input) {
return membreService.update(id, input, securityCtx)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
})).chain(() -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
)).invoke(Unchecked.consumer(out -> {
if (!out.equals("OK"))
throw new DInternalError("Impossible de reconnaitre le fichier: " + out);
}));
else
return Uni.createFrom().nullItem();
});
}
@POST
@Path("club")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Ajoute un nouveau membre", description = "Ajoute un nouveau membre avec les informations " +
"fournies dans le formulaire")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le membre a été ajouté avec succès"),
@APIResponse(responseCode = "400", description = "Les données envoyées sont invalides"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Long> addMembre(FullMemberForm input) {
return membreService.add(input, securityCtx.getSubject())
.invoke(Unchecked.consumer(id -> {
if (id == null) throw new InternalError("Fail to creat member data");
})).call(id -> {
if (input.getPhoto_data().length > 0)
return Uni.createFrom().future(Utils.replacePhoto(id, input.getPhoto_data(), media, "ppMembre"
));
else
return Uni.createFrom().nullItem();
});
}
@DELETE
@Path("club/{id}")
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime un membre en fonction de son identifiant", description = "Supprime " +
"un membre en fonction de son identifiant, ainsi que toutes les informations associées")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Le membre a été supprimé avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<String> deleteMembre(
@Parameter(description = "Identifiant de membre") @PathParam("id") long id) {
return membreService.delete(id, securityCtx);
}
}

View File

@ -1,160 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.MembreService;
import fr.titionfire.ffsaf.domain.service.PDFService;
import fr.titionfire.ffsaf.rest.data.MeData;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.security.Authenticated;
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.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.function.Consumer;
@Tag(name = "Membre", description = "Gestion des membres")
@Authenticated
@Path("api/member")
public class MembreEndpoints {
@Inject
MembreService membreService;
@Inject
PDFService pdfService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
SecurityCtx securityCtx;
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(membreModel.getClub().getId()))
throw new DForbiddenException();
});
@GET
@Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les détails d'un membre en fonction de son identifiant", description = "Renvoie les " +
"détails d'un membre en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les détails du membre"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<SimpleMembre> getById(
@Parameter(description = "Identifiant de membre") @PathParam("id") long id) {
return membreService.getById(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel);
}
@GET
@Path("/find/licence")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Operation(summary = "Renvoie les détails d'un membre en fonction de son numéro de licence", description = "Renvoie " +
"les détails d'un membre en fonction de son numéro de licence")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les détails du membre"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
@Produces(MediaType.APPLICATION_JSON)
public Uni<SimpleMembre> getByLicence(@QueryParam("id") long id) {
return membreService.getByLicence(id).onItem().invoke(checkPerm).map(SimpleMembre::fromModel);
}
@GET
@Path("me")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les informations du membre connecté", description = "Renvoie les informations du " +
"membre connecté, y compris le club et les licences")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Les informations du membre connecté"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<MeData> getMe() {
return membreService.getMembre(securityCtx.getSubject());
}
@GET
@Path("me/licence")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie l'attestation d'adhesion du membre connecté", description = "Renvoie l'attestation d'adhesion du " +
"membre connecté")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "L'attestation d'adhesion"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'a pas de licence active"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getMeLicence() {
return pdfService.getLicencePdf(securityCtx.getSubject());
}
@GET
@Path("me/photo")
@Authenticated
@Operation(summary = "Renvoie la photo du membre connecté", description = "Renvoie la photo du membre connecté")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La photo"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getMePhoto() {
return membreService.getByAccountId(securityCtx.getSubject())
.chain(Unchecked.function(
m -> Utils.getMediaFile(m.getId(), media, "ppMembre", Uni.createFrom().nullItem())));
}
@GET
@Path("{id}/photo")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Operation(summary = "Renvoie la photo d'un membre", description = "Renvoie la photo d'un membre en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La photo du membre"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas ou n'a pas de photo"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getPhoto(@PathParam("id") long id) throws URISyntaxException {
return Utils.getMediaFile(id, media, "ppMembre", membreService.getById(id).onItem()
.call(m -> Objects.equals(m.getUserId(), securityCtx.getSubject()) ?
Uni.createFrom().nullItem() : Uni.createFrom().item(m).invoke(checkPerm)));
}
@GET
@Path("{id}/licence")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
@Operation(summary = "Renvoie le pdf de la licence d'un membre", description = "Renvoie le pdf de la licence d'un membre en fonction de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Le pdf de la licence"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas ou n'a pas de licence active"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<Response> getLicencePDF(@PathParam("id") long id) {
return pdfService.getLicencePdf(membreService.getByIdWithLicence(id).onItem().invoke(checkPerm));
}
}

View File

@ -1,25 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.StatsService;
import fr.titionfire.ffsaf.rest.data.LicenceStats;
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;
@Path("api/stats")
public class StatsEndpoints {
@Inject
StatsService service;
@GET
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
public Uni<LicenceStats> getStats() {
return service.getStats();
}
}

View File

@ -1,53 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.WebhookService;
import fr.titionfire.ffsaf.rest.client.dto.HelloassoNotification;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.jboss.logging.Logger;
@Path("api/webhook")
public class WebhookEndpoints {
private static final Logger LOGGER = Logger.getLogger(WebhookEndpoints.class);
@Inject
WebhookService webhookService;
@Inject
RoutingContext context;
@ConfigProperty(name = "helloasso.webhook.ip-source")
String helloassoIp;
@ConfigProperty(name = "quarkus.http.proxy.proxy-address-forwarding")
boolean proxyForwarding;
@POST
@Path("ha")
@Operation(hidden = true)
@Consumes(MediaType.APPLICATION_JSON)
public Uni<Response> helloAsso(HelloassoNotification notification) {
String ip;
if (proxyForwarding) {
ip = context.request().getHeader("X-Forwarded-For");
if (ip == null)
ip = context.request().authority().host();
} else {
ip = context.request().authority().host();
}
if (!helloassoIp.equals(ip)) {
LOGGER.infof("helloAsso webhook reject : bas ip (%s)", ip);
return Uni.createFrom().item(Response.status(Response.Status.FORBIDDEN).build());
}
return webhookService.helloAssoNotification(notification);
}
}

View File

@ -1,32 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.client.dto.TokenResponse;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient(configKey = "helloasso-auth")
public interface HelloAssoAuthClient {
@POST
@Path("/token")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
Uni<TokenResponse> getToken(
@FormParam("grant_type") String grantType,
@FormParam("client_id") String clientId,
@FormParam("client_secret") String clientSecret
);
@POST
@Path("/token")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
Uni<TokenResponse> refreshToken(
@FormParam("grant_type") String grantType,
@FormParam("client_id") String clientId,
@FormParam("refresh_token") String refreshToken
);
}

View File

@ -1,24 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.domain.service.HelloAssoTokenService;
import io.quarkus.rest.client.reactive.ReactiveClientHeadersFactory;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MultivaluedMap;
import org.jboss.resteasy.reactive.common.util.MultivaluedTreeMap;
@ApplicationScoped
public class HelloAssoHeadersFactory extends ReactiveClientHeadersFactory {
@Inject
HelloAssoTokenService helloAssoTokenService;
@Override
public Uni<MultivaluedMap<String, String>> getHeaders(MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders) {
MultivaluedMap<String, String> map = new MultivaluedTreeMap<>();
return helloAssoTokenService.getValidAccessToken()
.invoke(token -> map.putSingle("Authorization", "Bearer " + token)).map(__ -> map);
}
}

View File

@ -1,51 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.client.dto.CheckoutIntentsRequest;
import fr.titionfire.ffsaf.rest.client.dto.CheckoutIntentsResponse;
import io.quarkus.rest.client.reactive.ClientExceptionMapper;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
@Path("/")
@RegisterRestClient(configKey = "helloasso-api")
@RegisterClientHeaders(HelloAssoHeadersFactory.class)
public interface HelloAssoService {
@GET
@Path("/users/me/organizations")
@Produces("text/plain")
Uni<String> test();
@POST
@Path("organizations/{organizationSlug}/checkout-intents")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
Uni<CheckoutIntentsResponse> checkout(@PathParam("organizationSlug") String organizationSlug,
CheckoutIntentsRequest data);
@ClientExceptionMapper
static RuntimeException toException(Response response, Method method) {
if (!method.getDeclaringClass().getName().equals("fr.titionfire.ffsaf.rest.client.HelloAssoService"))
return null;
if (method.getName().equals("checkout")) {
if (response.getStatus() == 400) {
if (response.getEntity() instanceof ByteArrayInputStream) {
ByteArrayInputStream error = response.readEntity(ByteArrayInputStream.class);
return new RuntimeException(new String(error.readAllBytes(), StandardCharsets.UTF_8));
}
return new RuntimeException("The remote service responded with HTTP 400");
}
}
return null;
}
}

View File

@ -1,7 +1,6 @@
package fr.titionfire.ffsaf.rest.client; package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot; import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.quarkus.cache.CacheResult;
import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
@ -16,6 +15,5 @@ public interface SirenService {
@GET @GET
@Path("/v3/unites_legales/{SIREN}") @Path("/v3/unites_legales/{SIREN}")
@CacheResult(cacheName = "AssoData_siren")
Uni<UniteLegaleRoot> get_unite(@PathParam("SIREN") String siren); Uni<UniteLegaleRoot> get_unite(@PathParam("SIREN") String siren);
} }

View File

@ -1,48 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import io.quarkus.cache.CacheResult;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
public interface StateIdService {
@GET
@Path("/associations/{rna}")
@CacheResult(cacheName = "AssoData_rna")
Uni<AssoData> get_rna(@PathParam("rna") String rna);
default Uni<AssoData> getAssoDataFromUnit(UniteLegaleRoot u) {
AssoData assoData = new AssoData();
assoData.setSiren(u.getUnite_legale().getSiren());
assoData.setRna(u.getUnite_legale().getIdentifiant_association());
AssoData.Identite identite = new AssoData.Identite();
identite.setNom(u.getUnite_legale().getDenomination());
identite.setSiret_siege(u.getUnite_legale().getEtablissement_siege().getSiret());
assoData.setIdentite(identite);
AssoData.Address address = new AssoData.Address();
StringBuilder voie = new StringBuilder();
if (u.getUnite_legale().getEtablissement_siege().getNumero_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getNumero_voie()).append(' ');
if (u.getUnite_legale().getEtablissement_siege().getType_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getType_voie()).append(' ');
if (u.getUnite_legale().getEtablissement_siege().getLibelle_voie() != null)
voie.append(u.getUnite_legale().getEtablissement_siege().getLibelle_voie()).append(' ');
address.setVoie(voie.toString().trim());
address.setComplement(u.getUnite_legale().getEtablissement_siege().getComplement_adresse());
address.setCode_postal(u.getUnite_legale().getEtablissement_siege().getCode_postal());
address.setCommune(
new AssoData.Commune(u.getUnite_legale().getEtablissement_siege().getLibelle_commune()));
assoData.setCoordonnees(new AssoData.Coordonnee(address));
return Uni.createFrom().item(assoData);
}
}

View File

@ -1,14 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class ApiError {
public String error;
public String error_description;
@Override
public String toString() {
return error + ": " + error_description;
}
}

View File

@ -1,30 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@RegisterForReflection
public class CheckoutIntentsRequest {
public int totalAmount;
public int initialAmount;
public String itemName;
public String backUrl;
public String errorUrl;
public String returnUrl;
public boolean containsDonation;
public Payer payer;
public CheckoutMetadata metadata;
@Data
@AllArgsConstructor
@RegisterForReflection
public static class Payer {
public String firstName;
public String lastName;
public String email;
}
}

View File

@ -1,11 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
@Data
@RegisterForReflection
public class CheckoutIntentsResponse {
public int id;
public String redirectUrl;
}

View File

@ -1,12 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class CheckoutMetadata {
public long checkoutDBId;
}

View File

@ -1,16 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public class HelloassoNotification {
private NotificationData data;
private String eventType;
private CheckoutMetadata metadata;
}

View File

@ -1,65 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public class NotificationData {
private Order order;
private Integer id;
private String formSlug;
private String formType;
private String organizationSlug;
private String checkoutIntentId;
private String oldSlugOrganization; // Pour les changements de nom d'association
private String newSlugOrganization;
private String state; // Pour les formulaires
private List<Item> items;
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class Order {
private Integer id;
private String organizationSlug;
private String formSlug;
private String formType;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class Item {
private String name;
private User user;
private List<CustomField> customFields;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class User {
private String firstName;
private String lastName;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public static class CustomField {
private String name;
private String answer;
}
}

View File

@ -1,30 +0,0 @@
package fr.titionfire.ffsaf.rest.client.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class TokenResponse {
@JsonProperty("access_token")
public String accessToken;
@JsonProperty("refresh_token")
public String refreshToken;
@JsonProperty("token_type")
public String tokenType; // Toujours "bearer"
@JsonProperty("expires_in")
public long expiresIn; // Durée de validité en secondes (1800s = 30min)
// Pour stocker l'heure d'obtention du token
private long timestamp;
public TokenResponse() {
this.timestamp = System.currentTimeMillis() / 1000; // Timestamp en secondes
}
// Vérifie si le token est expiré
public boolean isExpired() {
return (System.currentTimeMillis() / 1000) - timestamp >= expiresIn;
}
}

View File

@ -1,48 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@RegisterForReflection
public class AssoData {
String siren;
String rna;
Identite identite;
Coordonnee coordonnees;
@Data
@RegisterForReflection
public static class Identite {
String nom;
String siret_siege;
}
@Data
@RegisterForReflection
@NoArgsConstructor
@AllArgsConstructor
public static class Coordonnee {
Address adresse_gestion;
}
@Data
@RegisterForReflection
public static class Address {
String voie;
String complement;
String code_postal;
String pays;
Commune commune;
}
@Data
@RegisterForReflection
@NoArgsConstructor
@AllArgsConstructor
public static class Commune {
String nom;
}
}

View File

@ -1,23 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CategoryModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class CategoryData {
private Long id;
private String name;
private Long compet;
private Integer type;
public static CategoryData fromModel(CategoryModel model) {
if (model == null)
return null;
return new CategoryData(model.getSystemId(), model.getName(), model.getCompet().getId(), model.getType());
}
}

View File

@ -1,28 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class CategoryFullData {
private Long id;
private String name;
private Long compet;
private Integer type;
private List<MatchData> matches;
private List<TreeData> trees;
/*public static PouleFullData fromModel(PouleModel pouleModel) {
if (pouleModel == null)
return null;
PouleEntity pouleEntity = PouleEntity.fromModel(pouleModel);
return new PouleFullData(pouleEntity.getId(), pouleEntity.getName(), pouleEntity.getCompet().getId(),
pouleEntity.getType(), pouleModel.getMatchs().stream().map(MatchData::fromModel).toList(),
pouleEntity.getTrees().stream().map(TreeData::fromEntity).toList());
}*/
}

View File

@ -1,27 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.utils.Contact;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
@RegisterForReflection
public class ClubMapData {
public String name;
public String uuid;
public List<Location> training_location = new ArrayList<>();
public String training_day_time;
public Map<Contact, String> contact;
@Data
@RegisterForReflection
public static class Location {
public double lat;
public double lng;
public String addr;
}
}

View File

@ -1,87 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.RegisterMode;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@AllArgsConstructor
@RegisterForReflection
public class CompetitionData {
private Long id;
private String name;
private String description;
private String adresse;
private String uuid;
private Date date;
private Date toDate;
private CompetitionSystem system;
private RegisterMode registerMode;
private Date startRegister;
private Date endRegister;
private boolean publicVisible;
private Long club;
private String clubName;
private String owner;
private List<SimpleRegister> registers;
private boolean canEdit;
private String data1;
private String data2;
private String data3;
private String data4;
public static CompetitionData fromModel(CompetitionModel model) {
if (model == null)
return null;
return new CompetitionData(model.getId(), model.getName(), model.getDescription(), model.getAdresse(),
model.getUuid(), model.getDate(), model.getTodate(), model.getSystem(),
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false,
model.getData1(), model.getData2(), model.getData3(), model.getData4());
}
public static CompetitionData fromModelLight(CompetitionModel model) {
if (model == null)
return null;
CompetitionData out = new CompetitionData(model.getId(), model.getName(), model.getDescription(),
model.getAdresse(), "", model.getDate(), model.getTodate(), null,
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
null, model.getClub().getName(), "", null, false,
"","", "","");
if (model.getRegisterMode() == RegisterMode.HELLOASSO){
out.setData1(model.getData1());
out.setData2(model.getData2());
}
return out;
}
public CompetitionData addInsc(List<RegisterModel> insc) {
this.registers = insc.stream()
.map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight(),
i.getCategorie(), (i.getClub() == null) ? null : i.getClub().getId())).toList();
return this;
}
@Data
@AllArgsConstructor
@RegisterForReflection
public static class SimpleRegister {
long id;
int overCategory;
Integer weight;
Categorie categorie;
Long club;
}
}

View File

@ -1,35 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.MembreModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Data
@NoArgsConstructor
@RegisterForReflection
@Schema(name = "BureauMembre")
public class DeskMember {
@Schema(description = "Identifiant du membre", example = "1")
private Long id;
@Schema(description = "Nom du membre", example = "Doe")
private String lname;
@Schema(description = "Prénom du membre", example = "John")
private String fname;
@Schema(description = "Rôle du membre", example = "Président")
private String role;
public static DeskMember fromModel(MembreModel membreModel) {
if (membreModel == null)
return null;
DeskMember deskMember = new DeskMember();
deskMember.setId(membreModel.getId());
deskMember.setLname(membreModel.getLname());
deskMember.setFname(membreModel.getFname());
deskMember.setRole(membreModel.getRole().toString());
return deskMember;
}
}

Some files were not shown because too many files have changed in this diff Show More