initial commit

This commit is contained in:
2025-03-04 21:21:35 +01:00
commit 882bc0f26b
34 changed files with 2442 additions and 0 deletions

View File

@@ -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/paypoint-backend-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/paypoint-backend-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/paypoint-backend-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" ]

View File

@@ -0,0 +1,93 @@
####
# 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 -Dquarkus.package.jar.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/paypoint-backend-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/paypoint-backend-legacy-jar
#
# 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/paypoint-backend-legacy-jar
#
# 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'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
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" ]

View File

@@ -0,0 +1,27 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/paypoint-backend .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/paypoint-backend
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.10
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@@ -0,0 +1,30 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/paypoint-backend .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/paypoint-backend
#
###
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@@ -0,0 +1,16 @@
package nl.veenm;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class ExampleResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST";
}
}

View File

@@ -0,0 +1,29 @@
package nl.veenm;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
/**
* Example JPA entity defined as a Panache Entity.
* An ID field of Long type is provided, if you want to define your own ID field extends <code>PanacheEntityBase</code> instead.
* <p>
* This uses the active record pattern, you can also use the repository pattern instead:
* .
* <p>
* Usage (more example on the documentation)
* <p>
* {@code
* public void doSomething() {
* MyEntity entity1 = new MyEntity();
* entity1.field = "field-1";
* entity1.persist();
* <p>
* List<MyEntity> entities = MyEntity.listAll();
* }
* }
*/
@Entity
public class MyEntity extends PanacheEntity {
public String field;
}

View File

@@ -0,0 +1,87 @@
package nl.veenm.paypoint.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class AppUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String password; // Versleuteld wachtwoord
private String role;
private String firstName;
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "AppUser{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}

View File

@@ -0,0 +1,135 @@
package nl.veenm.paypoint.domain;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
public class Appointment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String description;
private LocalDateTime startDate;
private int startHour;
private int startMinute;
private int endHour;
private int endMinute;
private int durationInMinutes;
@ManyToOne
private Customer customer;
public Appointment(Long id, String title, String description, LocalDateTime startDate, int startHour, int startMinute, int endHour, int endMinute, int durationInMinutes) {
this.id = id;
this.title = title;
this.description = description;
this.startDate = startDate;
this.startHour = startHour;
this.startMinute = startMinute;
this.endHour = endHour;
this.endMinute = endMinute;
this.durationInMinutes = durationInMinutes;
}
public Appointment() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public LocalDateTime getStartDate() {
return startDate;
}
public void setStartDate(LocalDateTime startDate) {
this.startDate = startDate;
}
public int getStartHour() {
return startHour;
}
public void setStartHour(int startHour) {
this.startHour = startHour;
}
public int getStartMinute() {
return startMinute;
}
public void setStartMinute(int startMinute) {
this.startMinute = startMinute;
}
public int getEndHour() {
return endHour;
}
public void setEndHour(int endHour) {
this.endHour = endHour;
}
public int getEndMinute() {
return endMinute;
}
public void setEndMinute(int endMinute) {
this.endMinute = endMinute;
}
public int getDurationInMinutes() {
return durationInMinutes;
}
public void setDurationInMinutes(int durationInMinutes) {
this.durationInMinutes = durationInMinutes;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "Appointment{" +
"id=" + id +
", title='" + title + '\'' +
", description='" + description + '\'' +
", date=" + startDate +
", startHour=" + startHour +
", startMinute=" + startMinute +
", endHour=" + endHour +
", endMinute=" + endMinute +
", durationInMinutes=" + durationInMinutes +
", customer=" + customer +
'}';
}
}

View File

@@ -0,0 +1,22 @@
package nl.veenm.paypoint.domain;
public class AuthDTO {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,58 @@
package nl.veenm.paypoint.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
public Customer(Long id, String firstName, String lastName, String email) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Customer() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@@ -0,0 +1,40 @@
package nl.veenm.paypoint.domain;
public class UserDTO {
String username;
String fullName;
String email;
String token;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@@ -0,0 +1,26 @@
package nl.veenm.paypoint.helper;
import java.time.LocalDate;
public class EmailHelper {
public static String getIcs(String dtStamp, LocalDate date, String formattedTime, String formattedEndTime, String location) {
String formatted = """
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//PayPoint//Afspraak//NL
BEGIN:VEVENT
ORGANIZER;CN=Hairstyling By Daan:mailto:danielle@hairstylingbydaan.nl
UID:12345
DTSTAMP:%s
DTSTART:%sT%s00
DTEND:%sT%s00
SUMMARY:Afspraak bij Hairstyling By Daan
LOCATION:%s
DESCRIPTION:Bevestiging van uw afspraak bij Hairstyling By Daan
END:VEVENT
END:VCALENDAR
""".formatted(dtStamp, date.toString().replace("-", ""), formattedTime.replace(":", ""), date.toString().replace("-", ""), formattedEndTime.replace(":", ""), location.replace("<br>", " "));
System.out.println(formatted);
return formatted;
}
}

View File

@@ -0,0 +1,20 @@
package nl.veenm.paypoint.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.Appointment;
import java.time.LocalDateTime;
import java.util.Optional;
@ApplicationScoped
public class AppointmentRepository implements PanacheRepository<Appointment> {
public Optional<Appointment> findMostRecentByUserId(Long userId) {
LocalDateTime now = LocalDateTime.now().minusDays(1);
return find("customer = ?1 AND startDate >= ?2 ORDER BY startDate ASC",
userId, now)
.firstResultOptional();
}
}

View File

@@ -0,0 +1,10 @@
package nl.veenm.paypoint.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.Customer;
@ApplicationScoped
public class CustomerRepository implements PanacheRepository<Customer> {
}

View File

@@ -0,0 +1,14 @@
package nl.veenm.paypoint.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.AppUser;
@ApplicationScoped
public class UserRepository implements PanacheRepository<AppUser> {
public AppUser findByUsername(String username) {
return find("username", username).firstResult();
}
}

View File

@@ -0,0 +1,68 @@
package nl.veenm.paypoint.resource;
import jakarta.annotation.security.PermitAll;
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 nl.veenm.paypoint.domain.Appointment;
import nl.veenm.paypoint.service.AppointmentService;
import java.util.List;
@Path("/api/appointments")
@RolesAllowed({"ADMIN", "USER"})
public class AppointmentResource {
@Inject
AppointmentService appointmentService;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Appointment> getAppointments() {
return appointmentService.getAllAppointments();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/date")
public List<Appointment> getAppointmentsByDate(@QueryParam("start") String start) {
return appointmentService.getAppointmentsByDate(start);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/recent/{id}")
public Appointment getMostRecentAppointment(@PathParam("id") Long userId) {
return appointmentService.getMostRecentByUserId(userId);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}")
public Appointment getAppointmentById(@PathParam("id") Long id) {
return appointmentService.getAppointment(id);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response addAppointment(Appointment appointment) {
System.out.println(appointment);
appointmentService.add(appointment);
return Response.ok().build();
}
@DELETE
@Consumes(MediaType.APPLICATION_JSON)
public Response deleteAppointment(@QueryParam("id") Long id) {
appointmentService.delete(id);
return Response.ok().build();
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response updateAppointment(Appointment appointment) {
appointmentService.update(appointment);
return Response.ok().build();
}
}

View File

@@ -0,0 +1,64 @@
package nl.veenm.paypoint.resource;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.domain.UserDTO;
import nl.veenm.paypoint.service.TokenService;
import nl.veenm.paypoint.service.UserService;
@Path("/auth")
public class AuthResource {
@Inject
TokenService tokenService;
@Inject
UserService userService;
@POST
@Path("/register")
public Response register(AppUser appUser) {
userService.registerUser(appUser);
return Response.ok().build();
}
// @POST
// @Path("/login")
// @PermitAll
// @Consumes(MediaType.APPLICATION_JSON)
// public Response login(AuthDTO user) {
// System.out.println(user);
// System.out.println("admin".equals(user.getUsername()) && "password".equals(user.getPassword()));
// if ("admin".equals(user.getUsername()) && "password".equals(user.getPassword())) {
// UserDTO userDTO = new UserDTO();
// userDTO.setUsername(user.getUsername());
// userDTO.setEmail("vanveenmel11@gmail.com");
// userDTO.setFullName("Mel van Veen");
// userDTO.setToken(tokenService.generateToken(user.getUsername()));
// return Response.ok(userDTO).build();
// }
// return Response.status(Response.Status.UNAUTHORIZED).build();
// }
@POST
@Path("/login")
public Response login(AppUser user) {
AppUser authenticated = userService.authenticate(user.getUsername(), user.getPassword());
if (authenticated != null) {
String token = tokenService.generateToken(authenticated);
UserDTO userDTO = new UserDTO();
userDTO.setUsername(authenticated.getUsername());
userDTO.setEmail(authenticated.getEmail());
userDTO.setFullName(authenticated.getFirstName() + " " + authenticated.getLastName());
userDTO.setToken(token);
return Response.ok(userDTO).build();
} else {
return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid credentials").build();
}
}
}

View File

@@ -0,0 +1,35 @@
package nl.veenm.paypoint.resource;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import nl.veenm.paypoint.domain.Customer;
import nl.veenm.paypoint.service.CustomerService;
import java.util.List;
@Path("/api/customers")
@PermitAll()
public class CustomerResource {
@Inject
CustomerService customerService;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Customer> getCustomers() {
return customerService.getCustomers();
}
@POST
@Produces(MediaType.APPLICATION_JSON)
public Response addCustomer(Customer customer) {
customerService.addCustomer(customer);
return Response.ok().build();
}
}

View File

@@ -0,0 +1,102 @@
package nl.veenm.paypoint.service;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import nl.veenm.paypoint.domain.Appointment;
import nl.veenm.paypoint.repository.AppointmentRepository;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@ApplicationScoped
public class AppointmentService {
@Inject
AppointmentRepository appointmentRepository;
@Inject
EmailService emailService;
private List<Appointment> appointments;
public AppointmentService() {
LocalDateTime now = LocalDateTime.now();
appointments = new ArrayList<>();
appointments.add(new Appointment(1L, "Knippen Mel", "", now, 12, 0, 13, 0, 60));
appointments.add(new Appointment(2L, "Knippen Mel", "", now, 14, 0, 15, 0, 60));
}
@Transactional
public List<Appointment> getAllAppointments() {
return appointmentRepository.listAll();
}
@Transactional
public void add(Appointment appointment) {
// appointment.setStartDate(appointment.getStartDate().plusDays(1));
appointmentRepository.persist(appointment);
emailService.stuurBevestiging(appointment);
}
@Transactional
public List<Appointment> getAppointmentsByDate(String start) {
LocalDate date = LocalDate.parse(start);
LocalDateTime startOfDay = date.atStartOfDay(); // 00:00:00
LocalDateTime endOfDay = date.atTime(23, 59, 59); // 23:59:59
return appointmentRepository.find("startDate BETWEEN ?1 AND ?2", startOfDay, endOfDay).list();
}
@Transactional
public void delete(Long id) {
emailService.stuurVerwijdering(appointmentRepository.findById(id));
appointmentRepository.deleteById(id);
}
@Transactional
public void update(Appointment appointment) {
Appointment appointmentToUpdate = appointmentRepository.findById(appointment.getId());
appointmentToUpdate.setTitle(appointment.getTitle());
appointmentToUpdate.setStartDate(appointment.getStartDate());
appointmentToUpdate.setDescription(appointment.getDescription());
appointmentToUpdate.setStartHour(appointment.getStartHour());
appointmentToUpdate.setStartMinute(appointment.getStartMinute());
appointmentToUpdate.setEndHour(appointment.getEndHour());
appointmentToUpdate.setEndMinute(appointment.getEndMinute());
appointmentToUpdate.setCustomer(appointment.getCustomer());
appointmentToUpdate.setDurationInMinutes(appointment.getDurationInMinutes());
appointmentRepository.persist(appointmentToUpdate);
emailService.stuurBewerking(appointmentToUpdate);
}
@Transactional
public Appointment getAppointment(Long id) {
return appointmentRepository.findById(id);
}
@Transactional
@Scheduled(cron = "0 0 0 * * ?")
public void sendReminder() {
LocalDate date = LocalDate.now();
date = date.plusDays(1);
LocalDateTime startOfDay = date.atStartOfDay(); // 00:00:00
LocalDateTime endOfDay = date.atTime(23, 59, 59); // 23:59:59
List<Appointment> allAppointments = appointmentRepository.find("date BETWEEN ?1 AND ?2", startOfDay, endOfDay).list();
allAppointments.forEach(appointment -> {
emailService.stuurHerinnering(appointment);
});
}
public Appointment getMostRecentByUserId(Long userId) {
return appointmentRepository.findMostRecentByUserId(userId).orElse(null);
}
}

View File

@@ -0,0 +1,34 @@
package nl.veenm.paypoint.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import nl.veenm.paypoint.domain.Customer;
import nl.veenm.paypoint.repository.CustomerRepository;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@ApplicationScoped
public class CustomerService {
@Inject
CustomerRepository customerRepository;
public CustomerService() {
}
@Transactional
public List<Customer> getCustomers() {
List<Customer> customers = customerRepository.listAll();
return customers.stream()
.sorted(Comparator.comparing(Customer::getFirstName))
.collect(Collectors.toList());
}
@Transactional
public void addCustomer(Customer customer) {
customerRepository.persist(customer);
}
}

View File

@@ -0,0 +1,381 @@
package nl.veenm.paypoint.service;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.Appointment;
import nl.veenm.paypoint.helper.EmailHelper;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
@ApplicationScoped
public class EmailService {
private final Mailer mailer;
public EmailService(Mailer mailer) {
this.mailer = mailer;
}
public void stuurBevestiging(Appointment appointment) {
String location = "<br>Groenestraat 29<br>6681DW Bemmel";
String imageUrl = "https://hairstylingbydaan.nl/assets/img/Logo.png";
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedHour = String.valueOf(appointment.getStartHour()).length() == 1 ? "0" + appointment.getStartHour() : String.valueOf(appointment.getStartHour());
String formattedMinute = String.valueOf(appointment.getStartMinute()).length() == 1 ? "0" + appointment.getStartMinute() : String.valueOf(appointment.getStartMinute());
String formattedTime = formattedHour + ":" + formattedMinute;
String formattedEndHour = String.valueOf(appointment.getEndHour()).length() == 1 ? "0" + appointment.getEndHour() : String.valueOf(appointment.getEndHour());
String formattedEndMinute = String.valueOf(appointment.getEndMinute()).length() == 1 ? "0" + appointment.getEndMinute() : String.valueOf(appointment.getEndMinute());
String formattedEndTime = formattedEndHour + ":" + formattedEndMinute;
// Bouw de HTML-inhoud van de e-mail
String emailBody = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Afspraakbevestiging</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 80%%;
max-width: 600px;
background-color: #ffffff;
margin: 20px auto;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #3c3c3c;
color: white;
text-align: center;
padding: 15px;
font-size: 20px;
font-weight: bold;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.content {
padding: 20px;
text-align: center;
font-size: 16px;
color: #333;
}
.footer {
text-align: center;
font-size: 12px;
color: #666;
margin-top: 20px;
}
.image {
height: 128px;
width: auto;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">Bevestiging van uw afspraak</div>
<div class="content">
<h4>Beste %s,</h4>
<h4>Uw afspraak is succesvol ingepland!</h4>
<h4><strong>Datum:</strong> %s<br>
<strong>Tijd:</strong> %s<br><br>
<strong>Locatie:</strong> %s</h4>
</div>
<div class="footer">
<img src="%s" alt="Afspraak bevestiging" class="image">
<h4>Met vriendelijke groet,<br>Danielle<br>Hairstyling By Daan</h4>
<p>&copy; 2025 PayPoint. Alle rechten voorbehouden.</p>
</div>
</div>
</body>
</html>
""".formatted(appointment.getCustomer().getFirstName(), formattedDate, formattedTime,
location, imageUrl);
String subject = String.format(" Afspraak bevestigd: %s", formattedDate);
String recipient = appointment.getCustomer().getEmail();
LocalDateTime date = appointment.getStartDate();
date = date.withHour(appointment.getStartHour()).withMinute(appointment.getStartMinute());
String dtStamp = date.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));
mailer.send(Mail.withHtml(recipient, subject, emailBody)
.setFrom("Hairstyling By Daan <paypoint@melvanveen.nl>")
.addAttachment("afspraak.ics", EmailHelper.getIcs(dtStamp, date.toLocalDate(), formattedTime, formattedEndTime, location).getBytes(), "text/calendar"));
}
public void stuurVerwijdering(Appointment appointment) {
String imageUrl = "https://hairstylingbydaan.nl/assets/img/Logo.png";
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
// Bouw de HTML-inhoud van de e-mail
String emailBody = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Afspraakbevestiging</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 80%%;
max-width: 600px;
background-color: #ffffff;
margin: 20px auto;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #3c3c3c;
color: white;
text-align: center;
padding: 15px;
font-size: 20px;
font-weight: bold;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.content {
padding: 20px;
text-align: center;
font-size: 16px;
color: #333;
}
.footer {
text-align: center;
font-size: 12px;
color: #666;
margin-top: 20px;
}
.image {
height: 128px;
width: auto;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">Annulering van uw afspraak</div>
<div class="content">
<h4>Beste %s,</h4>
<h4>Uw afspraak op %s is geannuleerd.</h4>
</div>
<div class="footer">
<img src="%s" alt="Afspraak bevestiging" class="image">
<h4>Met vriendelijke groet,<br>Danielle<br>Hairstyling By Daan</h4>
<p>&copy; 2025 PayPoint. Alle rechten voorbehouden.</p>
</div>
</div>
</body>
</html>
""".formatted(appointment.getCustomer().getFirstName(), formattedDate, imageUrl);
String subject = String.format(" Afspraak geannuleerd: %s", formattedDate);
String recipient = appointment.getCustomer().getEmail();
mailer.send(Mail.withHtml(recipient, subject, emailBody).setFrom("Hairstyling By Daan <paypoint@melvanveen.nl>"));
}
public void stuurBewerking(Appointment appointment) {
String location = "<br>Groenestraat 29<br>6681DW Bemmel";
String imageUrl = "https://hairstylingbydaan.nl/assets/img/Logo.png";
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedTime = String.format("%s:%s", appointment.getStartHour(), String.valueOf(appointment.getStartMinute()).length() == 1 ? "0" + appointment.getStartMinute() : appointment.getStartMinute());
// Bouw de HTML-inhoud van de e-mail
String emailBody = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Afspraakbevestiging</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 80%%;
max-width: 600px;
background-color: #ffffff;
margin: 20px auto;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #3c3c3c;
color: white;
text-align: center;
padding: 15px;
font-size: 20px;
font-weight: bold;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.content {
padding: 20px;
text-align: center;
font-size: 16px;
color: #333;
}
.footer {
text-align: center;
font-size: 12px;
color: #666;
margin-top: 20px;
}
.image {
height: 128px;
width: auto;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">Wijziging van uw afspraak</div>
<div class="content">
<h4>Beste %s,</h4>
<h4>Uw afspraak op %s is gewijzigd.</h4>
<h4><strong>Datum:</strong> %s<br>
<strong>Tijd:</strong> %s<br><br>
<strong>Locatie:</strong> %s</h4>
</div>
<div class="footer">
<img src="%s" alt="Afspraak bevestiging" class="image">
<h4>Met vriendelijke groet,<br>Danielle<br>Hairstyling By Daan</h4>
<p>&copy; 2025 PayPoint. Alle rechten voorbehouden.</p>
</div>
</div>
</body>
</html>
""".formatted(appointment.getCustomer().getFirstName(), formattedDate, formattedDate, formattedTime,
location, imageUrl);
String subject = String.format(" Afspraak gewijzigd: %s", formattedDate);
String recipient = appointment.getCustomer().getEmail();
mailer.send(Mail.withHtml(recipient, subject, emailBody).setFrom("Hairstyling By Daan <paypoint@melvanveen.nl>"));
}
public void stuurHerinnering(Appointment appointment) {
String location = "<br>Groenestraat 29<br>6681DW Bemmel";
String imageUrl = "https://hairstylingbydaan.nl/assets/img/Logo.png";
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedTime = String.format("%s:%s", appointment.getStartHour(), String.valueOf(appointment.getStartMinute()).length() == 1 ? "0" + appointment.getStartMinute() : appointment.getStartMinute());
// Bouw de HTML-inhoud van de e-mail
String emailBody = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Afspraakbevestiging</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 80%%;
max-width: 600px;
background-color: #ffffff;
margin: 20px auto;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #3c3c3c;
color: white;
text-align: center;
padding: 15px;
font-size: 20px;
font-weight: bold;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.content {
padding: 20px;
text-align: center;
font-size: 16px;
color: #333;
}
.footer {
text-align: center;
font-size: 12px;
color: #666;
margin-top: 20px;
}
.image {
height: 128px;
width: auto;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">Herinnering van uw afspraak</div>
<div class="content">
<h4>Beste %s,</h4>
<h4>U heeft morgen een afspraak.</h4>
<h4><strong>Datum:</strong> %s<br>
<strong>Tijd:</strong> %s<br><br>
<strong>Locatie:</strong> %s</h4>
</div>
<div class="footer">
<img src="%s" alt="Afspraak bevestiging" class="image">
<h4>Met vriendelijke groet,<br>Danielle<br>Hairstyling By Daan</h4>
<p>&copy; 2025 PayPoint. Alle rechten voorbehouden.</p>
</div>
</div>
</body>
</html>
""".formatted(appointment.getCustomer().getFirstName(), formattedDate, formattedTime,
location, imageUrl);
String subject = String.format("Herinnering afspraak: %s", formattedDate);
String recipient = appointment.getCustomer().getEmail();
mailer.send(Mail.withHtml(recipient, subject, emailBody).setFrom("Hairstyling By Daan <paypoint@melvanveen.nl>"));
}
}

View File

@@ -0,0 +1,11 @@
package nl.veenm.paypoint.service;
import at.favre.lib.crypto.bcrypt.BCrypt;
public class PasswordService {
public static String hashPassword(String plainPassword) {
return BCrypt.withDefaults().hashToString(12, plainPassword.toCharArray());
}
}

View File

@@ -0,0 +1,24 @@
package nl.veenm.paypoint.service;
import io.smallrye.jwt.build.Jwt;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.AppUser;
import java.time.Duration;
@ApplicationScoped
public class TokenService {
public String generateToken(AppUser appUser) {
return Jwt.issuer("PayPoint")
.subject(appUser.getUsername())
.expiresIn(Duration.ofHours(1))
.claim("username", appUser.getUsername())
.claim("firstName", appUser.getFirstName())
.claim("lastName", appUser.getLastName())
.claim("email", appUser.getEmail())
.claim("groups", appUser.getRole())
.sign();
}
}

View File

@@ -0,0 +1,33 @@
package nl.veenm.paypoint.service;
import at.favre.lib.crypto.bcrypt.BCrypt;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.repository.UserRepository;
@ApplicationScoped
public class UserService {
@Inject
UserRepository userRepository;
@Transactional
public void registerUser(AppUser appUser) {
appUser.setPassword(PasswordService.hashPassword(appUser.getPassword()));
userRepository.persist(appUser);
}
public AppUser authenticate(String username, String password) {
AppUser user = userRepository.findByUsername(username);
if (user == null) {
return null;
}
if (BCrypt.verifyer().verify(password.toCharArray(), user.getPassword()).verified) {
return user;
}
return null;
}
}

View File

@@ -0,0 +1,40 @@
# PostgreSQL configuratie
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/paypoint
quarkus.datasource.username=${DB_USERNAME}
quarkus.datasource.password=${DB_PASSWORD}
quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.sql-load-script=no-file
quarkus.hibernate-orm.log.highlight-sql=false
quarkus.hibernate-orm.log.sql=false
quarkus.http.cors=true
quarkus.http.cors.origins=*
quarkus.http.cors.methods=GET,POST,OPTIONS,DELETE,PUT
# Mailer configuratie
quarkus.mailer.from=${MAILER_FROM}
quarkus.mailer.host=${MAILER_HOST}
quarkus.mailer.port=${MAILER_PORT}
quarkus.mailer.username=${MAILER_USERNAME}
quarkus.mailer.password=${MAILER_PASSWORD}
quarkus.mailer.mock=false
# JWT-instellingen
mp.jwt.verify.issuer=PayPoint
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.publickey.algorithm=RS256
# Signing Key Configuratie (voor token genereren)
smallrye.jwt.sign.key.location=privateKey.pem
smallrye.jwt.sign.key.algorithm=RS256
# Token Levensduur (optioneel)
smallrye.jwt.new-token.lifespan=3600
quarkus.log.category."io.quarkus.security".level=DEBUG
mp.jwt.verify.claims.groups=groups
quarkus.log.category."io.smallrye.jwt".level=DEBUG