All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 9m50s
fix: pdf_gen
466 lines
21 KiB
Java
466 lines
21 KiB
Java
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;
|
|
|
|
@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)));
|
|
}
|
|
|
|
public Collection<SimpleClubModel> findAllClub() throws Throwable {
|
|
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(
|
|
() -> repository.findAll().list()
|
|
.map(clubModels -> clubModels.stream().map(SimpleClubModel::fromModel).toList())));
|
|
}
|
|
|
|
public Uni<List<ClubModel>> getAll() {
|
|
return repository.listAll();
|
|
}
|
|
|
|
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());
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|