diff --git a/pom.xml b/pom.xml index 413b042..609c526 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,12 @@ io.quarkus quarkus-cache + + + com.github.librepdf + openpdf + 2.0.3 + diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java index 6b6b4c4..672f059 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/MembreService.java @@ -1,6 +1,12 @@ package fr.titionfire.ffsaf.domain.service; +import com.lowagie.text.*; +import com.lowagie.text.pdf.BaseFont; +import com.lowagie.text.pdf.PdfPCell; +import com.lowagie.text.pdf.PdfPTable; +import com.lowagie.text.pdf.PdfWriter; import fr.titionfire.ffsaf.data.model.ClubModel; +import fr.titionfire.ffsaf.data.model.LicenceModel; import fr.titionfire.ffsaf.data.model.MembreModel; import fr.titionfire.ffsaf.data.repository.ClubRepository; import fr.titionfire.ffsaf.data.repository.CombRepository; @@ -13,6 +19,7 @@ import fr.titionfire.ffsaf.rest.data.SimpleLicence; import fr.titionfire.ffsaf.rest.data.SimpleMembre; 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.ClubMemberForm; import fr.titionfire.ffsaf.rest.from.FullMemberForm; import fr.titionfire.ffsaf.utils.*; @@ -26,10 +33,17 @@ 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.nio.file.Files; +import java.text.SimpleDateFormat; import java.util.List; +import java.util.Objects; @WithSession @@ -111,6 +125,11 @@ public class MembreService { return repository.findById(id); } + public Uni getByIdWithLicence(long id) { + return repository.findById(id) + .call(m -> Mutiny.fetch(m.getLicences())); + } + public Uni getByLicence(long licence) { return repository.find("licence = ?1", licence).firstResult(); } @@ -276,4 +295,204 @@ public class MembreService { .invoke(meData::setLicences) .map(__ -> meData); } + + public Uni getLicencePdf(String subject) { + return getLicencePdf(repository.find("userId = ?1", subject).firstResult() + .call(m -> Mutiny.fetch(m.getLicences()))); + } + + public Uni getLicencePdf(Uni 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")); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + make_pdf(m, out, licence); + + byte[] buff = out.toByteArray(); + 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 void make_pdf(MembreModel m, ByteArrayOutputStream out, LicenceModel licence) throws IOException { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Document document = new Document(); + PdfWriter.getInstance(document, out); + document.open(); + + document.addCreator("FFSAF"); + document.addTitle( + "Attestation d'adhésion " + Utils.getSaison() + "-" + (Utils.getSaison() + 1) + " de " + m.getLname() + " " + m.getFname()); + document.addCreationDate(); + document.addProducer("https://www.ffsaf.fr"); + + InputStream fontStream = MembreService.class.getClassLoader().getResourceAsStream("asset/DMSans-Regular.ttf"); + if (fontStream == null) { + throw new IOException("Font file not found"); + } + BaseFont customFont = BaseFont.createFont("asset/DMSans-Regular.ttf", BaseFont.WINANSI, BaseFont.EMBEDDED, true, + null, fontStream.readAllBytes()); + + // Adding font + Font headerFont = new Font(customFont, 26, Font.BOLD); + Font subHeaderFont = new Font(customFont, 16, Font.BOLD); + Font bigFont = new Font(customFont, 18, Font.BOLD); + Font bodyFont = new Font(customFont, 15, Font.BOLD); + Font smallFont = new Font(customFont, 10, Font.NORMAL); + + // Creating the main table + PdfPTable mainTable = new PdfPTable(2); + mainTable.setWidthPercentage(100); + mainTable.setSpacingBefore(20f); + mainTable.setSpacingAfter(0f); + mainTable.setWidths(new float[]{120, 300}); + mainTable.getDefaultCell().setBorder(PdfPCell.NO_BORDER); + + // Adding logo + Image logo = Image.getInstance( + Objects.requireNonNull( + getClass().getClassLoader().getResource("asset/FFSSAF-bord-blanc-fond-transparent.png"))); + logo.scaleToFit(120, 120); + PdfPCell logoCell = new PdfPCell(logo); + logoCell.setHorizontalAlignment(Element.ALIGN_CENTER); + logoCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + logoCell.setPadding(0); + logoCell.setRowspan(1); + logoCell.setBorder(PdfPCell.NO_BORDER); + mainTable.addCell(logoCell); + + // Adding header + PdfPCell headerCell = new PdfPCell(new Phrase("FEDERATION FRANCE\nSOFT ARMORED FIGHTING", headerFont)); + headerCell.setHorizontalAlignment(Element.ALIGN_CENTER); + headerCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + headerCell.setBorder(PdfPCell.NO_BORDER); + mainTable.addCell(headerCell); + + document.add(mainTable); + + Paragraph addr = new Paragraph("5 place de la Barreyre\n63320 Champeix", subHeaderFont); + addr.setAlignment(Element.ALIGN_CENTER); + addr.setSpacingAfter(2f); + document.add(addr); + + Paragraph association = new Paragraph("Association loi 1901 W633001595\nSIRET 829 458 355 00015", smallFont); + association.setAlignment(Element.ALIGN_CENTER); + document.add(association); + + // Adding spacing + document.add(new Paragraph("\n\n")); + + // Adding attestation + PdfPTable attestationTable = new PdfPTable(1); + attestationTable.setWidthPercentage(60); + PdfPCell attestationCell = new PdfPCell( + new Phrase("ATTESTATION D'ADHESION\nSaison " + Utils.getSaison() + "-" + (Utils.getSaison() + 1), + bigFont)); + attestationCell.setHorizontalAlignment(Element.ALIGN_CENTER); + attestationCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + attestationCell.setPadding(20f); + attestationTable.addCell(attestationCell); + document.add(attestationTable); + + // Adding spacing + document.add(new Paragraph("\n\n")); + + // Adding member details table + PdfPTable memberTable = new PdfPTable(2); + memberTable.setWidthPercentage(100); + memberTable.setWidths(new float[]{130, 300}); + memberTable.getDefaultCell().setBorder(PdfPCell.NO_BORDER); + + // Adding member photo + Image memberPhoto; + 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]; + memberPhoto = Image.getInstance(Files.readAllBytes(file.toPath())); + } else { + memberPhoto = Image.getInstance( + Objects.requireNonNull(getClass().getClassLoader().getResource("asset/blank-profile-picture.png"))); + } + memberPhoto.scaleToFit(120, 150); + PdfPCell photoCell = new PdfPCell(memberPhoto); + photoCell.setHorizontalAlignment(Element.ALIGN_CENTER); + photoCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + photoCell.setRowspan(5); + photoCell.setBorder(PdfPCell.NO_BORDER); + memberTable.addCell(photoCell); + + // Adding member details + memberTable.addCell(new Phrase("NOM : " + m.getLname().toUpperCase(), bodyFont)); + memberTable.addCell(new Phrase("Prénom : " + m.getFname(), bodyFont)); + memberTable.addCell(new Phrase("Licence n° : " + m.getLicence(), bodyFont)); + memberTable.addCell(new Phrase("Certificat médical par Dr " + licence.getCertificate(), bodyFont)); + memberTable.addCell(new Phrase("")); // Empty cell for spacing + + document.add(memberTable); + + // Adding spacing + document.add(new Paragraph("\n")); + + Paragraph memberClub = new Paragraph("CLUB : " + m.getClub().getName().toUpperCase(), bodyFont); + document.add(memberClub); + + Paragraph memberClubNumber = new Paragraph("N° club : " + m.getClub().getNo_affiliation(), bodyFont); + document.add(memberClubNumber); + + // Adding spacing + document.add(new Paragraph("\n")); + + Paragraph memberBirthdate = new Paragraph( + "Date de naissance : " + ((m.getBirth_date() == null) ? "--" : sdf.format(m.getBirth_date())), + bodyFont); + document.add(memberBirthdate); + + Paragraph memberGender = new Paragraph("Sexe : " + m.getGenre().str, bodyFont); + document.add(memberGender); + + Paragraph memberAgeCategory = new Paragraph("Catégorie d'âge : " + m.getCategorie().getName(), bodyFont); + document.add(memberAgeCategory); + + // Adding spacing + document.add(new Paragraph("\n\n")); + + // Adding attestation text + PdfPTable textTable = new PdfPTable(1); + textTable.setWidthPercentage(100); + PdfPCell textCell = new PdfPCell(new Phrase( + """ + Ce document atteste que l’adhérent + - est valablement enregistré auprès de la FFSAF, + - est assuré dans sa pratique du Béhourd Léger et du Battle Arc en entraînement et en compétition. + + Il peut donc s’inscrire à tout tournoi organisé sous l’égide de la FFSAF s’il remplit les éventuelles + conditions de qualification. + Il peut participer à tout entraînement dans un club affilié si celui ci autorise les visiteurs.""", + smallFont)); + textCell.setHorizontalAlignment(Element.ALIGN_LEFT); + textCell.setVerticalAlignment(Element.ALIGN_MIDDLE); + textCell.setBorder(PdfPCell.NO_BORDER); + textTable.addCell(textCell); + document.add(textTable); + + // Close the document + document.close(); + } } diff --git a/src/main/java/fr/titionfire/ffsaf/net2/packet/RFile.java b/src/main/java/fr/titionfire/ffsaf/net2/packet/RFile.java deleted file mode 100644 index 2820eb1..0000000 --- a/src/main/java/fr/titionfire/ffsaf/net2/packet/RFile.java +++ /dev/null @@ -1,42 +0,0 @@ -package fr.titionfire.ffsaf.net2.packet; - -import fr.titionfire.ffsaf.ws.FileSocket; -import jakarta.enterprise.context.ApplicationScoped; -import org.jboss.logging.Logger; - -import java.util.HashMap; -import java.util.UUID; - -@ApplicationScoped -public class RFile { - private static final Logger LOGGER = Logger.getLogger(RFile.class); - - final IAction requestSend = (client_Thread, message) -> { - try { - switch (message.data().get("type").asText()) { - case "match": - String code = UUID.randomUUID() + "-" + UUID.randomUUID(); - - FileSocket.FileRecv fileRecv = new FileSocket.FileRecv(null, message.data().get("name").asText(), null, null, - System.currentTimeMillis()); - FileSocket.sessions.put(code, fileRecv); - - client_Thread.sendRepTo(code, message); - break; - default: - client_Thread.sendErrTo("", message); - break; - - } - } catch (Throwable e) { - LOGGER.error(e.getMessage(), e); - client_Thread.sendErrTo(e.getMessage(), message); - } - }; - - public static void register(HashMap iMap) { - RFile rFile = new RFile(); - - iMap.put("requestSend", rFile.requestSend); - } -} diff --git a/src/main/java/fr/titionfire/ffsaf/net2/packet/RegisterAction.java b/src/main/java/fr/titionfire/ffsaf/net2/packet/RegisterAction.java index 3078ab9..bc7fe66 100644 --- a/src/main/java/fr/titionfire/ffsaf/net2/packet/RegisterAction.java +++ b/src/main/java/fr/titionfire/ffsaf/net2/packet/RegisterAction.java @@ -9,6 +9,5 @@ public class RegisterAction { RComb.register(iMap); RClub.register(iMap); - RFile.register(iMap); } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java index c281ea1..46ef260 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/MembreEndpoints.java @@ -85,12 +85,29 @@ public class MembreEndpoints { "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 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é, y compris le club et les licences") + @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 getMeLicence() { + return membreService.getLicencePdf(securityCtx.getSubject()); + } + @GET @Path("{id}/photo") @RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"}) @@ -104,4 +121,18 @@ public class MembreEndpoints { public Uni getPhoto(@PathParam("id") long id) throws URISyntaxException { return Utils.getMediaFile(id, media, "ppMembre", membreService.getById(id).onItem().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 getLicencePDF(@PathParam("id") long id) { + return membreService.getLicencePdf(membreService.getByIdWithLicence(id).onItem().invoke(checkPerm)); + } } diff --git a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java index 360e492..f93aa36 100644 --- a/src/main/java/fr/titionfire/ffsaf/utils/Utils.java +++ b/src/main/java/fr/titionfire/ffsaf/utils/Utils.java @@ -12,7 +12,6 @@ import net.sf.jmimemagic.MagicParseException; import org.jboss.logging.Logger; import java.io.*; -import java.net.URI; import java.net.URISyntaxException; import java.net.URLConnection; import java.nio.file.Files; @@ -95,7 +94,7 @@ public class Utils { if (!dirFile.mkdirs()) throw new IOException("Fail to create directory " + dir); - FilenameFilter filter = (directory, filename) -> filename.startsWith(id +"."); + FilenameFilter filter = (directory, filename) -> filename.startsWith(id + "."); File[] files = dirFile.listFiles(filter); if (files != null) { for (File file : files) { @@ -134,22 +133,45 @@ public class Utils { return null; }); - URI uri = new URI("https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-chat/ava2.webp"); + Future future2 = CompletableFuture.supplyAsync(() -> { + try (InputStream st = Utils.class.getClassLoader().getResourceAsStream("asset/blank-profile-picture.png")) { + if (st == null) + return null; + return st.readAllBytes(); + } catch (IOException ignored) { + } + return null; + }); return uniBase.chain(__ -> Uni.createFrom().future(future) - .map(filePair -> { - if (filePair == null) - return Response.temporaryRedirect(uri).build(); + .chain(filePair -> { + if (filePair == null) { + return Uni.createFrom().future(future2).map(data -> { + if (data == null) + return Response.noContent().build(); - String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName()); + String mimeType = "image/apng"; + Response.ResponseBuilder resp = Response.ok(data); + resp.type(MediaType.APPLICATION_OCTET_STREAM); + resp.header(HttpHeaders.CONTENT_LENGTH, data.length); + resp.header(HttpHeaders.CONTENT_TYPE, mimeType); + resp.header(HttpHeaders.CONTENT_DISPOSITION, + "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\"")); + return resp.build(); + }); + } else { + return Uni.createFrom().item(() -> { + 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; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\"")); - return resp.build(); + 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; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\"")); + return resp.build(); + }); + } })); } diff --git a/src/main/java/fr/titionfire/ffsaf/ws/FileSocket.java b/src/main/java/fr/titionfire/ffsaf/ws/FileSocket.java deleted file mode 100644 index 283a09b..0000000 --- a/src/main/java/fr/titionfire/ffsaf/ws/FileSocket.java +++ /dev/null @@ -1,165 +0,0 @@ -package fr.titionfire.ffsaf.ws; - -import io.quarkus.runtime.annotations.RegisterForReflection; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.websocket.*; -import jakarta.websocket.server.PathParam; -import jakarta.websocket.server.ServerEndpoint; -import lombok.AllArgsConstructor; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jboss.logging.Logger; - -import java.io.*; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@ServerEndpoint("/api/ws/file/{code}") -@ApplicationScoped -public class FileSocket { - private static final Logger logger = Logger.getLogger(FileSocket.class); - public static Map sessions = new ConcurrentHashMap<>(); - - @ConfigProperty(name = "upload_dir") - String media; - - /*@Scheduled(every = "10s") - void increment() { - sessions.forEach((key, value) -> { - if (System.currentTimeMillis() - value.time > 60000) { - closeAndDelete(value); - if (value.session != null && value.session.isOpen()) { - try { - value.session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "Timeout")); - } catch (IOException e) { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - logger.error(errors.toString()); - } - } - sessions.remove(key); - } - }); - }*/ - - @OnOpen - public void onOpen(Session session, @PathParam("code") String code) { - try { - if (sessions.containsKey(code)) { - FileRecv fileRecv = sessions.get(code); - fileRecv.session = session; - fileRecv.file = new File(media + "-ext", "record/" + fileRecv.name); - fileRecv.fos = new FileOutputStream(fileRecv.file, false); - logger.info("Start reception of file: " + fileRecv.file.getAbsolutePath()); - } else { - session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "File not found")); - } - } catch (IOException e) { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - logger.error(errors.toString()); - } - } - - @OnClose - public void onClose(Session session, @PathParam("code") String code) { - if (sessions.containsKey(code)) { - FileRecv fileRecv = sessions.get(code); - if (fileRecv.fos != null) { - try { - fileRecv.fos.close(); - } catch (IOException e) { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - logger.error(errors.toString()); - } - } - logger.info("File received: " + fileRecv.file.getAbsolutePath()); - sessions.remove(code); - } - } - - @OnError - public void onError(Session session, @PathParam("code") String code, Throwable throwable) { - if (sessions.containsKey(code)) { - closeAndDelete(sessions.get(code)); - sessions.remove(code); - } - logger.error("Error on file reception: " + throwable.getMessage()); - } - - - @OnMessage - public void onMessage(String message, @PathParam("code") String code) { - if (message.equals("cancel")) { - if (sessions.containsKey(code)) { - closeAndDelete(sessions.get(code)); - sessions.remove(code); - } - logger.error("Error file " + code + " are cancel by the client"); - } - } - - private void closeAndDelete(FileRecv fileRecv) { - if (fileRecv.fos != null) { - try { - fileRecv.fos.close(); - } catch (IOException e) { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - logger.error(errors.toString()); - } - } - if (fileRecv.file.exists()) { - //noinspection ResultOfMethodCallIgnored - fileRecv.file.delete(); - } - } - - @OnMessage - public void onMessage(byte[] data, @PathParam("code") String code) { - int length = (data[1] << 7) | data[2]; - - byte check_sum = 0; - for (int j = 3; j < length + 3; j++) { - check_sum = (byte) (check_sum ^ data[j]); - } - // System.out.println(length + " - " + data[1] + " - " + data[0] + " - " + check_sum); - - if (sessions.containsKey(code)) { - FileRecv fileRecv = sessions.get(code); - - if (check_sum != data[0]) { - fileRecv.session.getAsyncRemote().sendText("Error: Checksum error", result -> { - if (result.getException() != null) { - logger.error("Unable to send message: " + result.getException()); - } - }); - return; - } - - try { - fileRecv.fos.write(data, 3, length); - } catch (IOException e) { - StringWriter errors = new StringWriter(); - e.printStackTrace(new PrintWriter(errors)); - logger.error(errors.toString()); - } - - fileRecv.session.getAsyncRemote().sendText("ok", result -> { - if (result.getException() != null) { - logger.error("Unable to send message: " + result.getException()); - } - }); - } - } - - @AllArgsConstructor - @RegisterForReflection - public static class FileRecv { - Session session; - String name; - File file; - FileOutputStream fos; - long time; - } -} diff --git a/src/main/resources/asset/DMSans-Regular.ttf b/src/main/resources/asset/DMSans-Regular.ttf new file mode 100644 index 0000000..07266ae Binary files /dev/null and b/src/main/resources/asset/DMSans-Regular.ttf differ diff --git a/src/main/resources/asset/FFSSAF-bord-blanc-fond-transparent.png b/src/main/resources/asset/FFSSAF-bord-blanc-fond-transparent.png new file mode 100644 index 0000000..e4d6e6d Binary files /dev/null and b/src/main/resources/asset/FFSSAF-bord-blanc-fond-transparent.png differ diff --git a/src/main/resources/asset/blank-profile-picture.png b/src/main/resources/asset/blank-profile-picture.png new file mode 100644 index 0000000..9e34293 Binary files /dev/null and b/src/main/resources/asset/blank-profile-picture.png differ diff --git a/src/main/webapp/public/blank-profile-picture.png b/src/main/webapp/public/blank-profile-picture.png new file mode 100644 index 0000000..9e34293 Binary files /dev/null and b/src/main/webapp/public/blank-profile-picture.png differ diff --git a/src/main/webapp/src/pages/MePage.jsx b/src/main/webapp/src/pages/MePage.jsx index edaa3fa..3ae80a2 100644 --- a/src/main/webapp/src/pages/MePage.jsx +++ b/src/main/webapp/src/pages/MePage.jsx @@ -4,7 +4,7 @@ import {useFetch} from "../hooks/useFetch.js"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { faCalendarDay, - faEnvelope, faFlag, + faEnvelope, faFilePdf, faFlag, faInfoCircle, faMars, faMarsAndVenus, @@ -77,6 +77,13 @@ function PhotoCard({data}) { alt="avatar" className="rounded-circle img-fluid" style={{object_fit: 'contain'}}/> + + + + ; } diff --git a/src/main/webapp/src/pages/admin/member/MemberPage.jsx b/src/main/webapp/src/pages/admin/member/MemberPage.jsx index dc02918..7cf7cad 100644 --- a/src/main/webapp/src/pages/admin/member/MemberPage.jsx +++ b/src/main/webapp/src/pages/admin/member/MemberPage.jsx @@ -9,6 +9,8 @@ import {LicenceCard} from "./LicenceCard.jsx"; import {toast} from "react-toastify"; import {apiAxios, errFormater} from "../../../utils/Tools.js"; import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faFilePdf} from "@fortawesome/free-solid-svg-icons"; const vite_url = import.meta.env.VITE_URL; @@ -83,6 +85,12 @@ function PhotoCard({data}) { alt="avatar" className="rounded-circle img-fluid" style={{object_fit: 'contain'}}/> + + + ; } diff --git a/src/main/webapp/src/pages/club/member/MemberPage.jsx b/src/main/webapp/src/pages/club/member/MemberPage.jsx index 1d44a65..66a9709 100644 --- a/src/main/webapp/src/pages/club/member/MemberPage.jsx +++ b/src/main/webapp/src/pages/club/member/MemberPage.jsx @@ -8,6 +8,8 @@ import {LicenceCard} from "./LicenceCard.jsx"; import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx"; import {apiAxios, errFormater} from "../../../utils/Tools.js"; import {toast} from "react-toastify"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faFilePdf} from "@fortawesome/free-solid-svg-icons"; const vite_url = import.meta.env.VITE_URL; @@ -81,6 +83,12 @@ function PhotoCard({data}) { alt="avatar" className="rounded-circle img-fluid" style={{object_fit: 'contain'}}/> + + + ; }