1 Commits

Author SHA1 Message Date
c553ff14da start theme implementation 2024-02-03 16:24:12 +01:00
447 changed files with 929 additions and 14439 deletions

View File

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

View File

@@ -1,96 +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 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

View File

@@ -59,7 +59,6 @@ jobs:
username: ${{ secrets.SSH_USER }}
port: ${{ secrets.SSH_PORT }}
key: ${{ secrets.SSH_KEY }}
command_timeout: 20m
script: |
cd ${{ secrets.TARGET_DIR }}/ffsaf
cp ../vite.env src/main/webapp/.env

3
.gitignore vendored
View File

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

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

70
pom.xml
View File

@@ -1,19 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<?xml version="1.0"?>
<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>
<groupId>fr.titionfire</groupId>
<artifactId>ffsaf-site</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-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>
<surefire-plugin.version>3.2.3</surefire-plugin.version>
<surefire-plugin.version>3.1.2</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -33,27 +34,27 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-qute</artifactId>
<artifactId>quarkus-resteasy-reactive-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
@@ -65,24 +66,17 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.tika</groupId>
<artifactId>quarkus-tika</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-authorization</artifactId>
@@ -90,9 +84,10 @@
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-rest-client</artifactId>
<artifactId>quarkus-keycloak-admin-client-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -105,34 +100,6 @@
<artifactId>jodd-util</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets</artifactId>
</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>
<build>
<plugins>
@@ -201,11 +168,10 @@
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.package.type>native</quarkus.package.type>
<quarkus.native.additional-build-args>
--initialize-at-run-time=com.fasterxml.jackson.databind.ext.DOMDeserializer
-H:+UnlockExperimentalVMOptions
</quarkus.native.additional-build-args>
<quarkus.native.enabled>true</quarkus.native.enabled>
<quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
</properties>
</profile>
</profiles>

View File

@@ -14,17 +14,13 @@
# 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/openjdk-17:1.18
USER 0
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
COPY --chown=1001:root src/main/resources/cacerts /work/cacerts
COPY --chown=1001:root src/main/pdf_gen/target/pdf_gen-*.jar /work/make_pdf.jar
COPY --chown=1001:root ffsaf/target/*-runner /work/application
COPY --chown=1001:root ffsaf/src/main/resources/cacerts /work/cacerts
RUN mkdir /work/media && chown -R 1001:root /work/media
EXPOSE 8080

View File

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

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

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,20 +0,0 @@
package fr.titionfire.ffsaf.data.id;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Data;
import java.io.Serializable;
@Data
public class RegisterId implements Serializable {
@ManyToOne
@JoinColumn(name = "id_competition")
private CompetitionModel competition;
@ManyToOne
@JoinColumn(name = "id_membre")
private MembreModel membre;
}

View File

@@ -1,29 +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;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "affiliation")
public class AffiliationModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "Identifiant de l'affiliation", example = "42")
Long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club", referencedColumnName = "id")
ClubModel club;
@Schema(description = "Saison de l'affiliation", example = "2021")
int saison;
}

View File

@@ -1,48 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.RoleAsso;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "affiliation_request")
public class AffiliationRequestModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
long siret;
String RNA;
String address;
String contact;
String m1_lname;
String m1_fname;
String m1_email;
int m1_lincence;
RoleAsso m1_role;
String m2_lname;
String m2_fname;
String m2_email;
int m2_lincence;
RoleAsso m2_role;
String m3_lname;
String m3_fname;
String m3_email;
int m3_lincence;
RoleAsso m3_role;
int saison;
}

View File

@@ -4,9 +4,7 @@ import fr.titionfire.ffsaf.utils.Contact;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.List;
import java.util.Map;
@Getter
@@ -18,66 +16,37 @@ import java.util.Map;
@Entity
@Table(name = "club")
public class ClubModel implements LoggableModel {
public class ClubModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "Identifiant du club", example = "1")
Long id;
@Schema(description = "Identifiant long du club (UUID)", example = "b94f3167-3f6a-449c-a73b-ec84202bf07e")
String clubId;
@Schema(description = "Nom du club", example = "Association sportive")
String name;
@Schema(description = "Pays du club", example = "FR")
String country;
String shieldURL;
//@Enumerated(EnumType.STRING)
@ElementCollection
@CollectionTable(name = "club_contact_mapping",
joinColumns = {@JoinColumn(name = "club_id", referencedColumnName = "id")})
@MapKeyColumn(name = "contact_type")
@Schema(description = "Les contacts du club", example = "{\"SITE\": \"www.test.com\", \"COURRIEL\": \"test@test.com\"}")
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;
@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;
@Schema(description = "Contact interne du club", example = "john.doe@test.com")
String contact_intern;
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
String address;
@Schema(description = "RNA du club", example = "W123456789")
String RNA;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long SIRET;
String SIRET;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation;
String no_affiliation;
@Schema(description = "Club international", example = "false")
boolean international;
@OneToMany(mappedBy = "club", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@Schema(description = "Liste des affiliations du club (optionnel)")
List<AffiliationModel> affiliations;
@Override
public String getObjectName() {
return this.name;
}
@Override
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Club;
}
}

View File

@@ -1,44 +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.Date;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "compet")
public class CompetitionModel {
@Id
@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;
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
List<RegisterModel> insc;
String owner;
}

View File

@@ -1,38 +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;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "licence")
public class LicenceModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "L'identifiant de la licence.")
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre", referencedColumnName = "id")
@Schema(description = "Le membre de la licence. (optionnel)")
MembreModel membre;
Long club_id;
@Schema(description = "La saison de la licence.", example = "2025")
int saison;
@Schema(description = "Nom du médecin sur certificat médical.", example = "M. Jean")
String certificate;
@Schema(description = "Licence validée", example = "true")
boolean validate;
}

View File

@@ -1,45 +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;
String target_name;
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_poule", referencedColumnName = "id")
PouleModel poule = null;
long poule_ord = 0;
boolean isEnd = true;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
List<ScoreEmbeddable> scores = new ArrayList<>();
char groupe = 'A';
}

View File

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

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 = "poule")
public class PouleModel {
@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_poule", referencedColumnName = "id")
List<MatchModel> matchs;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "id_poule", referencedColumnName = "id")
List<TreeModel> tree;
Integer type;
}

View File

@@ -1,40 +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;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "register")
@IdClass(RegisterId.class)
public class RegisterModel {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_competition")
CompetitionModel competition;
@Id
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "id_membre")
MembreModel membre;
Integer weight;
int overCategory = 0;
Categorie categorie;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "club")
ClubModel club = null;
}

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_poule")
Long poule;
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.AffiliationRequestModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class AffiliationRequestRepository implements PanacheRepositoryBase<AffiliationRequestModel, 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.LicenceModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class LicenceRepository implements PanacheRepositoryBase<LicenceModel, 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,9 +0,0 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.PouleModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PouleRepository implements PanacheRepositoryBase<PouleModel, Long> {
}

View File

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

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,13 +18,14 @@ public class ClubEntity {
private String name;
private String clubId;
private String country;
private String shieldURL;
private Map<Contact, String> contact;
private String training_location;
private String training_day_time;
private String contact_intern;
private String RNA;
private Long SIRET;
private Long no_affiliation;
private String SIRET;
private String no_affiliation;
private boolean international;
public static ClubEntity fromModel (ClubModel model) {
@@ -37,6 +38,7 @@ public class ClubEntity {
.name(model.getName())
.clubId(model.getClubId())
.country(model.getCountry())
.shieldURL(model.getShieldURL())
.contact(model.getContact())
.training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time())
@@ -47,4 +49,10 @@ public class ClubEntity {
.international(model.isInternational())
.build();
}
public ClubModel toModel () {
return new ClubModel(this.id, this.clubId, this.name, this.country, this.shieldURL, this.contact, this.training_location,
this.training_day_time, this.contact_intern, this.RNA, this.SIRET, this.no_affiliation,
this.international);
}
}

View File

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

View File

@@ -1,439 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.data.SimpleAffiliation;
import fr.titionfire.ffsaf.rest.data.SimpleReqAffiliation;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestForm;
import fr.titionfire.ffsaf.rest.from.AffiliationRequestSaveForm;
import fr.titionfire.ffsaf.utils.Genre;
import fr.titionfire.ffsaf.utils.GradeArbitrage;
import fr.titionfire.ffsaf.utils.SequenceType;
import fr.titionfire.ffsaf.utils.Utils;
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.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 java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class AffiliationService {
@Inject
CombRepository combRepository;
@Inject
ClubRepository clubRepository;
@Inject
AffiliationRequestRepository repositoryRequest;
@Inject
AffiliationRepository repository;
@Inject
KeycloakService keycloakService;
@Inject
SequenceRepository sequenceRepository;
@Inject
LicenceRepository licenceRepository;
@Inject
ReactiveMailer reactiveMailer;
@ConfigProperty(name = "upload_dir")
String media;
@ConfigProperty(name = "notif.affRequest.mail")
List<String> mails;
public Uni<List<AffiliationRequestModel>> getAllReq() {
return repositoryRequest.listAll();
}
public Uni<AffiliationRequestModel> pre_save(AffiliationRequestForm form, boolean unique) {
AffiliationRequestModel affModel = form.toModel();
int currentSaison = Utils.getSaison();
return Uni.createFrom().item(affModel)
.invoke(Unchecked.consumer(model -> {
if (model.getSaison() != currentSaison && model.getSaison() != currentSaison + 1) {
throw new DBadRequestException("Saison non valid");
}
}))
.chain(() -> repositoryRequest.count("siret = ?1 and saison = ?2", affModel.getSiret(),
affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
}
}))
.chain(() -> clubRepository.find("SIRET = ?1", affModel.getSiret()).firstResult().chain(club ->
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) {
throw new DBadRequestException("Affiliation déjà existante");
}
}))
.map(o -> affModel)
.call(model -> ((model.getM1_lincence() != -1) ? combRepository.find("licence",
model.getM1_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new DBadRequestException("Licence membre n°1 inconnue");
}
})) : Uni.createFrom().nullItem())
)
.call(model -> ((model.getM2_lincence() != -1) ? combRepository.find("licence",
model.getM2_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new DBadRequestException("Licence membre n°2 inconnue");
}
})) : Uni.createFrom().nullItem())
)
.call(model -> ((model.getM3_lincence() != -1) ? combRepository.find("licence",
model.getM3_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new DBadRequestException("Licence membre n°3 inconnue");
}
})) : Uni.createFrom().nullItem())
);
}
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.setRNA(model.getRNA());
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) {
// 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")))
.onItem()
.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) {
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.map(model -> {
model.setName(form.getName());
model.setSiret(form.getSiret());
model.setRNA(form.getRna());
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")))
.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()) :
keycloakService.setClubGroupMembre(m, club).map(__ -> m.getUserId()))
.call(userId -> keycloakService.setAutoRoleMembre(userId, m.getRole(), m.getGrade_arbitrage())))
.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)))));
}
public Uni<?> accept(AffiliationRequestSaveForm form) {
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.chain(req ->
clubRepository.find("SIRET = ?1", form.getSiret()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
.call(club -> setMembre(form.new Member(1), club, req.getSaison())
.call(__ -> setMembre(form.new Member(2), club, req.getSaison())
.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) {
return Uni.createFrom().nullItem()
.chain(() -> {
ClubModel club = new ClubModel();
club.setName(form.getName());
club.setCountry("FR");
club.setSIRET(form.getSiret());
club.setRNA(form.getRna());
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) {
return Uni.createFrom().nullItem()
.chain(() -> {
club.setName(form.getName());
club.setCountry("FR");
club.setSIRET(form.getSiret());
club.setRNA(form.getRna());
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)));
})
.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("SIRET = ?1", out.getSiret()).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.getSiret(), 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("siret = ?1", model.getSIRET())
.map(reqs -> reqs.stream().map(req ->
new SimpleAffiliation(req.getId() * -1, model.getId(), 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) {
return repositoryRequest.findById(id)
.call(aff -> 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())
))
.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,88 +1,32 @@
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.MembreModel;
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.request.SReqClub;
import fr.titionfire.ffsaf.rest.data.ClubMapData;
import fr.titionfire.ffsaf.rest.data.DeskMember;
import fr.titionfire.ffsaf.rest.data.RenewAffData;
import fr.titionfire.ffsaf.rest.data.SimpleClubList;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.rest.from.FullClubForm;
import fr.titionfire.ffsaf.rest.from.PartClubForm;
import fr.titionfire.ffsaf.utils.*;
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.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.vertx.VertxContextSupport;
import io.smallrye.mutiny.Multi;
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.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
import java.util.Collection;
import java.util.List;
@WithSession
@ApplicationScoped
public class ClubService {
private static final Logger LOGGER = Logger.getLogger(ClubService.class);
@Inject
ClubRepository repository;
@Inject
ServerCustom serverCustom;
@Inject
CombRepository combRepository;
@Inject
KeycloakService keycloakService;
@ConfigProperty(name = "upload_dir")
String media;
@Inject
LoggerService ls;
@ConfigProperty(name = "pdf-maker.jar-path")
String pdfMakerJarPath;
@ConfigProperty(name = "pdf-maker.sign-file")
String sign_file;
public SimpleClubModel findByIdOptionalClub(long id) throws Throwable {
return VertxContextSupport.subscribeAndAwait(
() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
}
public Collection<SimpleClubModel> findAllClub() throws Throwable {
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(
() -> repository.findAll().list()
.map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
() -> repository.findAll().list().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
}
public Uni<List<ClubModel>> getAll() {
@@ -91,375 +35,8 @@ public class ClubService {
public Uni<?> setClubId(Long id, String id1) {
return repository.findById(id).chain(clubModel -> {
ls.logChange("KC UUID", clubModel.getClubId(), id1, clubModel);
clubModel.setClubId(id1);
return Panache.withTransaction(() -> repository.persist(clubModel))
.call(() -> ls.append());
return Panache.withTransaction(() -> repository.persist(clubModel));
});
}
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<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) {
return repository.findById(id).call(m -> Mutiny.fetch(m.getContact()))
.onItem().transformToUni(Unchecked.function(m -> {
TypeReference<HashMap<Contact, String>> typeRef = new TypeReference<>() {
};
m.setName(input.getName());
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());
ls.logChange("N° RNA", m.getRNA(), input.getRna(), m);
m.setRNA(input.getRna());
if (input.getSiret() != null && !input.getSiret().isBlank()) {
ls.logChange("N° SIRET", m.getSIRET(), input.getSiret(), m);
m.setSIRET(Long.parseLong(input.getSiret()));
}
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());
}))
.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());
clubModel.setRNA(input.getRna());
if (input.getSiret() != null && !input.getSiret().isBlank())
clubModel.setSIRET(Long.parseLong(input.getSiret()));
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.setSiret(clubModel.getSIRET());
data.setRna(clubModel.getRNA());
data.setAddress(clubModel.getAddress());
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();
}
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(
repository.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, LOGGER);
}
static byte[] getPdf(List<String> cmd, UUID uuid, Logger logger) 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();
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,134 +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.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.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.util.HashMap;
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
@CacheName("safca-config")
Cache cache;
@Inject
@CacheName("safca-have-access")
Cache cacheAccess;
@Inject
CompetitionRepository competitionRepository;
public Uni<SimpleCompet> getSafcaConfig(long id) {
return cache.get(id, k -> {
CompletableFuture<SimpleCompet> f = new CompletableFuture<>();
SReqCompet.getConfig(serverCustom.clients, id, f);
System.out.println("get config");
try {
return f.get(1500, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
});
}
public Uni<HashMap<Long, String>> getAllHaveAccess(String subject) {
return cacheAccess.get(subject, k -> {
CompletableFuture<HashMap<Long, String>> f = new CompletableFuture<>();
SReqCompet.getAllHaveAccess(serverCustom.clients, subject, f);
System.out.println("get all have access");
try {
return f.get(1500, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
});
}
public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
return hasViewPerm(securityCtx, Uni.createFrom().item(competitionModel));
}
public Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, long id) {
return hasViewPerm(securityCtx, competitionRepository.findById(id));
}
private Uni<CompetitionModel> hasViewPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
return in.call(o -> (
securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) ?
Uni.createFrom().nullItem()
:
o.getSystem() == CompetitionSystem.SAFCA ?
hasSafcaViewPerm(securityCtx, o.getId())
: Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> {
if (!securityCtx.isInClubGroup(o.getClub().getId()))
throw new DForbiddenException();
})
));
}
public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, CompetitionModel competitionModel) {
return hasEditPerm(securityCtx, Uni.createFrom().item(competitionModel));
}
public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, long id) {
return hasEditPerm(securityCtx, competitionRepository.findById(id));
}
public Uni<CompetitionModel> hasEditPerm(SecurityCtx securityCtx, Uni<CompetitionModel> in) {
return in.call(o -> (
securityCtx.getSubject().equals(o.getOwner()) || securityCtx.roleHas("federation_admin")) ?
Uni.createFrom().nullItem()
:
o.getSystem() == CompetitionSystem.SAFCA ?
hasSafcaEditPerm(securityCtx, o.getId())
: Uni.createFrom().nullItem().invoke(Unchecked.consumer(__ -> {
if (!securityCtx.isInClubGroup(o.getClub().getId()))
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,365 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
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.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.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.data.model.RegisterModel;
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.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 org.hibernate.reactive.mutiny.Mutiny;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.*;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
public class CompetitionService {
@Inject
CompetitionRepository repository;
@Inject
PouleRepository pouleRepository;
@Inject
MatchRepository matchRepository;
@Inject
KeycloakService keycloakService;
@Inject
CombRepository combRepository;
@Inject
ServerCustom serverCustom;
@Inject
CompetPermService permService;
@Inject
Vertx vertx;
@Inject
@CacheName("safca-config")
Cache cache;
@Inject
@CacheName("safca-have-access")
Cache cacheAccess;
@Inject
RegisterRepository registerRepository;
public Uni<CompetitionData> getById(SecurityCtx securityCtx, Long id) {
if (id == 0) {
return Uni.createFrom()
.item(new CompetitionData(null, "", "", new Date(), CompetitionSystem.SAFCA,
null, "", "", null));
}
return permService.hasViewPerm(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) {
return repository.listAll()
.chain(o ->
permService.getAllHaveAccess(securityCtx.getSubject())
.chain(map -> Uni.createFrom().item(o.stream()
.filter(p -> {
if (securityCtx.getSubject().equals(p.getOwner()))
return true;
if (p.getSystem() == CompetitionSystem.SAFCA) {
if (map.containsKey(p.getId()))
return map.get(p.getId()).equals("admin");
return securityCtx.roleHas("federation_admin")
|| securityCtx.roleHas("safca_super_admin");
}
return securityCtx.roleHas("federation_admin");
})
.map(CompetitionData::fromModel).toList())
));
}
public Uni<List<CompetitionData>> getAllSystem(SecurityCtx securityCtx,
CompetitionSystem system) {
if (system == CompetitionSystem.SAFCA) {
return permService.getAllHaveAccess(securityCtx.getSubject())
.chain(map ->
repository.list("system = ?1", system)
.map(data -> data.stream()
.filter(p -> {
if (securityCtx.getSubject().equals(p.getOwner()))
return true;
if (map.containsKey(p.getId()))
return map.get(p.getId()).equals("admin");
return securityCtx.roleHas("federation_admin")
|| securityCtx.roleHas("safca_super_admin");
})
.map(CompetitionData::fromModel).toList())
);
}
return repository.list("system = ?1", system)
.map(data -> data.stream()
.filter(p -> {
if (securityCtx.getSubject().equals(p.getOwner()))
return true;
return securityCtx.roleHas("federation_admin") ||
securityCtx.isInClubGroup(p.getClub().getId());
})
.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 (combModel == null)
throw new DNotFoundException("Profile non trouvé");
if (data.getSystem() == CompetitionSystem.SAFCA)
if (!securityCtx.getRoles().contains("safca_create_compet"))
throw new DForbiddenException("Vous ne pouvez pas créer de compétition SAFCA");
}))
.map(MembreModel::getClub)
.chain(clubModel -> {
CompetitionModel model = new CompetitionModel();
model.setId(null);
model.setSystem(data.getSystem());
model.setClub(clubModel);
model.setDate(data.getDate());
model.setInsc(new ArrayList<>());
model.setUuid(UUID.randomUUID().toString());
model.setName(data.getName());
model.setOwner(securityCtx.getSubject());
return Panache.withTransaction(() -> repository.persist(model));
}).map(CompetitionData::fromModel)
.call(__ -> cacheAccess.invalidate(securityCtx.getSubject()));
} else {
return permService.hasEditPerm(securityCtx, data.getId())
.chain(model -> {
model.setDate(data.getDate());
model.setName(data.getName());
return vertx.getOrCreateContext().executeBlocking(() ->
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(__ -> cacheAccess.invalidate(securityCtx.getSubject()));
}
}
public Uni<List<SimpleRegisterComb>> getRegister(SecurityCtx securityCtx, Long id) {
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();
}
public Uni<SimpleRegisterComb> addRegisterComb(SecurityCtx securityCtx, Long id, RegisterRequestData data) {
return permService.hasEditPerm(securityCtx, id)
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
.chain(combModel -> Mutiny.fetch(c.getInsc())
.chain(Unchecked.function(insc -> {
Optional<RegisterModel> opt = insc.stream()
.filter(m -> m.getMembre().equals(combModel)).findAny();
RegisterModel r;
if (opt.isPresent()) {
r = opt.get();
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());
}
} 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());
insc.add(r);
}
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 Panache.withTransaction(() -> repository.persist(c)).map(__ -> r);
}))))
.chain(r -> Mutiny.fetch(r.getMembre().getLicences())
.map(licences -> SimpleRegisterComb.fromModel(r, licences)));
}
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) {
return permService.hasEditPerm(securityCtx, id)
.chain(c -> registerRepository.delete("competition = ?1 AND membre.id = ?2", c, combId)
.invoke(Unchecked.consumer(l -> {
if (l != 0){
if (c.getSystem() == CompetitionSystem.SAFCA) {
SReqRegister.sendRmIfNeed(serverCustom.clients, combId, id);
}
}else{
throw new DBadRequestException("Combattant non inscrit");
}
}))
).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 -> pouleRepository.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(
() -> pouleRepository.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()));
}
}

View File

@@ -2,10 +2,8 @@ package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.reactive.ReactiveMailer;
import fr.titionfire.ffsaf.utils.KeycloakException;
import fr.titionfire.ffsaf.utils.RequiredAction;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
@@ -23,10 +21,8 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ApplicationScoped
public class KeycloakService {
@@ -44,12 +40,6 @@ public class KeycloakService {
@ConfigProperty(name = "keycloak.realm")
String realm;
@ConfigProperty(name = "email.enabled")
boolean enabled_email;
@Inject
ReactiveMailer reactiveMailer;
@Inject
Vertx vertx;
@@ -57,28 +47,21 @@ public class KeycloakService {
if (club.getClubId() == null) {
LOGGER.infof("Creation 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")));
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")));
GroupRepresentation groupRepresentation = new GroupRepresentation();
groupRepresentation.setName(club.getId() + "-" + club.getName());
try (Response response =
keycloak.realm(realm).groups().group(clubGroup.getId()).subGroup(groupRepresentation)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo()
.equals(Response.Status.CONFLICT))
throw new KeycloakException(
"Fail to set group parent for club: %s (reason=%s)".formatted(club.getName(),
response.getStatusInfo().getReasonPhrase()));
try (Response response = keycloak.realm(realm).groups().group(clubGroup.getId()).subGroup(groupRepresentation)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo().equals(Response.Status.CONFLICT))
throw new KeycloakException("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()
.filter(g -> g.getName().startsWith(club.getId() + "-")).findAny()
.map(GroupRepresentation::getId)
.orElseThrow(
() -> new KeycloakException("Fail to fetch group %s*".formatted(club.getId() + "-")));
.filter(g -> g.getName().startsWith(club.getId() + "-")).findAny().map(GroupRepresentation::getId)
.orElseThrow(() -> new KeycloakException("Fail to fetch group %s*".formatted(club.getId() + "-")));
}
).call(id -> clubService.setClubId(club.getId(), id));
}
@@ -87,110 +70,41 @@ public class KeycloakService {
public Uni<String> getUserFromMember(MembreModel membreModel) {
if (membreModel.getUserId() == null) {
return Uni.createFrom()
.failure(new DInternalError("No keycloak user linked to the user id=" + membreModel.getId()));
return Uni.createFrom().failure(new NullPointerException("No keycloak user linked to the user id=" + membreModel.getId()));
}
return Uni.createFrom().item(membreModel::getUserId);
}
public Uni<String> setClubGroupMembre(MembreModel membreModel, ClubModel club) {
if (club == null)
return 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()));
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";
})));
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) {
return vertx.getOrCreateContext().executeBlocking(() -> {
UserResource user = keycloak.realm(realm).users().get(userId);
UserRepresentation user2 = user.toRepresentation();
String oldEmail = user2.getEmail();
if (email.equals(user2.getEmail()))
return null;
user2.setEmail(email);
user2.setRequiredActions(List.of(RequiredAction.VERIFY_EMAIL.name()));
user.update(user2);
if (enabled_email)
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 (ré)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) {
List<String> toRemove = new ArrayList<>(List.of("club_president", "club_tresorier", "club_secretaire",
"club_respo_intra", "asseseur", "arbitre"));
List<String> toAdd = new ArrayList<>();
switch (role) {
case PRESIDENT, VPRESIDENT -> toAdd.add("club_president");
case TRESORIER, VTRESORIER -> toAdd.add("club_tresorier");
case SECRETAIRE, VSECRETAIRE -> toAdd.add("club_secretaire");
case MEMBREBUREAU -> toAdd.add("club_respo_intra");
}
switch (gradeArbitrage) {
case ARBITRE -> toAdd.addAll(List.of("asseseur", "arbitre"));
case ASSESSEUR -> toAdd.add("asseseur");
}
toRemove.removeAll(toAdd);
return updateRole(id, toAdd, toRemove);
}
public Uni<Pair<UserResource, UserCompteState>> fetchCompte(String id) {
public Uni<UserCompteState> fetchCompte(String id) {
return vertx.getOrCreateContext().executeBlocking(() -> {
UserResource user = keycloak.realm(realm).users().get(id);
UserRepresentation user2 = user.toRepresentation();
return new Pair<>(user, new UserCompteState(user2.isEnabled(), user2.getUsername(),
user2.isEmailVerified()));
return new UserCompteState(user2.isEnabled(), user2.getUsername(), user2.isEmailVerified(),
user.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).toList(),
user.groups().stream().map(GroupRepresentation::getName).toList());
});
}
public Uni<List<String>> fetchRole(String id) {
return vertx.getOrCreateContext().executeBlocking(() ->
keycloak.realm(realm).users().get(id).roles().realmLevel().listEffective().stream()
.map(RoleRepresentation::getName).toList());
keycloak.realm(realm).users().get(id).roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).toList());
}
public Uni<?> updateRole(String id, List<String> toAdd, List<String> toRemove) {
return vertx.getOrCreateContext().executeBlocking(() -> {
RoleScopeResource resource = keycloak.realm(realm).users().get(id).roles().realmLevel();
List<RoleRepresentation> roles = keycloak.realm(realm).roles().list();
List<RoleRepresentation> roles = keycloak.realm(realm) .roles().list();
resource.add(roles.stream().filter(r -> toAdd.contains(r.getName())).toList());
resource.remove(roles.stream().filter(r -> toRemove.contains(r.getName())).toList());
return "OK";
@@ -207,23 +121,15 @@ public class KeycloakService {
throw new KeycloakException("User name is null");
})).chain(membreModel -> creatUser(membreModel).chain(user -> {
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) {
String login = makeLogin(membreModel);
LOGGER.infof("Creation of user %s...", login);
return vertx.getOrCreateContext().executeBlocking(() -> {
String login;
int i = 1;
do {
login = makeLogin(membreModel);
if (i > 1) {
login += i;
}
i++;
} while (!keycloak.realm(realm).users().searchByUsername(login, true).isEmpty());
LOGGER.infof("Creation of user %s...", login);
UserRepresentation user = new UserRepresentation();
user.setUsername(login);
user.setFirstName(membreModel.getFname());
@@ -235,92 +141,18 @@ public class KeycloakService {
RequiredAction.UPDATE_PASSWORD.name()));
try (Response response = keycloak.realm(realm).users().create(user)) {
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo()
.equals(Response.Status.CONFLICT))
throw new KeycloakException("Fail to creat user %s (reason=%s)".formatted(login,
response.getStatusInfo().getReasonPhrase()));
if (!response.getStatusInfo().equals(Response.Status.CREATED) && !response.getStatusInfo().equals(Response.Status.CONFLICT))
throw new KeycloakException("Fail to creat user %s (reason=%s)".formatted(login, response.getStatusInfo().getReasonPhrase()));
}
String finalLogin = login;
return getUser(login).orElseThrow(
() -> new KeycloakException("Fail to fetch user %s".formatted(finalLogin)));
return getUser(login).orElseThrow(() -> new KeycloakException("Fail to fetch user %s".formatted(login)));
})
.call(user -> enabled_email ?
vertx.getOrCreateContext().executeBlocking(() -> {
keycloak.realm(realm).users().get(user.getId())
.executeActionsEmail(List.of(RequiredAction.VERIFY_EMAIL.name(),
RequiredAction.UPDATE_PASSWORD.name()));
return null;
}) : Uni.createFrom().nullItem())
.invoke(user -> membreModel.setUserId(user.getId()))
.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 pour accéder à l'intranet a été créé.
Ce compte vous permettra de consulter vos informations, de vous inscrire aux compétitions et de consulter vos résultats.
Vous allez recevoir dans les prochaines minutes un email vous demandant de vérifier votre email et de définir un mot de passe.
L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr
Votre nom d'utilisateur est : %s
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
""", 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 -> setClubGroupMembre(membreModel, membreModel.getClub()));
}
public Uni<?> setId(long id, String nid) {
return membreService.setUserId(id, nid).map(__ -> "OK");
}
public Uni<?> removeAccount(String userId) {
return vertx.getOrCreateContext().executeBlocking(() -> {
try (Response response = keycloak.realm(realm).users().delete(userId)) {
System.out.println(response.getStatusInfo());
if (!response.getStatusInfo().equals(Response.Status.NO_CONTENT))
throw new KeycloakException("Fail to delete user %s (reason=%s)".formatted(userId,
response.getStatusInfo().getReasonPhrase()));
}
return null;
});
}
public Uni<?> removeClubGroup(String clubId) {
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) {
private Optional<UserRepresentation> getUser(String username) {
List<UserRepresentation> users = keycloak.realm(realm).users().searchByUsername(username, true);
if (users.isEmpty())
@@ -329,24 +161,14 @@ public class KeycloakService {
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) {
return Normalizer.normalize(
(model.getFname().toLowerCase() + "." + model.getLname().toLowerCase()).replace(' ', '_'),
Normalizer.Form.NFD)
return Normalizer.normalize((model.getFname().toLowerCase() + "." + model.getLname().toLowerCase()).replace(' ', '_'), Normalizer.Form.NFD)
.replaceAll("\\p{M}", "");
}
@RegisterForReflection
public record UserCompteState(Boolean enabled, String login, Boolean emailVerified) {
public record UserCompteState(Boolean enabled, String login, Boolean emailVerified, List<String> realmRoles,
List<String> groups) {
}
}

View File

@@ -1,149 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.LicenceRepository;
import fr.titionfire.ffsaf.data.repository.SequenceRepository;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.utils.SequenceType;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.Panache;
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 org.hibernate.reactive.mutiny.Mutiny;
import java.util.List;
import java.util.function.Consumer;
@WithSession
@ApplicationScoped
public class LicenceService {
@Inject
LicenceRepository repository;
@Inject
CombRepository combRepository;
@Inject
SequenceRepository sequenceRepository;
public Uni<List<LicenceModel>> getLicence(long id, Consumer<MembreModel> checkPerm) {
return combRepository.findById(id).invoke(checkPerm)
.chain(combRepository -> Mutiny.fetch(combRepository.getLicences()));
}
public Uni<List<LicenceModel>> getCurrentSaisonLicence(SecurityCtx securityCtx) {
if (securityCtx == null || securityCtx.getSubject() == null)
return repository.find("saison = ?1", Utils.getSaison()).list();
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult().map(MembreModel::getClub)
.chain(clubModel -> combRepository.find("club = ?1", clubModel).list())
.chain(membres -> repository.find("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres).list());
}
public Uni<LicenceModel> setLicence(long id, LicenceForm form) {
if (form.getId() == -1) {
return combRepository.findById(id).chain(membreModel -> {
LicenceModel model = new LicenceModel();
model.setMembre(membreModel);
model.setClub_id((membreModel.getClub() == null) ? null : membreModel.getClub().getId());
model.setSaison(form.getSaison());
model.setCertificate(form.getCertificate());
model.setValidate(form.isValidate());
return Panache.withTransaction(() -> repository.persist(model)
.call(m -> (m.isValidate() && membreModel.getLicence() <= 0) ?
sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
.invoke(i -> membreModel.setLicence(Math.toIntExact(i)))
.chain(() -> combRepository.persist(membreModel))
: Uni.createFrom().nullItem()
));
});
} else {
return repository.findById(form.getId()).chain(model -> {
model.setCertificate(form.getCertificate());
model.setValidate(form.isValidate());
return Panache.withTransaction(() -> repository.persist(model)
.call(m -> m.isValidate() ? Mutiny.fetch(m.getMembre())
.call(membreModel -> (membreModel.getLicence() <= 0) ?
sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
.invoke(i -> membreModel.setLicence(Math.toIntExact(i)))
.chain(() -> combRepository.persist(membreModel))
: Uni.createFrom().nullItem())
: Uni.createFrom().nullItem()
));
});
}
}
public Uni<?> deleteLicence(long id) {
return Panache.withTransaction(() -> repository.deleteById(id));
}
public Uni<LicenceModel> askLicence(long id, LicenceForm form, Consumer<MembreModel> checkPerm) {
return combRepository.findById(id).invoke(checkPerm).chain(membreModel -> {
if (form.getId() == -1) {
return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count()
.invoke(Unchecked.consumer(count -> {
if (count > 0)
throw new DBadRequestException("Licence déjà demandée");
})).chain(__ -> combRepository.findById(id).chain(membreModel2 -> {
LicenceModel model = new LicenceModel();
model.setClub_id((membreModel2.getClub() == null) ? null : membreModel2.getClub().getId());
model.setMembre(membreModel2);
model.setSaison(Utils.getSaison());
model.setCertificate(form.getCertificate());
model.setValidate(false);
return Panache.withTransaction(() -> repository.persist(model));
}));
} else {
return repository.findById(form.getId()).chain(model -> {
model.setCertificate(form.getCertificate());
return Panache.withTransaction(() -> repository.persist(model));
});
}
});
}
public Uni<?> deleteAskLicence(long id, Consumer<MembreModel> checkPerm) {
return repository.findById(id)
.call(licenceModel -> Mutiny.fetch(licenceModel.getMembre()).invoke(checkPerm))
.invoke(Unchecked.consumer(licenceModel -> {
if (licenceModel.isValidate())
throw new DBadRequestException("Impossible de supprimer une licence déjà validée");
}))
.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,97 +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 log(ActionType action, String message, LoggableModel model) {
log(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 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.PouleRepository;
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
PouleRepository pouleRepository;
@Inject
CombRepository combRepository;
@Inject
CompetPermService permService;
public Uni<MatchData> getById(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.hasViewPerm(securityCtx, data.getPoule().getCompet()))
.map(MatchData::fromModel);
}
public Uni<List<MatchData>> getAllByPoule(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return pouleRepository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found"))
.call(data -> permService.hasViewPerm(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 pouleRepository.find("systemId = ?1 AND system = ?2", data.getPoule(), system)
.firstResult()
.onItem().ifNull().failWith(() -> new DNotFoundException("Poule not found"))
.call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet()))
.map(pouleModel -> {
MatchModel model = new MatchModel();
model.setId(null);
model.setSystem(system);
model.setSystemId(data.getId());
model.setPoule(pouleModel);
return model;
});
} else {
return pouleRepository.find("systemId = ?1 AND system = ?2", data.getPoule(), 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.setPoule_ord(data.getPoule_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.getPoule().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.getPoule().getCompet()))
.chain(data -> Panache.withTransaction(() -> repository.delete(data)));
}
}

View File

@@ -1,546 +1,82 @@
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.*;
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.SimpleCombModel;
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.SimpleMembreInOutData;
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.FullMemberForm;
import fr.titionfire.ffsaf.utils.*;
import fr.titionfire.ffsaf.utils.Pair;
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.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.scheduler.Scheduled;
import io.quarkus.vertx.VertxContextSupport;
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.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import static fr.titionfire.ffsaf.domain.service.ClubService.getPdf;
import java.util.List;
@WithSession
@ApplicationScoped
public class MembreService {
private static final Logger LOGGER = Logger.getLogger(MembreService.class);
@Inject
CombRepository repository;
@Inject
ClubRepository clubRepository;
@Inject
LicenceRepository licenceRepository;
@Inject
CompetitionRepository competitionRepository;
@Inject
ServerCustom serverCustom;
@Inject
KeycloakService keycloakService;
@ConfigProperty(name = "upload_dir")
String media;
@ConfigProperty(name = "pdf-maker.jar-path")
String pdfMakerJarPath;
@Inject
RegisterRepository registerRepository;
@Inject
LoggerService ls;
public SimpleCombModel find(int licence, String np) throws Throwable {
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() ->
repository.find(
"licence = ?1 AND (unaccent(lname) ILIKE unaccent(?2) OR unaccent(fname) ILIKE unaccent(?2))",
repository.find("licence = ?1 AND (lname collate UTF8_GENERAL_CI LIKE ?2 OR fname collate UTF8_GENERAL_CI LIKE ?2)",
licence, np).firstResult().map(SimpleCombModel::fromModel)));
}
public SimpleCombModel findByIdOptionalComb(long id) throws Throwable {
return VertxContextSupport.subscribeAndAwait(
() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleCombModel::fromModel)));
return VertxContextSupport.subscribeAndAwait(() -> 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) " +
"OR unaccent(fname || ' ' || lname) ILIKE unaccent(?1) OR unaccent(lname || ' ' || fname) ILIKE unaccent(?1)";
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club) {
if (search == null)
search = "";
search = "%" + search.replaceAll(" ", "% %") + "%";
PanacheQuery<MembreModel> query;
if (club == null || club.isBlank()) {
query = repository.find(FIND_NAME_REQUEST, Sort.ascending("fname", "lname"), search)
.page(Page.ofSize(limit));
} else {
if (club.equals("null")) {
query = repository.find(
"club IS NULL AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), search).page(Page.ofSize(limit));
} else {
query = repository.find(
"LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ")",
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.replaceAll(" ", "% %") + "%";
String finalSearch = search;
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
PanacheQuery<MembreModel> query = repository.find(
"club = ?2 AND (" + FIND_NAME_REQUEST + ")",
Sort.ascending("fname", "lname"), finalSearch, membreModel.getClub())
.page(Page.ofSize(limit));
return getPageResult(query, limit, page);
});
}
private Uni<PageResult<SimpleMembre>> getPageResult(PanacheQuery<MembreModel> query, int limit, int page) {
return Uni.createFrom().item(new PageResult<SimpleMembre>())
.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(SimpleMembre::fromModel).toList())
.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<>();
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",
data2.stream().map(SimpleMembreInOutData::getLicence).filter(Objects::nonNull).toList(),
data2.stream().map(o -> (o.getNom() + " " + o.getPrenom()).toLowerCase()).toList());
})
.call(Unchecked.function(membres -> {
for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get()))
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 -> Objects.equals(m.getLicence(), dataIn.getLicence()) || m.getLname()
.equals(dataIn.getNom()) && m.getFname().equals(dataIn.getPrenom())).findFirst()
.orElseGet(() -> {
MembreModel mm = new MembreModel();
mm.setClub(clubModel.get());
mm.setLicences(new ArrayList<>());
mm.setCountry("FR");
return mm;
});
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)) {
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<List<MembreModel>> getAll() {
return repository.listAll(Sort.ascending("fname", "lname"));
}
public Uni<MembreModel> getById(long 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) {
return update(repository.findById(id)
.chain(membreModel -> clubRepository.findById(membre.getClub())
.map(club -> new Pair<>(membreModel, club)))
.onItem().transform(pair -> {
return repository.findById(id)
.chain(membreModel -> clubRepository.findById(membre.getClub()).map(club -> new Pair<>(membreModel, club)))
.onItem().transformToUni(pair -> {
MembreModel m = pair.getKey();
ls.logChange("Rôle", m.getRole(), membre.getRole(), m);
m.setRole(membre.getRole());
ls.logChange("Club", m.getClub(), pair.getValue(), m);
m.setFname(membre.getFname());
m.setLname(membre.getLname());
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());
return m;
}), membre, true);
}
public Uni<String> update(long id, FullMemberForm membre, SecurityCtx securityCtx) {
return update(repository.findById(id)
.invoke(Unchecked.consumer(membreModel -> {
if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
throw new DForbiddenException();
}))
.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());
m.setEmail(membre.getEmail());
return Panache.withTransaction(() -> repository.persist(m));
})
.invoke(membreModel -> SReqComb.sendIfNeed(serverCustom.clients,
SimpleCombModel.fromModel(membreModel)))
.call(membreModel -> (admin && membreModel.getUserId() != null) ?
((membreModel.getClub() != null) ?
keycloakService.setClubGroupMembre(membreModel, membreModel.getClub()) :
keycloakService.clearUser(membreModel.getUserId()))
: Uni.createFrom().nullItem())
.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())
.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())
keycloakService.setClubGroupMembre(membreModel, membreModel.getClub()) : Uni.createFrom().nullItem())
.map(__ -> "OK");
}
public Uni<Long> add(FullMemberForm input) {
return clubRepository.findById(input.getClub())
.chain(clubModel -> {
MembreModel model = getMembreModel(input, clubModel);
return Panache.withTransaction(() -> repository.persist(model));
})
.call(membreModel -> ls.logAAdd(membreModel))
.invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients,
SimpleCombModel.fromModel(membreModel)))
.map(MembreModel::getId);
}
public Uni<Long> add(FullMemberForm input, String subject) {
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> {
MembreModel model = getMembreModel(input, membreModel.getClub());
model.setRole(RoleAsso.MEMBRE);
model.setGrade_arbitrage(GradeArbitrage.NA);
return Panache.withTransaction(() -> repository.persist(model));
})
.call(membreModel -> ls.logAAdd(membreModel))
.invoke(membreModel -> SReqComb.sendIfNeedAdd(serverCustom.clients,
SimpleCombModel.fromModel(membreModel)))
.map(MembreModel::getId);
}
public Uni<String> delete(long id) {
return repository.findById(id)
.call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
.call(membreModel -> ls.logADelete(membreModel))
.call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
.invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
.map(__ -> "Ok");
}
public Uni<String> delete(long id, SecurityCtx securityCtx) {
return repository.findById(id)
.invoke(Unchecked.consumer(membreModel -> {
if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
throw new DForbiddenException();
}))
.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()
.invoke(Unchecked.consumer(l -> {
if (l > 0)
throw new DBadRequestException("Impossible de supprimer un membre avec des licences");
})))
.call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
.call(membreModel -> ls.logADelete(membreModel))
.call(membreModel -> Panache.withTransaction(() -> repository.delete(membreModel)))
.invoke(membreModel -> SReqComb.sendRm(serverCustom.clients, id))
.call(__ -> Utils.deleteMedia(id, media, "ppMembre"))
.map(__ -> "Ok");
}
public Uni<?> setUserId(Long id, String id1) {
return repository.findById(id).chain(membreModel -> {
ls.logChange("KC UUID", membreModel.getUserId(), id1, membreModel);
membreModel.setUserId(id1);
return Panache.withTransaction(() -> repository.persist(membreModel))
.call(() -> ls.append());
return Panache.withTransaction(() -> repository.persist(membreModel));
});
}
private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) {
MembreModel model = new MembreModel();
model.setFname(input.getFname());
model.setLname(input.getLname());
model.setEmail(input.getEmail());
model.setLicence(null);
model.setGenre(input.getGenre());
model.setCountry(input.getCountry());
model.setBirth_date(input.getBirth_date());
model.setCategorie(Utils.getCategoryFormBirthDate(input.getBirth_date(), new Date()));
model.setClub(clubModel);
model.setRole(input.getRole());
model.setGrade_arbitrage(input.getGrade_arbitrage());
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);
}
public Uni<Response> getLicencePdf(String subject) {
return getLicencePdf(repository.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, LOGGER);
}
}

View File

@@ -1,280 +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.PouleModel;
import fr.titionfire.ffsaf.data.model.TreeModel;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.data.PouleData;
import fr.titionfire.ffsaf.rest.data.PouleFullData;
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 PouleService {
@Inject
PouleRepository repository;
@Inject
CompetitionRepository competRepository;
@Inject
MatchRepository matchRepository;
@Inject
TreeRepository treeRepository;
@Inject
CombRepository combRepository;
@Inject
CompetPermService permService;
public Uni<PouleData> getById(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.hasViewPerm(securityCtx, data.getCompet()))
.map(PouleData::fromModel);
}
public Uni<List<PouleData>> getAll(SecurityCtx securityCtx, CompetitionSystem system) {
return repository.list("system = ?1", system)
.chain(o ->
permService.getAllHaveAccess(securityCtx.getSubject())
.chain(map -> Uni.createFrom().item(o.stream()
.filter(p -> {
if (securityCtx.getSubject().equals(p.getCompet().getOwner()))
return true;
if (p.getSystem() == CompetitionSystem.SAFCA) {
if (map.containsKey(p.getCompet().getId()))
return map.get(p.getId()).equals("admin");
return securityCtx.roleHas("federation_admin")
|| securityCtx.roleHas("safca_super_admin");
}
return securityCtx.roleHas("federation_admin");
})
.map(PouleData::fromModel).toList())
));
}
public Uni<PouleData> addOrUpdate(SecurityCtx securityCtx, CompetitionSystem system, PouleData 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 -> {
PouleModel model = new PouleModel();
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(PouleData::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, PouleModel poule,
List<MatchModel> matchModelList) {
TreeModel mm = findNode(node, data.getMatch());
if (mm == null) {
mm = new TreeModel();
mm.setId(null);
}
mm.setLevel(data.getLevel());
mm.setPoule(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<?> syncPoule(SecurityCtx securityCtx, CompetitionSystem system, PouleFullData 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 -> {
PouleModel model = new PouleModel();
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.poule = o;
return workData;
})
.call(o -> Panache.withTransaction(() -> repository.persist(o.poule)))
.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.poule.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.poule.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.poule.getMatchs(), m.getId());
if (mm == null) {
mm = new MatchModel();
mm.setId(null);
mm.setSystem(system);
mm.setSystemId(m.getId());
}
mm.setPoule(in.poule);
mm.setPoule_ord(m.getPoule_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.setGroupe(m.getGroupe());
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.poule.getTree(), in.poule, in.match)).toList())
.andCollectFailures())
.map(__ -> "OK");
}
private static class WorkData {
PouleModel poule;
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,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,4 +0,0 @@
package fr.titionfire.ffsaf.domain.service;
public class TreeService {
}

View File

@@ -40,7 +40,7 @@ public class Client_Thread extends Thread {
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 {
this.serv = serv;
@@ -162,7 +162,7 @@ public class Client_Thread extends Thread {
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;
do {
uuid = UUID.randomUUID();

View File

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

View File

@@ -8,14 +8,12 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Schema(hidden = true)
public class SimpleCombModel {
Long id;
String lname = "";
@@ -26,12 +24,12 @@ public class SimpleCombModel {
int licence = 0;
String country = "fr";
public static SimpleCombModel fromModel(MembreModel model) {
public static SimpleCombModel fromModel(MembreModel model) {
if (model == null)
return null;
return new SimpleCombModel(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
(model.getClub() == null) ? null : SimpleClubModel.fromModel(model.getClub()),
model.getGenre(), (model.getLicence() == 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 {
private static final Logger LOGGER = Logger.getLogger(RComb.class);
final IAction findComb = (client_Thread, message) -> {
try {
SimpleCombModel combModel = ServerCustom.getInstance().membreService.find(message.data().get("licence").asInt(), message.data().get("np").asText());
client_Thread.sendRepTo(combModel, message);
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
client_Thread.sendErrTo(e.getMessage(), message);
}
};
final CIA<Long> findByIdOptionalComb = new CIA<>(Long.class, (client_Thread, message) -> {
try {
SimpleCombModel combModel = ServerCustom.getInstance().membreService.findByIdOptionalComb(message.data());
@@ -24,6 +34,7 @@ public class RComb {
public static void register(HashMap<String, IAction> iMap) {
RComb rComb = new RComb();
iMap.put("findComb", rComb.findComb);
iMap.put("findByIdOptionalComb", rComb.findByIdOptionalComb);
}
}

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<Long, 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 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.AffiliationService;
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.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.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")
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
@Path("{id}")
@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)
@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.")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Réussite"),
@APIResponse(responseCode = "403", description = "Accès refusé")
})
public Uni<?> deleteAffiliation(
@Parameter(description = "L'identifiant de l'affiliation") @PathParam("id") long id) {
return service.deleteAffiliation(id);
}
}

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"})
@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));
}
@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,31 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("api/asso")
public class AssoEndpoints {
@RestClient
SirenService sirenService;
@GET
@Path("siren/{siren}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<UniteLegaleRoot> getInfoSiren(@PathParam("siren") String siren) {
return sirenService.get_unite(siren).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 400)
return new DNotFoundException("Siret introuvable");
}
return throwable;
});
}
}

View File

@@ -11,9 +11,6 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
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.URISyntaxException;
@@ -28,16 +25,10 @@ public class AuthEndpoints {
SecurityIdentity securityIdentity;
@Inject
JsonWebToken IdToken;
JsonWebToken accessToken;
@GET
@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() {
return !securityIdentity.isAnonymous();
}
@@ -46,21 +37,14 @@ public class AuthEndpoints {
@Path("/userinfo")
@Authenticated
@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() {
return UserInfo.makeUserInfo(IdToken, securityIdentity);
return UserInfo.makeUserInfo(accessToken, securityIdentity);
}
@GET
@Path("/login")
@Authenticated
@Produces(MediaType.TEXT_PLAIN)
@Operation(hidden = true)
public Response login() throws URISyntaxException {
return Response.temporaryRedirect(new URI(redirect)).build();
}

View File

@@ -1,351 +1,28 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.domain.service.ClubService;
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.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.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import 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.HashMap;
import java.util.List;
import java.util.function.Consumer;
@Tag(name = "Club", description = "Gestion des clubs")
@Path("api/club")
public class ClubEndpoints {
@Inject
ClubService clubService;
@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
@Path("/no_detail")
@Authenticated
@RolesAllowed("federation_admin")
@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() {
return clubService.getAll().map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).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 clubService.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 clubService.getAffiliationPdf(securityCtx.getSubject());
}
@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,136 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.MembreService;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
import fr.titionfire.ffsaf.utils.Pair;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jodd.net.MimeTypes;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@Path("api/member")
public class CombEndpoints {
@Inject
MembreService membreService;
@ConfigProperty(name = "upload_dir")
String media;
@GET
@Path("/all")
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<SimpleMembre>> getAll() {
return membreService.getAll().map(membreModels -> membreModels.stream().map(SimpleMembre::fromModel).toList());
}
@GET
@Path("{id}")
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
public Uni<SimpleMembre> getById(@PathParam("id") long id) {
return membreService.getById(id).map(SimpleMembre::fromModel);
}
@POST
@Path("{id}")
@RolesAllowed("federation_admin")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<String> setAdminMembre(@PathParam("id") long id, FullMemberForm input) {
Future<String> future = CompletableFuture.supplyAsync(() -> {
try (InputStream is = new BufferedInputStream(new ByteArrayInputStream(input.getPhoto_data()))) {
String mimeType = URLConnection.guessContentTypeFromStream(is);
String[] 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/" + input.getId() + extension).toPath(), input.getPhoto_data());
return "OK";
} catch (IOException e) {
return e.getMessage();
}
});
if (input.getPhoto_data().length > 0) {
return membreService.update(id, input)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
}))
.chain(() -> Uni.createFrom().future(future))
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to get MimeType " + out);
}));
} else {
return membreService.update(id, input)
.invoke(Unchecked.consumer(out -> {
if (!out.equals("OK")) throw new InternalError("Fail to update data: " + out);
}));
}
}
@GET
@Path("{id}/photo")
@RolesAllowed("federation_admin")
public Uni<Response> getPhoto(@PathParam("id") long id) throws URISyntaxException {
Future<Pair<File, byte[]>> future = CompletableFuture.supplyAsync(() -> {
FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id));
File[] files = new File(media, "ppMembre").listFiles(filter);
if (files != null && files.length > 0) {
File file = files[0];
try {
byte[] data = Files.readAllBytes(file.toPath());
return new Pair<>(file, data);
} catch (IOException ignored) {
}
}
return null;
});
URI uri = new URI("https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-chat/ava2.webp");
return Uni.createFrom().future(future)
.map(filePair -> {
if (filePair == null)
return Response.temporaryRedirect(uri).build();
String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName());
Response.ResponseBuilder resp = Response.ok(filePair.getValue());
resp.type(MediaType.APPLICATION_OCTET_STREAM);
resp.header(HttpHeaders.CONTENT_LENGTH, filePair.getValue().length);
resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
resp.header(HttpHeaders.CONTENT_DISPOSITION, "inline; ");
return resp.build();
});
}
}

View File

@@ -1,109 +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.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 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) {
return service.getById(securityCtx, id);
}
@GET
@Path("{id}/register")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<SimpleRegisterComb>> getRegister(@PathParam("id") Long id) {
return service.getRegister(securityCtx, id);
}
@POST
@Path("{id}/register")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<SimpleRegisterComb> addRegisterComb(@PathParam("id") Long id, RegisterRequestData data) {
return service.addRegisterComb(securityCtx, id, data);
}
@DELETE
@Path("{id}/register/{comb_id}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<Void> removeRegisterComb(@PathParam("id") Long id, @PathParam("comb_id") Long combId) {
return service.removeRegisterComb(securityCtx, id, combId);
}
@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);
}
@GET
@Path("all/{system}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<CompetitionData>> getAllSystem(@PathParam("system") CompetitionSystem system) {
return service.getAllSystem(securityCtx, system);
}
@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,110 +1,48 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.KeycloakService;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.MemberPermForm;
import fr.titionfire.ffsaf.utils.Pair;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Vertx;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
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 java.util.ArrayList;
import java.util.List;
@Tag(name = "Compte", description = "Gestion des comptes utilisateurs")
@Path("api/compte")
public class CompteEndpoints {
@Inject
KeycloakService service;
@Inject
SecurityCtx securityCtx;
@Inject
Vertx vertx;
@GET
@Path("{id}")
@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) {
return service.fetchCompte(id).call(pair -> vertx.getOrCreateContext().executeBlocking(() -> {
if (!securityCtx.roleHas("federation_admin") && pair.getKey().groups().stream()
.map(GroupRepresentation::getPath)
.noneMatch(s -> s.startsWith("/club/") && securityCtx.contains(s)))
throw new DForbiddenException();
return pair;
})).map(Pair::getValue);
@RolesAllowed("federation_admin")
public Uni<?> getCompte(@PathParam("id") String id) {
return service.fetchCompte(id);
}
@PUT
@Path("{id}/init")
@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) {
return service.initCompte(id);
}
@PUT
@Path("{id}/setUUID/{nid}")
@RolesAllowed("federation_admin")
@Operation(hidden = true)
public Uni<?> initCompte(@PathParam("id") long id, @PathParam("nid") String nid) {
return service.setId(id, nid);
}
@GET
@Path("{id}/roles")
@RolesAllowed("federation_admin")
@Operation(summary = "Renvoie les rôles d'un compte utilisateur", description = "Renvoie les rôles d'un compte" +
" 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) {
public Uni<?> getRole(@PathParam("id") String id) {
return service.fetchRole(id);
}
@PUT
@Path("{id}/roles")
@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) {
List<String> toAdd = new ArrayList<>();
List<String> toRemove = new ArrayList<>();

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

@@ -1,158 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.LicenceService;
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.from.LicenceForm;
import fr.titionfire.ffsaf.utils.SecurityCtx;
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.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import java.util.List;
import java.util.function.Consumer;
@Path("api/licence")
public class LicenceEndpoints {
@Inject
LicenceService licenceService;
@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 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) {
return licenceService.getLicence(id, checkPerm)
.map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
}
@GET
@Path("current/admin")
@RolesAllowed({"federation_admin"})
@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() {
return licenceService.getCurrentSaisonLicence(null)
.map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
}
@GET
@Path("current/club")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@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() {
return licenceService.getCurrentSaisonLicence(securityCtx)
.map(licenceModels -> licenceModels.stream().map(SimpleLicence::fromModel).toList());
}
@POST
@Path("{id}")
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
@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) {
return licenceService.setLicence(id, form).map(SimpleLicence::fromModel);
}
@DELETE
@Path("{id}")
@RolesAllowed("federation_admin")
@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) {
return licenceService.deleteLicence(id);
}
@POST
@Path("club/{id}")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@Produces(MediaType.APPLICATION_JSON)
@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) {
return licenceService.askLicence(id, form, checkPerm).map(SimpleLicence::fromModel);
}
@DELETE
@Path("club/{id}")
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
@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) {
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}/")
public class MatchEndpoints {
@PathParam("system")
private CompetitionSystem system;
@Inject
MatchService service;
@Inject
SecurityCtx securityCtx;
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<MatchData> getById(@PathParam("id") Long id) {
return service.getById(securityCtx, system, id);
}
@GET
@Path("getAllByPoule/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<MatchData>> getAllByPoule(@PathParam("id") Long id) {
return service.getAllByPoule(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,144 +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) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.searchAdmin(limit, page - 1, search, club);
}
@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,156 +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) {
if (limit == null)
limit = 50;
if (page == null || page < 1)
page = 1;
return membreService.search(limit, page - 1, search, 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,156 +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.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;
@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 membreService.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 membreService.getLicencePdf(membreService.getByIdWithLicence(id).onItem().invoke(checkPerm));
}
}

View File

@@ -1,63 +0,0 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.PouleService;
import fr.titionfire.ffsaf.rest.data.PouleData;
import fr.titionfire.ffsaf.rest.data.PouleFullData;
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}/")
public class PouleEndpoints {
@PathParam("system")
private CompetitionSystem system;
@Inject
PouleService service;
@Inject
SecurityCtx securityCtx;
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<PouleData> getById(@PathParam("id") Long id) {
return service.getById(securityCtx, system, id);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<PouleData>> getAll() {
return service.getAll(securityCtx, system);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<PouleData> addOrUpdate(PouleData data) {
return service.addOrUpdate(securityCtx, system, data);
}
@POST
@Path("sync")
@Consumes(MediaType.APPLICATION_JSON)
public Uni<?> syncPoule(PouleFullData data) {
return service.syncPoule(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,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,19 +0,0 @@
package fr.titionfire.ffsaf.rest.client;
import fr.titionfire.ffsaf.rest.data.UniteLegaleRoot;
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.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
@ClientHeaderParam(name = "X-Client-Secret", value = "${siren-api.key}")
public interface SirenService {
@GET
@Path("/v3/unites_legales/{SIREN}")
Uni<UniteLegaleRoot> get_unite(@PathParam("SIREN") String siren);
}

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,53 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.data.model.RegisterModel;
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 uuid;
private Date date;
private CompetitionSystem system;
private Long club;
private String clubName;
private String owner;
private List<SimpleRegister> registers;
public static CompetitionData fromModel(CompetitionModel model) {
if (model == null)
return null;
return new CompetitionData(model.getId(), model.getName(), model.getUuid(), model.getDate(), model.getSystem(),
model.getClub().getId(), model.getClub().getName(), model.getOwner(), null);
}
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;
}
}

View File

@@ -1,48 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@Data
@RegisterForReflection
public class LicenceStats {
private ConcurrentHashMap<Integer, YearStats> licences = new ConcurrentHashMap<>();
private List<CategoriesStats> categories = new ArrayList<>();
@Data
@RegisterForReflection
public static class CategoriesStats {
private String name;
private int count = 0;
}
@Data
@RegisterForReflection
public static class YearStats {
private int h = 0;
private int f = 0;
private int na = 0;
private int not_valid = 0;
public void addH() {
this.h++;
}
public void addF() {
this.f++;
}
public void addNa() {
this.na++;
}
public void addNotValid() {
this.not_valid++;
}
}
}

View File

@@ -1,36 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
@RegisterForReflection
public class MatchData {
private Long id;
private Long c1_id;
private String c1_str;
private Long c2_id;
private String c2_str;
private Long poule;
private long poule_ord;
private boolean isEnd = true;
private char groupe;
private List<ScoreEmbeddable> scores;
public static MatchData fromModel(MatchModel model) {
if (model == null)
return null;
return new MatchData(model.getSystemId(),
(model.getC1_id() == null) ? null : model.getC1_id().getId(), model.getC1_str(),
(model.getC2_id() == null) ? null : model.getC2_id().getId(), model.getC2_str(),
model.getPoule().getId(), model.getPoule_ord(), model.isEnd(), model.getGroupe(),
model.getScores());
}
}

View File

@@ -1,59 +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 lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.Date;
import java.util.List;
@Data
@ToString
@NoArgsConstructor
@RegisterForReflection
public class MeData {
@Schema(description = "L'identifiant du membre.", example = "1")
private long id;
@Schema(description = "Le nom du membre.", example = "Dupont")
private String lname = "";
@Schema(description = "Le prénom du membre.", example = "Jean")
private String fname = "";
@Schema(description = "La catégorie du membre.", example = "SENIOR")
private String categorie;
@Schema(description = "Le nom du club du membre.", example = "Association sportive")
private String club;
@Schema(description = "Le genre du membre.", example = "Homme")
private String genre;
@Schema(description = "Le numéro de licence du membre.", example = "12345")
private int licence;
@Schema(description = "Le pays du membre.", example = "FR")
private String country;
@Schema(description = "La date de naissance du membre.")
private Date birth_date;
@Schema(description = "L'adresse e-mail du membre.", example = "jean.dupont@example.com")
private String email;
@Schema(description = "Le rôle du membre dans l'association.", example = "MEMBRE")
private String role;
@Schema(description = "Le grade d'arbitrage du membre.", example = "N/A")
private String grade_arbitrage;
@Schema(description = "La liste des licences du membre.")
private List<SimpleLicence> licences;
public void setMembre(MembreModel membreModel) {
this.id = membreModel.getId();
this.lname = membreModel.getLname();
this.fname = membreModel.getFname();
this.categorie = membreModel.getCategorie().getName();
this.club = membreModel.getClub() == null ? "Sans club" : membreModel.getClub().getName();
this.genre = membreModel.getGenre().str;
this.licence = membreModel.getLicence();
this.country = membreModel.getCountry();
this.birth_date = membreModel.getBirth_date();
this.email = membreModel.getEmail();
this.role = membreModel.getRole().str;
this.grade_arbitrage = membreModel.getGrade_arbitrage().str;
}
}

View File

@@ -1,23 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.PouleModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class PouleData {
private Long id;
private String name;
private Long compet;
private Integer type;
public static PouleData fromModel(PouleModel model) {
if (model == null)
return null;
return new PouleData(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 PouleFullData {
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,15 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
@Data
@RegisterForReflection
public class RegisterRequestData {
private Long licence;
private String fname;
private String lname;
private Integer weight;
private int overCategory;
}

View File

@@ -1,45 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.utils.RoleAsso;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
public class RenewAffData {
String name;
Long siret;
String rna;
String address;
int saison;
List<RenewMember> members;
@Data
@AllArgsConstructor
@RegisterForReflection
public static class RenewMember {
String lname;
String fname;
String email;
int licence;
RoleAsso role;
public RenewMember(MembreModel o) {
this.lname = o.getLname();
this.fname = o.getFname();
this.email = o.getEmail();
this.licence = o.getLicence();
this.role = o.getRole();
}
}
}

View File

@@ -1,35 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.AffiliationModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Data
@Builder
@AllArgsConstructor
@RegisterForReflection
public class SimpleAffiliation {
@Schema(description = "L'identifiant de l'affiliation.", example = "1")
private Long id;
@Schema(description = "L'identifiant du club associé à l'affiliation.", example = "123")
private Long club;
@Schema(description = "La saison de l'affiliation.", example = "2022")
private int saison;
@Schema(description = "Indique si l'affiliation est validée ou non.", example = "true")
private boolean validate;
public static SimpleAffiliation fromModel(AffiliationModel model) {
if (model == null)
return null;
return new SimpleAffiliationBuilder()
.id(model.getId())
.club(model.getClub().getId())
.saison(model.getSaison())
.validate(true)
.build();
}
}

View File

@@ -1,70 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.utils.Contact;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.HashMap;
import java.util.Map;
@Data
@Builder
@ToString
@AllArgsConstructor
@RegisterForReflection
public class SimpleClub {
@Schema(description = "L'identifiant unique du club.", example = "1")
private Long id;
@Schema(description = "Identifiant long du club (UUID)", example = "b94f3167-3f6a-449c-a73b-ec84202bf07e")
private String clubId;
@Schema(description = "Le nom du club.", example = "Association sportive")
private String name;
@Schema(description = "Le pays du club.", example = "FR")
private String country;
@Schema(description = "Les contacts du club", example = "{\"SITE\": \"www.test.com\", \"COURRIEL\": \"test@test.com\"}")
private Map<Contact, String> contact;
@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}]")
private String training_location;
@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}]")
private String training_day_time;
@Schema(description = "Contact interne du club", example = "john.doe@test.com")
private String contact_intern;
@Schema(description = "Adresse postale du club", example = "1 rue de l'exemple, 75000 Paris")
private String address;
@Schema(description = "RNA du club", example = "W123456789")
private String RNA;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
private Long SIRET;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
private Long no_affiliation;
@Schema(description = "Club international", example = "false")
private boolean international;
@Schema(description = "Une map contenant les contacts possible pout un club.")
private HashMap<String, String> contactMap = null;
public static SimpleClub fromModel(ClubModel model) {
if (model == null)
return null;
return new SimpleClubBuilder()
.id(model.getId())
.clubId(model.getClubId())
.name(model.getName())
.country(model.getCountry())
.contact(model.getContact())
.training_location(model.getTraining_location())
.training_day_time(model.getTraining_day_time())
.contact_intern(model.getContact_intern())
.RNA(model.getRNA())
.SIRET(model.getSIRET())
.no_affiliation(model.getNo_affiliation())
.international(model.isInternational())
.address(model.getAddress())
.build();
}
}

View File

@@ -1,35 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.ClubModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
public class SimpleClubList {
@Schema(description = "Identifiant du club", example = "1")
Long id;
@Schema(description = "Nom du club", example = "Club de test")
String name;
@Schema(description = "Pays du club", example = "FR")
String country;
@Schema(description = "Numéro SIRET du club", example = "12345678901234")
Long siret;
@Schema(description = "Numéro d'affiliation du club", example = "12345")
Long no_affiliation;
public static SimpleClubList fromModel(ClubModel model) {
if (model == null)
return null;
return new SimpleClubList(model.getId(), model.getName(), model.getCountry(), model.getSIRET(),
model.getNo_affiliation());
}
}

View File

@@ -1,28 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@RegisterForReflection
public class SimpleCompetData {
private long id;
private boolean show_blason;
private boolean show_flag;
private List<String> admin;
private List<String> table;
public static SimpleCompetData fromModel(SimpleCompet compet) {
if (compet == null)
return null;
return new SimpleCompetData(compet.id(), compet.show_blason(), compet.show_flag(),
new ArrayList<>(), new ArrayList<>());
}
}

View File

@@ -1,38 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Data
@Builder
@AllArgsConstructor
@RegisterForReflection
public class SimpleLicence {
@Schema(description = "ID de la licence", example = "1")
Long id;
@Schema(description = "ID du membre", example = "1")
Long membre;
@Schema(description = "Saison de la licence", example = "2024")
int saison;
@Schema(description = "Nom du médecin sur certificat médical.", example = "M. Jean")
String certificate;
@Schema(description = "Validation de la licence", example = "true")
boolean validate;
public static SimpleLicence fromModel(LicenceModel model) {
if (model == null)
return null;
return new SimpleLicenceBuilder()
.id(model.getId())
.membre(model.getMembre().getId())
.saison(model.getSaison())
.certificate(model.getCertificate())
.validate(model.isValidate())
.build();
}
}

View File

@@ -10,7 +10,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.Date;
@@ -19,33 +18,19 @@ import java.util.Date;
@AllArgsConstructor
@RegisterForReflection
public class SimpleMembre {
@Schema(description = "L'identifiant du membre.", example = "1")
private long id;
@Schema(description = "L'identifiant long du membre (userID).", example = "e81d1d35-d897-421e-8086-6c5e74d13c6e")
private String userId;
@Schema(description = "Le nom du membre.", example = "Dupont")
private String lname = "";
@Schema(description = "Le prénom du membre.", example = "Jean")
private String fname = "";
@Schema(description = "La catégorie du membre.", example = "SENIOR")
private Categorie categorie;
@Schema(description = "Le club du membre.")
private SimpleClubModel club;
@Schema(description = "Le genre du membre.", example = "H")
private Genre genre;
@Schema(description = "Le numéro de licence du membre.", example = "12345")
private Integer licence;
@Schema(description = "Le pays du membre.", example = "FR")
private int licence;
private String country;
@Schema(description = "La date de naissance du membre.")
private Date birth_date;
@Schema(description = "L'adresse e-mail du membre.", example = "jean.dupont@example.com")
private String email;
@Schema(description = "Le rôle du membre dans l'association.", example = "MEMBRE")
private RoleAsso role;
@Schema(description = "Le grade d'arbitrage du membre.", example = "N/A")
private GradeArbitrage grade_arbitrage;
@Schema(hidden = true)
private String url_photo;
public static SimpleMembre fromModel(MembreModel model) {

View File

@@ -1,40 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@RegisterForReflection
@AllArgsConstructor
public class SimpleMembreInOutData {
Integer licence;
String nom;
String prenom;
String email;
String genre;
Date birthdate;
boolean licenceCurrent;
String certif;
public static SimpleMembreInOutData fromModel(MembreModel membreModel, List<LicenceModel> lc) {
LicenceModel currentLicence = lc.stream().filter(l -> l.getMembre().getId().equals(membreModel.getId()))
.findFirst().orElse(null);
return new SimpleMembreInOutData(
membreModel.getLicence(),
membreModel.getLname(),
membreModel.getFname(),
membreModel.getEmail(),
membreModel.getGenre().str,
membreModel.getBirth_date(),
currentLicence != null,
currentLicence == null ? null : currentLicence.getCertificate()
);
}
}

View File

@@ -1,36 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
@RegisterForReflection
public class SimpleRegisterComb {
private long id;
private String fname;
private String lname;
private String categorie;
private SimpleClubModel club;
private Integer licence;
private Integer weight;
private int overCategory;
private boolean hasLicenceActive;
public static SimpleRegisterComb fromModel(RegisterModel register, List<LicenceModel> licences) {
MembreModel membreModel = register.getMembre();
return new SimpleRegisterComb(membreModel.getId(), membreModel.getFname(), membreModel.getLname(),
(register.getCategorie() == null) ? "Catégorie inconnue" : register.getCategorie().getName(),
SimpleClubModel.fromModel(register.getClub()), membreModel.getLicence(), register.getWeight(),
register.getOverCategory(),
licences.stream().anyMatch(l -> l.isValidate() && l.getSaison() == Utils.getSaison()));
}
}

View File

@@ -1,79 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.AffiliationRequestModel;
import fr.titionfire.ffsaf.utils.RoleAsso;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@RegisterForReflection
public class SimpleReqAffiliation {
@Schema(description = "Identifiant de la demande d'affiliation", example = "1")
Long id;
@Schema(description = "Identifiant du club", example = "1")
Long club;
@Schema(description = "Nom du club si club similar trouver (même siret)", example = "Association sportive")
String club_name;
@Schema(description = "Identifiant du club affilié", example = "1")
Long club_no_aff;
@Schema(description = "Nom du club demander", example = "Association sportive")
String name;
@Schema(description = "Numéro SIRET de l'association", example = "12345678901234")
long siret;
@Schema(description = "Numéro RNA de l'association", example = "W123456789")
String RNA;
@Schema(description = "Adresse de l'association", example = "1 rue de l'exemple, 75000 Paris")
String address;
@Schema(description = "Email de contact de l'association", example = "test@test.fr")
String contact;
@Schema(description = "Liste des membres pour la demande d'affiliation")
List<AffiliationMember> members;
@Schema(description = "Saison de l'affiliation", example = "2025")
int saison;
public static SimpleReqAffiliation fromModel(AffiliationRequestModel model) {
if (model == null)
return null;
return new SimpleReqAffiliation.SimpleReqAffiliationBuilder()
.id(model.getId())
.name(model.getName())
.siret(model.getSiret())
.RNA(model.getRNA())
.address(model.getAddress())
.saison(model.getSaison())
.contact(model.getContact())
.members(List.of(
new AffiliationMember(model.getM1_lname(), model.getM1_fname(), model.getM1_email(),
model.getM1_lincence(), model.getM1_role()),
new AffiliationMember(model.getM2_lname(), model.getM2_fname(), model.getM2_email(),
model.getM2_lincence(), model.getM2_role()),
new AffiliationMember(model.getM3_lname(), model.getM3_fname(), model.getM3_email(),
model.getM3_lincence(), model.getM3_role())
))
.build();
}
@Data
@AllArgsConstructor
@RegisterForReflection
public static class AffiliationMember {
@Schema(description = "Nom du membre", example = "Doe")
String lname;
@Schema(description = "Prénom du membre", example = "John")
String fname;
@Schema(description = "Email du membre", example = "john.doe@test.com")
String email;
@Schema(description = "Numéro de licence du membre", example = "12345")
int licence;
@Schema(description = "Rôle du membre", example = "MEMBRE")
RoleAsso role;
}
}

View File

@@ -1,35 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.AffiliationRequestModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Data
@Builder
@AllArgsConstructor
@RegisterForReflection
public class SimpleReqAffiliationResume {
@Schema(description = "L'identifiant de la demande d'affiliation.", example = "1")
Long id;
@Schema(description = "Le nom de l'association.", example = "Association sportive")
String name;
@Schema(description = "Le numéro SIRET de l'association.", example = "12345678901234")
long siret;
@Schema(description = "La saison de l'affiliation.", example = "2025")
int saison;
public static SimpleReqAffiliationResume fromModel(AffiliationRequestModel model) {
if (model == null)
return null;
return new SimpleReqAffiliationResume.SimpleReqAffiliationResumeBuilder()
.id(model.getId())
.name(model.getName())
.siret(model.getSiret())
.saison(model.getSaison())
.build();
}
}

View File

@@ -1,26 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.TreeModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class TreeData {
private Long id;
private Long poule;
private Integer level;
private Long match;
private TreeData left;
private TreeData right;
public static TreeData fromModel(TreeModel model) {
if (model == null)
return null;
return new TreeData(model.getId(), model.getPoule(), model.getLevel(), model.getMatch().getId(),
fromModel(model.getLeft()), fromModel(model.getRight()));
}
}

View File

@@ -1,107 +0,0 @@
package fr.titionfire.ffsaf.rest.data;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
@Data
@RegisterForReflection
public class UniteLegaleRoot {
public UniteLegale unite_legale;
@Data
@RegisterForReflection
public static class UniteLegale {
public String activite_principale;
public Object annee_categorie_entreprise;
public Object annee_effectifs;
public Object caractere_employeur;
public Object categorie_entreprise;
public String categorie_juridique;
public String date_creation;
public String date_debut;
public Date date_dernier_traitement;
public String denomination;
public Object denomination_usuelle_1;
public Object denomination_usuelle_2;
public Object denomination_usuelle_3;
public String economie_sociale_solidaire;
public Etablissement etablissement_siege;
public ArrayList<Etablissement> etablissements;
public String etat_administratif;
public String identifiant_association;
public String nic_siege;
public Object nom;
public Object nom_usage;
public int nombre_periodes;
public String nomenclature_activite_principale;
public Object prenom_1;
public Object prenom_2;
public Object prenom_3;
public Object prenom_4;
public Object prenom_usuel;
public Object pseudonyme;
public Object sexe;
public Object sigle;
public String siren;
public String societe_mission;
public String statut_diffusion;
public Object tranche_effectifs;
public Object unite_purgee;
}
@Data
@RegisterForReflection
public static class Etablissement {
private String activite_principale;
private Object activite_principale_registre_metiers;
private Object annee_effectifs;
private String caractere_employeur;
private Object code_cedex;
private Object code_cedex_2;
private String code_commune;
private Object code_commune_2;
private Object code_pays_etranger;
private Object code_pays_etranger_2;
private String code_postal;
private Object code_postal_2;
private Object complement_adresse;
private Object complement_adresse2;
private String date_creation;
private String date_debut;
private Date date_dernier_traitement;
private Object denomination_usuelle;
private Object distribution_speciale;
private Object distribution_speciale_2;
private Object enseigne_1;
private Object enseigne_2;
private Object enseigne_3;
private boolean etablissement_siege;
private String etat_administratif;
private Object indice_repetition;
private Object indice_repetition_2;
private Object libelle_cedex;
private Object libelle_cedex_2;
private String libelle_commune;
private Object libelle_commune_2;
private Object libelle_commune_etranger;
private Object libelle_commune_etranger_2;
private Object libelle_pays_etranger;
private Object libelle_pays_etranger_2;
private String libelle_voie;
private Object libelle_voie_2;
private String nic;
private int nombre_periodes;
private String nomenclature_activite_principale;
private String numero_voie;
private Object numero_voie_2;
private String siren;
private String siret;
private String statut_diffusion;
private Object tranche_effectifs;
private String type_voie;
private Object type_voie_2;
}
}

View File

@@ -5,33 +5,21 @@ import io.quarkus.security.identity.SecurityIdentity;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Data
@Builder
@RegisterForReflection
public class UserInfo {
@Schema(description = "L'identifiant de l'utilisateur.", example = "1234567890")
String id;
@Schema(description = "Le nom complet de l'utilisateur.", example = "John Doe")
String name;
@Schema(description = "Le prénom de l'utilisateur.", example = "John")
String givenName;
@Schema(description = "Le nom de famille de l'utilisateur.", example = "Doe")
String familyName;
@Schema(description = "L'adresse e-mail de l'utilisateur.", example = "jihn.doe@test.fr")
String email;
@Schema(description = "L'adresse e-mail de l'utilisateur a été vérifiée.", example = "true")
boolean emailVerified;
@Schema(description = "La date d'expiration du token d'accès.")
long expiration;
@Schema(description = "La liste des groupes de l'utilisateur.")
List<String> groups;
@Schema(description = "La liste des rôles de l'utilisateur.")
Set<String> groups;
Set<String> roles;
public static UserInfo makeUserInfo(JsonWebToken accessToken, SecurityIdentity securityIdentity) {
@@ -43,13 +31,7 @@ public class UserInfo {
builder.email(accessToken.getClaim("email"));
builder.emailVerified(accessToken.getClaim("email_verified"));
builder.expiration(accessToken.getExpirationTime());
List<String> groups = new ArrayList<>();
if (accessToken.getClaim("user_groups") instanceof Iterable<?>) {
for (Object str : (Iterable<?>) accessToken.getClaim("user_groups")) {
groups.add(str.toString().substring(1, str.toString().length() - 1));
}
}
builder.groups(groups);
builder.groups(accessToken.getGroups());
builder.roles(securityIdentity.getRoles());
return builder.build();
}

View File

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

View File

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

View File

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

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