feat: add affiliation pdf generator

This commit is contained in:
2025-07-07 13:16:42 +02:00
parent 280a8fa3f3
commit 34be19fbed
16 changed files with 429 additions and 78 deletions

View File

@@ -20,11 +20,18 @@ 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

View File

@@ -31,10 +31,16 @@ 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;
@@ -42,6 +48,7 @@ import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
@WithSession
@ApplicationScoped
public class ClubService {
private static final Logger LOGGER = Logger.getLogger(ClubService.class);
@Inject
ClubRepository repository;
@@ -61,6 +68,12 @@ public class ClubService {
@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)));
@@ -333,4 +346,120 @@ public class ClubService {
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() - 1))
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

@@ -33,12 +33,15 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;
import java.io.*;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static fr.titionfire.ffsaf.domain.service.ClubService.getPdf;
@WithSession
@ApplicationScoped
@@ -506,6 +509,7 @@ public class MembreService {
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);
@@ -526,50 +530,6 @@ public class MembreService {
cmd.add("/dev/null");
}
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;
}
return getPdf(cmd, uuid, LOGGER);
}
}

View File

@@ -207,6 +207,21 @@ public class ClubEndpoints {
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"})
@@ -241,13 +256,28 @@ public class ClubEndpoints {
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) {
@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)));
}