From e0e0b3836788ac041eee14fddfca18c54c727187 Mon Sep 17 00:00:00 2001 From: veenm Date: Sun, 1 Mar 2026 21:09:36 +0100 Subject: [PATCH] V2 FOR MAH BABY --- .github/workflow/new-image.yml | 50 ++++++++++ pom.xml | 2 +- src/main/docker/Dockerfile.jvm | 97 +++++++++++++++++++ .../nl/veenm/jobfindr/domain/Vacature.java | 43 +++++++- .../jobfindr/resources/VacatureResource.java | 21 ++-- .../jobfindr/scrapers/ScraperService.java | 43 +++----- .../veenm/jobfindr/services/EmailService.java | 51 +++++----- .../jobfindr/services/VacatureService.java | 81 +++++++--------- 8 files changed, 269 insertions(+), 119 deletions(-) create mode 100644 .github/workflow/new-image.yml create mode 100644 src/main/docker/Dockerfile.jvm diff --git a/.github/workflow/new-image.yml b/.github/workflow/new-image.yml new file mode 100644 index 0000000..265c02b --- /dev/null +++ b/.github/workflow/new-image.yml @@ -0,0 +1,50 @@ +name: Build and Push Quarkus Image + +on: + push: + branches: [ "main" ] # Pas aan naar jouw hoofdbranch als dit 'master' is + +env: + # VERVANG DIT door de domeinnaam van jouw Gitea server (zonder https://) + REGISTRY: gitea.melvanveen.nl + # Dit pakt automatisch de eigenaar en naam van de repo (bijv. username/quarkus-app) + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest # Zorg dat je Gitea runner Docker ondersteunt + + steps: + - name: Code ophalen + uses: actions/checkout@v4 + + - name: Java 21 instellen + uses: actions/setup-java@v4 + with: + java-version: '21' # Pas aan naar 17 als je Java 17 gebruikt + distribution: 'temurin' + cache: 'maven' # Dit versnelt volgende builds aanzienlijk + + - name: Quarkus applicatie bouwen (.jar genereren) + run: ./mvnw clean package -DskipTests + + - name: Inloggen op Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + # Gitea genereert automatisch dit token, net als GitHub. + # Deze heeft de rechten om packages te pushen. + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker image bouwen en pushen + uses: docker/build-push-action@v5 + with: + context: . + # We gebruiken de standaard JVM Dockerfile van Quarkus + file: src/main/docker/Dockerfile.jvm + push: true + # We taggen de image met 'latest' én met de specifieke commit-hash + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 226418d..78a8579 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ UTF-8 quarkus-bom io.quarkus.platform - 3.17.6 + 3.32.1 true 3.5.0 diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..4c24c59 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,97 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/jobfindr-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/jobfindr-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/jobfindr-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.20 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/src/main/java/nl/veenm/jobfindr/domain/Vacature.java b/src/main/java/nl/veenm/jobfindr/domain/Vacature.java index 55cc709..590aec6 100644 --- a/src/main/java/nl/veenm/jobfindr/domain/Vacature.java +++ b/src/main/java/nl/veenm/jobfindr/domain/Vacature.java @@ -11,7 +11,6 @@ public class Vacature { @Id @GeneratedValue private Long id; - private String titel; private String locatie; private String url; @@ -19,6 +18,8 @@ public class Vacature { @OneToOne @JoinColumn(name = "detail_id") private VacatureDetail detail; + private String description; + private LocalDate closingDate; public VacatureDetail getDetail() { return detail; @@ -53,5 +54,45 @@ public class Vacature { public void setDatum(LocalDate datum) { this.datum = datum; } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public void setTitel(String titel) { + this.titel = titel; + } + + public void setLocatie(String locatie) { + this.locatie = locatie; + } + + public void setUrl(String url) { + this.url = url; + } + + public LocalDate getDatum() { + return datum; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public LocalDate getClosingDate() { + return closingDate; + } + + public void setClosingDate(LocalDate closingDate) { + this.closingDate = closingDate; + } } diff --git a/src/main/java/nl/veenm/jobfindr/resources/VacatureResource.java b/src/main/java/nl/veenm/jobfindr/resources/VacatureResource.java index 98997e2..6ef0137 100644 --- a/src/main/java/nl/veenm/jobfindr/resources/VacatureResource.java +++ b/src/main/java/nl/veenm/jobfindr/resources/VacatureResource.java @@ -6,7 +6,6 @@ import jakarta.ws.rs.core.MediaType; import nl.veenm.jobfindr.domain.Vacature; import nl.veenm.jobfindr.services.VacatureService; -import java.io.IOException; import java.util.List; @Path("/vacatures") @@ -18,25 +17,25 @@ public class VacatureResource { VacatureService vacatureService; @GET - public List getVacatures() throws IOException { + public List getVacatures() { return vacatureService.getServices(); } + @GET + @Path("/test") + public void testEmails(){ + vacatureService.checkAndSendNewVacatures(); + } + @GET() @Path("/all") - public List getAllVacatures() throws IOException { + public List getAllVacatures() { return vacatureService.getAllVacatures(); } - @GET - @Path("/detail") - public Vacature getVacature() throws IOException { - return vacatureService.getVacature(); - } - - @GET + @PATCH @Path("/clean") - public void cleanVacatures() throws IOException { + public void cleanVacatures() { vacatureService.cleanVacatures(); } } diff --git a/src/main/java/nl/veenm/jobfindr/scrapers/ScraperService.java b/src/main/java/nl/veenm/jobfindr/scrapers/ScraperService.java index 899fd54..de95f2a 100644 --- a/src/main/java/nl/veenm/jobfindr/scrapers/ScraperService.java +++ b/src/main/java/nl/veenm/jobfindr/scrapers/ScraperService.java @@ -1,10 +1,7 @@ package nl.veenm.jobfindr.scrapers; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; import nl.veenm.jobfindr.domain.Vacature; -import nl.veenm.jobfindr.domain.VacatureDetail; -import nl.veenm.jobfindr.repository.VacatureDetailRepository; import org.jboss.logging.Logger; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -12,8 +9,9 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; @@ -22,6 +20,8 @@ public class ScraperService { private static final Logger log = Logger.getLogger(ScraperService.class); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + public List scrapeVacatures(String url) throws IOException { // Maak verbinding met de website Document doc = Jsoup.connect(url).get(); @@ -34,7 +34,7 @@ public class ScraperService { List vacatures = new ArrayList<>(); for (Element vacatureElement : vacatureElements) { // Haal de titel en de link op - String title = vacatureElement.select("h4.small-header").text(); + String title = vacatureElement.select("div.heading-6").text(); String link = vacatureElement.select("a.vacature-target").attr("data-target").replace("/split", ""); // Zorg ervoor dat de link volledig is @@ -42,7 +42,7 @@ public class ScraperService { link = "https://www.meesterbaan.nl" + link; } - // Haal de locatie op (zoek naar het eerstvolgende div met class 'location mt-1') + assert vacatureElement.parent() != null; Element locationElement = vacatureElement.parent().selectFirst("div.location.mt-1"); String location = locationElement != null ? locationElement.text() : "Onbekende locatie"; @@ -61,34 +61,19 @@ public class ScraperService { return vacatures.stream().filter(a -> !a.getLocatie().equals("Onbekende locatie")).collect(Collectors.toList()); } - public Vacature getVacatureDetails(Vacature vacature) throws IOException { + public void getVacatureDetails(Vacature vacature) throws IOException { Document doc = Jsoup.connect(vacature.getUrl()).get(); - Elements vacatureElements = doc.select("div.list-property"); - - HashMap details = new HashMap<>(); + Elements vacatureElements = doc.select("div.rounded.gray-tag-md.px-2.py-2"); + StringBuilder sb = new StringBuilder(); vacatureElements.forEach(element -> { - String detail = element.selectFirst("label").text(); - String value = element.selectXpath("div").text(); - - details.put(detail, value); + sb.append(element.text()).append("
"); + if (element.text().startsWith("Sluitingsdatum:")) { + vacature.setClosingDate(LocalDate.parse(element.text().replace("Sluitingsdatum: ", ""), formatter)); + } }); - VacatureDetail detail = new VacatureDetail(); - - detail.setGeplaatst(details.get("Geplaatst") != null? details.get("Geplaatst") : "Onbekend"); - detail.setSluitingsdatum(details.get("Sluitingsdatum")!= null? details.get("Sluitingsdatum") : "Onbekend"); - detail.setSalaris(details.get("Salaris")!= null? details.get("Salaris") : "Onbekend"); - detail.setAantalUren(details.get("Aantal uren")!= null? details.get("Aantal uren") : "Onbekend"); - detail.setFte(details.get("FTE")!= null? details.get("FTE") : "Onbekend"); - detail.setStartdatum(details.get("Startdatum")!= null? details.get("Startdatum") : "Onbekend"); - detail.setSoortVacature(details.get("Soort vacature")!= null? details.get("Soort vacature") : "Onbekend"); - detail.setDienstverband(details.get("Dienstverband")!= null? details.get("Dienstverband") : "Onbekend"); - detail.setZijInstromers(details.get("Zij-instromers")!= null? details.get("Zij-instromers") : "Onbekend"); - - vacature.setDetail(detail); - - return vacature; + vacature.setDescription(sb.toString()); } } \ No newline at end of file diff --git a/src/main/java/nl/veenm/jobfindr/services/EmailService.java b/src/main/java/nl/veenm/jobfindr/services/EmailService.java index 9313d55..28b6a63 100644 --- a/src/main/java/nl/veenm/jobfindr/services/EmailService.java +++ b/src/main/java/nl/veenm/jobfindr/services/EmailService.java @@ -33,46 +33,39 @@ public class EmailService { emailBody.append("") .append("") .append("") .append("") .append("") - .append("

Lieve Danthe,

") - .append("

Hier zijn de nieuwste vacatures die ik heb gevonden:

") - .append("
"); + .append("
") + .append("

Lieve Danthe, ✨

") + .append("

Ik heb weer even voor je gezocht. Hier zijn de nieuwste vacatures die perfect bij jou passen:

"); for (Vacature vacature : vacatures) { emailBody.append("
") .append("

").append(vacature.getTitel()).append("

") - .append("

Locatie: ").append(vacature.getLocatie()).append("

") - .append("

Geplaatst: ").append(vacature.getDetail().getGeplaatst()).append("

") - .append("

Sluitingsdatum: ").append(vacature.getDetail().getSluitingsdatum()).append("

") - .append("

Salaris: ").append(vacature.getDetail().getSalaris()).append("

") - .append("

Aantal uren: ").append(vacature.getDetail().getAantalUren()).append("

") - .append("

FTE: ").append(vacature.getDetail().getFte()).append("

") - .append("

Startdatum: ").append(vacature.getDetail().getStartdatum()).append("

") - .append("

Soort vacature: ").append(vacature.getDetail().getSoortVacature()).append("

") - .append("

Dienstverband: ").append(vacature.getDetail().getDienstverband()).append("

") - .append("

Zij-instromers: ").append(vacature.getDetail().getZijInstromers()).append("

") - .append("

Bekijk vacature

") + .append("Korte beschrijving") + .append("

").append(vacature.getDescription()).append("

") + .append("") .append("
"); } - emailBody.append("
") - .append("
") - .append("Met vriendelijke groet,
") - .append("Het vacatureteam
") - .append("(a.k.a je vriendje)") + emailBody.append("
") + .append("Met heel veel liefde gebouwd,
") + .append("Het vacatureteam
") + .append("(a.k.a. je vriendje ❤️)") .append("
") + .append("
") // Einde email-container .append("") .append(""); diff --git a/src/main/java/nl/veenm/jobfindr/services/VacatureService.java b/src/main/java/nl/veenm/jobfindr/services/VacatureService.java index 8960ab3..836ab23 100644 --- a/src/main/java/nl/veenm/jobfindr/services/VacatureService.java +++ b/src/main/java/nl/veenm/jobfindr/services/VacatureService.java @@ -13,8 +13,6 @@ import org.jboss.logging.Logger; import java.io.IOException; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; @@ -39,26 +37,19 @@ public class VacatureService { private final String className = this.getClass().getSimpleName(); - public List getServices() throws IOException { + public List getServices() { eventService.logInfo(VacatureService.class.getName(), "getServices", "getServices aangeroepen"); - List vacatures = new ArrayList<>(); - vacatures.addAll(scraperService.scrapeVacatures("https://www.meesterbaan.nl/vacatures/50-km?trefwoord=duits&locatie=Apeldoorn")); - vacatures.addAll(scraperService.scrapeVacatures("https://www.meesterbaan.nl/vacatures/50-km?trefwoord=frans&locatie=Apeldoorn")); + List vacatures = getVacatures(); emailService.stuurVacatureEmail("vanveenmel11@gmail.com", vacatures); return vacatures; } - public void checkAndSendNewVacatures() throws IOException { + @Transactional + public void checkAndSendNewVacatures() { logger.info("Checking for new vacatures"); eventService.logInfo(className, "checkAndSendNewVacatures", "Checking for new vacatures"); LocalDate today = LocalDate.now(); - - // Scrape de nieuwste vacatures - String url = "https://www.meesterbaan.nl/vacatures/50-km?trefwoord=duits&locatie=Apeldoorn"; - List todayVacatures = scraperService.scrapeVacatures(url); - - url = "https://www.meesterbaan.nl/vacatures/50-km?trefwoord=frans&locatie=Apeldoorn"; - todayVacatures.addAll(scraperService.scrapeVacatures(url)); + List todayVacatures = getVacatures(); todayVacatures.forEach(todayVacature -> { try { @@ -71,25 +62,18 @@ public class VacatureService { // Haal de vacatures van gisteren op List yesterdayVacatures = vacatureRepository.findAll().stream().toList(); - System.out.println(todayVacatures.size()); - System.out.println(yesterdayVacatures.size()); - - todayVacatures.removeIf(a -> yesterdayVacatures.stream().anyMatch(b -> b.getUrl().equals(a.getUrl()))); - System.out.println(todayVacatures.size()); - // Alleen nieuwe vacatures opslaan en versturen if (!todayVacatures.isEmpty()) { // Sla de nieuwe vacatures op todayVacatures.forEach(vacature -> { vacature.setDatum(today); - vacatureDetailRepository.persist(vacature.getDetail()); vacatureRepository.persist(vacature); }); // Verstuur een e-mail - emailService.stuurVacatureEmail("danthefranken@gmail.com", todayVacatures); +// emailService.stuurVacatureEmail("danthefranken@gmail.com", todayVacatures); emailService.stuurVacatureEmail("vanveenmel11@gmail.com", todayVacatures); logger.info("Nieuwe vacatures verstuurd en opgeslagen."); eventService.logSucces(className, "checkAndSendNewVacatures", "Nieuwe vacatures verstuurd en opgeslagen."); @@ -99,10 +83,28 @@ public class VacatureService { } } + private List getVacatures() { + List urls = List.of( + "https://www.meesterbaan.nl/vacatures/plaats/apeldoorn/35-km?trefwoord=geschiedenis", + "https://www.meesterbaan.nl/vacatures/plaats/apeldoorn/35-km?trefwoord=Docent%20Geschiedenis" + ); + List todayVacatures = new ArrayList<>(); + + // Scrape de nieuwste vacatures + urls.forEach(url -> { + try { + todayVacatures.addAll(scraperService.scrapeVacatures(url)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return todayVacatures; + } + @Scheduled(cron = "0 0 9 * * ?") @Scheduled(cron = "0 0 17 * * ?") @Transactional - void dagelijksControleerEnVerstuur() throws IOException { + void dagelijksControleerEnVerstuur() { checkAndSendNewVacatures(); } @@ -111,41 +113,24 @@ public class VacatureService { return vacatureRepository.listAll(); } - public Vacature getVacature() throws IOException { - return scraperService.getVacatureDetails(new Vacature()); - } - @Scheduled(cron = "0 0 0 * * ?") @Transactional public void cleanVacatures() { logger.info("Cleaning vacatures"); eventService.logInfo(className, "cleanVacatures", "Cleaning vacatures"); getAllVacatures().forEach(vacature -> { - var date = convertStringToLocalDate(vacature.getDetail().getSluitingsdatum()); + var date = vacature.getClosingDate(); var today = LocalDate.now(); - if (date != null) { - if (date.isBefore(today)) { - logger.info("Deleting vacature " + vacature.getTitel()); - eventService.logInfo(className, "cleanVacatures", "Deleting vacature " + vacature.getTitel()); - vacatureDetailRepository.delete(vacature.getDetail()); - vacatureRepository.delete(vacature); - } + if (date.isBefore(today)) { + logger.info("Deleting vacature " + vacature.getTitel()); + eventService.logInfo(className, "cleanVacatures", "Deleting vacature " + vacature.getTitel()); + vacatureDetailRepository.delete(vacature.getDetail()); + vacatureRepository.delete(vacature); } - eventService.logSucces(className, "cleanVacatures", "Cleaning vacatures done"); }); + logger.info("Cleaning vacatures done"); + eventService.logSucces(className, "cleanVacatures", "Cleaning vacatures done"); } - - - public static LocalDate convertStringToLocalDate(String dateString) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); - try { - return LocalDate.parse(dateString, formatter); - } catch (DateTimeParseException e) { - logger.error("Ongeldige datumstring: " + dateString); - return null; // Of gooi een aangepaste exception als dat gewenst is - } - } - }