8 Commits

Author SHA1 Message Date
74bf3526ea refactoring betreft het datum object
All checks were successful
Docker Image CI / build-and-push (push) Successful in 11m23s
Docker Image CI / deploy (push) Successful in 25s
Docker Image CI / notify-failure (push) Has been skipped
2025-05-25 18:05:45 +02:00
69add9d8e3 pom fix ivm database
All checks were successful
Docker Image CI / build-and-push (push) Successful in 10m40s
Docker Image CI / deploy (push) Successful in 31s
Docker Image CI / notify-failure (push) Has been skipped
2025-04-20 14:23:17 +02:00
434448c423 fix Dockerfile
All checks were successful
Docker Image CI / build-and-push (push) Successful in 10m46s
Docker Image CI / deploy (push) Successful in 31s
Docker Image CI / notify-failure (push) Has been skipped
2025-04-19 22:39:04 +02:00
99cd5f2e66 fix betreft email voor invite pt 3
Some checks failed
Docker Image CI / build-and-push (push) Failing after 10m19s
Docker Image CI / deploy (push) Has been skipped
Docker Image CI / notify-failure (push) Successful in 12s
2025-04-19 20:21:37 +02:00
764f931a29 fix betreft email voor invite
Some checks failed
Docker Image CI / build-and-push (push) Failing after 10m31s
Docker Image CI / deploy (push) Has been skipped
Docker Image CI / notify-failure (push) Successful in 12s
2025-04-19 15:16:00 +02:00
ce8e83c40a fix betreft email voor invite en logging van gebruiker info
All checks were successful
Docker Image CI / build-and-push (push) Successful in 11m16s
Docker Image CI / deploy (push) Successful in 30s
Docker Image CI / notify-failure (push) Has been skipped
2025-04-19 12:39:12 +02:00
46d7937db5 fix betreft afspraken
All checks were successful
Docker Image CI / build-and-push (push) Successful in 10m45s
Docker Image CI / deploy (push) Successful in 24s
Docker Image CI / notify-failure (push) Has been skipped
2025-04-18 23:30:59 +02:00
a497b8162b Update:
All checks were successful
Docker Image CI / build-and-push (push) Successful in 11m9s
Docker Image CI / deploy (push) Successful in 27s
Docker Image CI / notify-failure (push) Has been skipped
-delen van agenda toegevoegd
-popup aangepast
-accepteren van uitnodiging toegevoegd
-koppelen van gebruiker aan bedrijf
-versturen van uitnodiging via mail
2025-04-18 22:41:17 +02:00
32 changed files with 1114 additions and 164 deletions

90
.github/workflows/deploy-docker.yml vendored Normal file
View File

@@ -0,0 +1,90 @@
name: Docker Image CI
on:
push:
branches:
- main
tags:
- "docker-build-*"
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
# Stap 1: Code ophalen
- uses: actions/checkout@v4
# Stap 2: Versienummer ophalen uit package.json en opslaan als artifact
- name: Extract Angular version
run: |
echo "$(cat package.json | jq -r '.version')" > version.txt
- name: Save version as artifact
uses: actions/upload-artifact@v3
with:
name: version
path: version.txt
# Stap 3: Inloggen bij Docker Hub
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Stap 4: Docker-image bouwen en taggen met Angular-versie
- name: Build the Docker image
run: |
VERSION=$(cat version.txt)
docker buildx build . --file Dockerfile --tag veenm/paypoint:$VERSION --tag veenm/paypoint:latest --platform linux/amd64
# Stap 5: Docker-image pushen naar Docker Hub (huidige versie tag)
- name: Push the Docker image (version)
run: |
VERSION=$(cat version.txt)
docker push veenm/paypoint:$VERSION
# Stap 6: Docker-image pushen naar Docker Hub (latest tag)
- name: Push the Docker image (latest)
run: docker push veenm/paypoint:latest
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
# Stap 1: Artifact ophalen
- name: Download version artifact
uses: actions/download-artifact@v3
with:
name: version
# Stap 2: Lees versie uit het artifact
- name: Read version
id: read_version
run: echo "VERSION=$(cat version.txt)" >> $GITHUB_ENV
# Stap 3: Maak verbinding via SSH naar de TrueNAS SCALE server en update de container
- name: SSH into TrueNAS SCALE and update Docker container
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.TRUENAS_HOST }}
username: ${{ secrets.TRUENAS_USER }}
password: ${{ secrets.TRUENAS_PASSWORD }}
port: ${{ secrets.TRUENAS_PORT }}
script: |
VERSION=${{ env.VERSION }}
echo "Gekozen versie: $VERSION"
# Stop en verwijder de huidige container
docker stop paypoint || true
docker rm paypoint || true
# Haal de nieuwste image binnen
docker pull veenm/paypoint:$VERSION
# Start een nieuwe container
docker run -d --name paypoint --restart unless-stopped -p 15001:80 veenm/paypoint:$VERSION
# Opruimen oude images
docker image prune -f

44
.github/workflows/increase-version.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Increase Version
on:
push:
branches:
- main # Start bij commits op de main branch
jobs:
bump-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name "Gitea Actions"
git config --global user.email "actions@gitea.local"
- name: Bump version
run: |
npm version patch --no-git-tag-version
git add package.json package-lock.json
git commit -m "chore: bump version [skip ci]" || echo "No changes to commit"
# Pull the latest changes from the remote main branch before pushing
git pull origin main --rebase
# Push changes to remote main branch
git push origin main
trigger-pipeline-b:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Create and push tag
run: |
git config --global user.name "Gitea Actions"
git config --global user.email "actions@gitea.local"
git tag docker-build-$(date +%s)
git push --tags

View File

@@ -80,7 +80,7 @@
FROM registry.access.redhat.com/ubi8/openjdk-21:1.20
ENV LANGUAGE='en_US:en'
ENV QUARKUS_PROFILE=test
# 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/
@@ -90,7 +90,7 @@ 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_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Dquarkus.profile=prod -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

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>nl.veenm</groupId>
<artifactId>paypoint-backend</artifactId>
@@ -101,6 +102,7 @@
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
<include>templates/agenda-invite.html</include>
</includes>
</resource>
</resources>
@@ -151,7 +153,8 @@
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner
</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>

View File

@@ -0,0 +1,7 @@
package nl.veenm.paypoint.domain;
public enum AccessLevel {
READ_ONLY,
USER
}

View File

@@ -17,13 +17,8 @@ public class AppUser {
private String firstName;
private String lastName;
@ManyToMany
@JoinTable(
name = "user_company",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "company_id")
)
private Set<Company> companies = new HashSet<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<UserCompany> userCompanies = new HashSet<>();
public Long getId() {
return id;
@@ -81,12 +76,12 @@ public class AppUser {
this.lastName = lastName;
}
public Set<Company> getCompanies() {
return companies;
public Set<UserCompany> getUserCompanies() {
return userCompanies;
}
public void setCompanies(Set<Company> companies) {
this.companies = companies;
public void setUserCompanies(Set<UserCompany> userCompanies) {
this.userCompanies = userCompanies;
}
@Override

View File

@@ -3,19 +3,17 @@ package nl.veenm.paypoint.domain;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
@Entity
public class Appointment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private UUID id;
private String title;
private String description;
private LocalDateTime startDate;
private int startHour;
private int startMinute;
private int endHour;
private int endMinute;
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
private int durationInMinutes;
@ManyToOne
@@ -24,27 +22,15 @@ public class Appointment {
@ManyToOne
private Company company;
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() {
public UUID getId() {
return id;
}
public void setId(Long id) {
public void setId(UUID id) {
this.id = id;
}
@@ -64,44 +50,12 @@ public class Appointment {
this.description = description;
}
public LocalDateTime getStartDate() {
return startDate;
public LocalDateTime getStartDateTime() {
return startDateTime;
}
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 void setStartDateTime(LocalDateTime startDate) {
this.startDateTime = startDate;
}
public int getDurationInMinutes() {
@@ -124,6 +78,14 @@ public class Appointment {
return company;
}
public LocalDateTime getEndDateTime() {
return endDateTime;
}
public void setEndDateTime(LocalDateTime end) {
this.endDateTime = end;
}
public void setCompany(Company company) {
this.company = company;
}
@@ -134,13 +96,11 @@ public class Appointment {
"id=" + id +
", title='" + title + '\'' +
", description='" + description + '\'' +
", date=" + startDate +
", startHour=" + startHour +
", startMinute=" + startMinute +
", endHour=" + endHour +
", endMinute=" + endMinute +
", start=" + startDateTime +
", end=" + endDateTime +
", durationInMinutes=" + durationInMinutes +
", customer=" + customer +
", company=" + company +
'}';
}
}

View File

@@ -1,6 +1,5 @@
package nl.veenm.paypoint.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import java.util.HashSet;
@@ -18,9 +17,8 @@ public class Company {
private String postal_code;
private String city;
@ManyToMany(mappedBy = "companies")
@JsonIgnore
private Set<AppUser> users = new HashSet<>();
@OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<UserCompany> userCompanies = new HashSet<>();
public void setId(Long id) {
this.id = id;
@@ -78,11 +76,11 @@ public class Company {
this.city = city;
}
public Set<AppUser> getUsers() {
return users;
public Set<UserCompany> getUserCompanies() {
return userCompanies;
}
public void setUsers(Set<AppUser> users) {
this.users = users;
public void setUserCompanies(Set<UserCompany> userCompanies) {
this.userCompanies = userCompanies;
}
}

View File

@@ -0,0 +1,100 @@
package nl.veenm.paypoint.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.Instant;
import java.util.UUID;
@Entity
public class InviteEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public UUID id;
public Long company_id;
public String email;
public String token;
public Instant expiresAt;
public boolean used;
public Instant createdAt;
public InviteEntity() {
}
public InviteEntity(UUID id, Long company_id, String email, String token, Instant expiresAt, boolean used, Instant createdAt) {
this.id = id;
this.company_id = company_id;
this.email = email;
this.token = token;
this.expiresAt = expiresAt;
this.used = used;
this.createdAt = createdAt;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public Long getCompany_id() {
return company_id;
}
public void setCompany_id(Long company_id) {
this.company_id = company_id;
}
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;
}
public Instant getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(Instant expires_at) {
this.expiresAt = expires_at;
}
public boolean isUsed() {
return used;
}
public void setUsed(boolean used) {
this.used = used;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant created_at) {
this.createdAt = created_at;
}
}
//Kolom | Type | Beschrijving
//id | UUID (PK) | Uniek ID
//bedrijf_id | Long | ID van het bedrijf/agenda
//email | String | Ontvanger
//token | String (uniek) | Token (bijv. Base64 of UUID)
//expires_at | Timestamp | Expiratietijd (bijv. 24u geldig)
//used | Boolean | Of de uitnodiging al geaccepteerd is
//created_at | Timestamp | Voor logging/audit

View File

@@ -0,0 +1,7 @@
package nl.veenm.paypoint.domain;
public class InviteRequest {
public Long companyId;
public String email;
}

View File

@@ -0,0 +1,5 @@
package nl.veenm.paypoint.domain;
public class UrlRequest {
public String url;
}

View File

@@ -0,0 +1,60 @@
package nl.veenm.paypoint.domain;
import jakarta.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "user_company")
public class UserCompany {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
@ManyToOne
@JoinColumn(name = "user_id")
private AppUser user;
@ManyToOne
@JoinColumn(name = "company_id")
private Company company;
@Enumerated(EnumType.STRING)
@Column(name = "access_level")
private AccessLevel accessLevel;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public AppUser getUser() {
return user;
}
public void setUser(AppUser user) {
this.user = user;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public AccessLevel getAccessLevel() {
return accessLevel;
}
public void setAccessLevel(AccessLevel accessLevel) {
this.accessLevel = accessLevel;
}
}

View File

@@ -0,0 +1,78 @@
package nl.veenm.paypoint.domain.dto;
import java.util.Set;
public class AppUserDTO {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
private String role;
private String token;
private Set<UserCompanyDTO> companies;
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 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 getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Set<UserCompanyDTO> getCompanies() {
return companies;
}
public void setCompanies(Set<UserCompanyDTO> companies) {
this.companies = companies;
}
}

View File

@@ -0,0 +1,81 @@
package nl.veenm.paypoint.domain.dto;
import nl.veenm.paypoint.domain.Customer;
import java.time.LocalDateTime;
import java.util.UUID;
public class AppointmentDTO {
private UUID id;
private String title;
private String description;
private LocalDateTime startDateTime;
private LocalDateTime endDateTime;
private int durationInMinutes;
private Customer customer;
private CompanyDTO company;
public UUID getId() {
return id;
}
public void setId(UUID 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 getStartDateTime() {
return startDateTime;
}
public void setStartDateTime(LocalDateTime startDateTime) {
this.startDateTime = startDateTime;
}
public LocalDateTime getEndDateTime() {
return endDateTime;
}
public void setEndDateTime(LocalDateTime endDateTime) {
this.endDateTime = endDateTime;
}
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;
}
public CompanyDTO getCompany() {
return company;
}
public void setCompany(CompanyDTO company) {
this.company = company;
}
}

View File

@@ -0,0 +1,67 @@
package nl.veenm.paypoint.domain.dto;
public class CompanyDTO {
private Long id;
private String name;
private String email;
private String address;
private String postalCode;
private String city;
private String imgHref;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getImgHref() {
return imgHref;
}
public void setImgHref(String imgHref) {
this.imgHref = imgHref;
}
}

View File

@@ -0,0 +1,33 @@
package nl.veenm.paypoint.domain.dto;
import java.util.UUID;
public class UserCompanyDTO {
private UUID id;
private String accessLevel;
private CompanyDTO company;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getAccessLevel() {
return accessLevel;
}
public void setAccessLevel(String accessLevel) {
this.accessLevel = accessLevel;
}
public CompanyDTO getCompany() {
return company;
}
public void setCompany(CompanyDTO company) {
this.company = company;
}
}

View File

@@ -0,0 +1,55 @@
package nl.veenm.paypoint.domain.mapper;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.domain.Company;
import nl.veenm.paypoint.domain.UserCompany;
import nl.veenm.paypoint.domain.dto.AppUserDTO;
import nl.veenm.paypoint.domain.dto.CompanyDTO;
import nl.veenm.paypoint.domain.dto.UserCompanyDTO;
import java.util.Set;
import java.util.stream.Collectors;
public class AppUserMapper {
public static AppUserDTO toDTO(AppUser user, String token) {
AppUserDTO dto = toDTO(user);
dto.setToken(token);
return dto;
}
public static AppUserDTO toDTO(AppUser user) {
AppUserDTO dto = new AppUserDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
dto.setEmail(user.getEmail());
dto.setFirstName(user.getFirstName());
dto.setLastName(user.getLastName());
dto.setRole(user.getRole());
if (user.getUserCompanies() != null) {
Set<UserCompanyDTO> companyDTOs = user.getUserCompanies()
.stream()
.map(AppUserMapper::toUserCompanyDTO)
.collect(Collectors.toSet());
dto.setCompanies(companyDTOs);
}
return dto;
}
private static UserCompanyDTO toUserCompanyDTO(UserCompany uc) {
UserCompanyDTO dto = new UserCompanyDTO();
dto.setId(uc.getId());
dto.setAccessLevel(uc.getAccessLevel().name());
Company company = uc.getCompany();
CompanyDTO companyDTO = CompanyMapper.toDto(company);
dto.setCompany(companyDTO);
return dto;
}
}

View File

@@ -0,0 +1,25 @@
package nl.veenm.paypoint.domain.mapper;
import nl.veenm.paypoint.domain.Appointment;
import nl.veenm.paypoint.domain.dto.AppointmentDTO;
public class AppointmentMapper {
public static AppointmentDTO toDto(Appointment appointment) {
if (appointment == null) return null;
AppointmentDTO dto = new AppointmentDTO();
dto.setId(appointment.getId());
dto.setTitle(appointment.getTitle());
dto.setDescription(appointment.getDescription());
dto.setStartDateTime(appointment.getStartDateTime());
dto.setEndDateTime(appointment.getEndDateTime());
dto.setDurationInMinutes(appointment.getDurationInMinutes());
dto.setCustomer(appointment.getCustomer());
dto.setCompany(CompanyMapper.toDto(appointment.getCompany()));
return dto;
}
}

View File

@@ -0,0 +1,39 @@
package nl.veenm.paypoint.domain.mapper;
import nl.veenm.paypoint.domain.Company;
import nl.veenm.paypoint.domain.dto.CompanyDTO;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class CompanyMapper {
public static CompanyDTO toDto(Company company) {
if (company == null) {
return null;
}
CompanyDTO dto = new CompanyDTO();
dto.setId(company.getId());
dto.setName(company.getName());
dto.setEmail(company.getEmail());
dto.setAddress(company.getAddress());
dto.setPostalCode(company.getPostal_code());
dto.setCity(company.getCity());
dto.setImgHref(company.getImg_href());
return dto;
}
public static List<CompanyDTO> toDtoList(Collection<Company> companies) {
if (companies == null) {
return Collections.emptyList();
}
return companies.stream()
.map(CompanyMapper::toDto)
.collect(Collectors.toList());
}
}

View File

@@ -10,6 +10,7 @@ import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ApplicationScoped
public class AppointmentRepository implements PanacheRepository<Appointment> {
@@ -22,14 +23,23 @@ public class AppointmentRepository implements PanacheRepository<Appointment> {
.firstResultOptional();
}
public Appointment findByUUID(UUID uuid) {
return find("id = ?1", uuid).firstResult();
}
public List<Appointment> findMostRecentByCompanyId(Company company) {
return find("company = ?1", company).list();
}
public List<Appointment> findAppointmentsForCompanies(Set<Company> companies, LocalDateTime startDate, LocalDateTime endDate) {
return find("SELECT a FROM Appointment a WHERE a.company IN :companies AND a.startDate BETWEEN :start AND :end",
Parameters.with("companies", companies).and("start", startDate).and("end", endDate))
public List<Appointment> findAppointmentsForCompaniesForDay(Set<Long> companyIds, LocalDateTime startDate, LocalDateTime endDate) {
return find("SELECT a FROM Appointment a WHERE a.company.id IN :companyIds AND a.startDateTime BETWEEN :start AND :end",
Parameters.with("companyIds", companyIds).and("start", startDate).and("end", endDate))
.list();
}
public List<Appointment> findAppointmentsForCompaniesForWeek(Set<Long> companyIds, LocalDateTime startDate, LocalDateTime endDate) {
return find("SELECT a FROM Appointment a WHERE a.company.id IN :companyIds AND a.startDateTime BETWEEN :start AND :end",
Parameters.with("companyIds", companyIds).and("start", startDate).and("end", endDate))
.list();
}
}

View File

@@ -0,0 +1,12 @@
package nl.veenm.paypoint.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.InviteEntity;
@ApplicationScoped
public class InviteRepository implements PanacheRepository<InviteEntity> {
public InviteEntity findByToken(String token) {
return find("token", token).firstResult();
}
}

View File

@@ -0,0 +1,15 @@
package nl.veenm.paypoint.repository;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.domain.UserCompany;
import java.util.List;
@ApplicationScoped
public class UserCompanyRepository implements PanacheRepository<UserCompany> {
public List<UserCompany> getAllByUserId(AppUser user) {
return find("user", user).list();
}
}

View File

@@ -0,0 +1,40 @@
package nl.veenm.paypoint.resource;
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.InviteEntity;
import nl.veenm.paypoint.domain.InviteRequest;
import nl.veenm.paypoint.domain.UrlRequest;
import nl.veenm.paypoint.service.AgendaService;
@Path("/agenda")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AgendaResource {
@Inject
AgendaService agendaService;
@POST
@Path("/{email}")
public void sendInvite(@PathParam("email") String email, UrlRequest urlRequest) {
agendaService.sendInvite(email, urlRequest.url);
}
@GET
@Path("/verify")
@Produces(MediaType.APPLICATION_JSON)
public Response verifyInvite(@QueryParam("token") String token) {
return agendaService.verifyInvite(token);
}
@POST
@Path("/createinvite")
public InviteEntity createInvite(InviteRequest inviteRequest) {
return agendaService.createInvite(inviteRequest);
}
}

View File

@@ -6,6 +6,7 @@ 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.domain.dto.AppointmentDTO;
import nl.veenm.paypoint.service.AppointmentService;
import org.eclipse.microprofile.jwt.JsonWebToken;
@@ -30,11 +31,20 @@ public class AppointmentResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/date")
public List<Appointment> getAppointmentsByDate(@QueryParam("start") String start) {
public List<AppointmentDTO> getAppointmentsByDate(@QueryParam("start") String start) {
String user = jwt.getClaim("username");
return appointmentService.getAppointmentsByDate(start, user);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/date/week")
public List<AppointmentDTO> getAppointmentsByDateWeek(@QueryParam("start") String start, @QueryParam("end") String end) {
String user = jwt.getClaim("username");
System.out.println("user: " + user);
return appointmentService.getAppointmentsByWeek(start, end, user);
}
//TODO: Deze werkend maken
// @GET
@@ -47,7 +57,7 @@ public class AppointmentResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{id}")
public Appointment getAppointmentById(@PathParam("id") Long id) {
public AppointmentDTO getAppointmentById(@PathParam("id") Long id) {
return appointmentService.getAppointment(id);
}
@@ -68,7 +78,7 @@ public class AppointmentResource {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response updateAppointment(Appointment appointment) {
public Response updateAppointment(AppointmentDTO appointment) {
return Response.ok(appointmentService.update(appointment)).build();
}
}

View File

@@ -5,7 +5,8 @@ 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.domain.dto.AppUserDTO;
import nl.veenm.paypoint.domain.mapper.AppUserMapper;
import nl.veenm.paypoint.service.TokenService;
import nl.veenm.paypoint.service.UserService;
@@ -25,36 +26,15 @@ public class AuthResource {
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) {
System.out.println("login");
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();
AppUserDTO authenticatedDTO = AppUserMapper.toDTO(authenticated, token);
return Response.ok(authenticatedDTO).build();
} else {
return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid credentials").build();
}

View File

@@ -3,11 +3,12 @@ package nl.veenm.paypoint.resource;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import nl.veenm.paypoint.domain.Company;
import nl.veenm.paypoint.domain.UserCompany;
import nl.veenm.paypoint.domain.dto.CompanyDTO;
import nl.veenm.paypoint.service.CompanyService;
import org.eclipse.microprofile.jwt.JsonWebToken;
import java.util.Set;
import java.util.List;
@Path("/company")
@Produces(MediaType.APPLICATION_JSON)
@@ -21,14 +22,25 @@ public class CompanyResource {
JsonWebToken jwt;
@GET
public Set<Company> getCompanies() {
public List<UserCompany> getCompanies() {
return this.companyService.getCompanies(this.jwt.getClaim("username"));
}
@GET
@Path("/{id}")
public CompanyDTO getCompanyById(@PathParam("id") Long id) {
return this.companyService.getCompanyById(id);
}
@POST
public void linkCompany(@QueryParam("user") Long userId, @QueryParam("company") Long companyId) {
System.out.println(userId);
System.out.println(companyId);
@Path("/link")
public void linkCompany(@QueryParam("user") Long userId, @QueryParam("token") String token) {
this.companyService.linkCompanyWithInvite(userId, token);
}
@POST
@Path("/link/noInvite")
public void linkCompanyToNewUser(@QueryParam("user") Long userId, @QueryParam("company") Long companyId) {
this.companyService.linkCompany(userId, companyId);
}
}

View File

@@ -0,0 +1,65 @@
package nl.veenm.paypoint.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.core.Response;
import nl.veenm.paypoint.domain.InviteEntity;
import nl.veenm.paypoint.domain.InviteRequest;
import nl.veenm.paypoint.repository.InviteRepository;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
@ApplicationScoped
public class AgendaService {
@Inject
EmailService emailService;
@Inject
InviteRepository inviteRepository;
public void sendInvite(String email, String url) {
this.emailService.stuurUitnodiging(email, url);
}
@Transactional
public InviteEntity createInvite(InviteRequest inviteRequest) {
String token = generateSecureToken(); // zoals eerder besproken
InviteEntity invite = new InviteEntity();
invite.setCompany_id(inviteRequest.companyId);
invite.setEmail(inviteRequest.email);
invite.setToken(token);
invite.setUsed(false);
invite.setCreatedAt(Instant.now());
invite.setExpiresAt(Instant.now().plus(1, ChronoUnit.HOURS)); // 1u geldig
inviteRepository.persist(invite);
return invite;
}
public String generateSecureToken() {
byte[] randomBytes = new byte[24];
new SecureRandom().nextBytes(randomBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
}
public Response verifyInvite(String token) {
InviteEntity invite = inviteRepository.findByToken(token);
if (invite == null || invite.isUsed()) {
return Response.status(Response.Status.BAD_REQUEST).entity("Ongeldige of reeds gebruikte uitnodiging").build();
}
if (invite.getExpiresAt().isBefore(Instant.now())) {
return Response.status(Response.Status.GONE).entity("Deze uitnodiging is verlopen").build();
}
return Response.ok(invite).build();
}
}

View File

@@ -7,6 +7,12 @@ import jakarta.transaction.Transactional;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.domain.Appointment;
import nl.veenm.paypoint.domain.Company;
import nl.veenm.paypoint.domain.dto.AppUserDTO;
import nl.veenm.paypoint.domain.dto.AppointmentDTO;
import nl.veenm.paypoint.domain.dto.CompanyDTO;
import nl.veenm.paypoint.domain.dto.UserCompanyDTO;
import nl.veenm.paypoint.domain.mapper.AppUserMapper;
import nl.veenm.paypoint.domain.mapper.AppointmentMapper;
import nl.veenm.paypoint.repository.AppointmentRepository;
import nl.veenm.paypoint.repository.CompanyRepository;
import nl.veenm.paypoint.repository.UserRepository;
@@ -14,6 +20,8 @@ import nl.veenm.paypoint.repository.UserRepository;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ApplicationScoped
public class AppointmentService {
@@ -39,25 +47,43 @@ public class AppointmentService {
}
@Transactional
public Appointment add(Appointment appointment, Long companyId, String username) {
public AppointmentDTO add(Appointment appointment, Long companyId, String username) {
Company company = companyRepository.findById(companyId);
AppUser user = userRepository.findByUsername(username);
appointment.setCompany(company);
appointmentRepository.persist(appointment);
emailService.stuurBevestiging(appointment, user);
return appointment;
return AppointmentMapper.toDto(appointment);
}
@Transactional
public List<Appointment> getAppointmentsByDate(String start, String username) {
public List<AppointmentDTO> getAppointmentsByDate(String start, String username) {
LocalDate date = LocalDate.parse(start);
AppUser user = userRepository.findByUsername(username);
AppUserDTO appUserDTO = AppUserMapper.toDTO(user);
Set<Long> companies = appUserDTO.getCompanies().stream().map(UserCompanyDTO::getCompany).map(CompanyDTO::getId).collect(Collectors.toSet());
LocalDateTime startOfDay = date.atStartOfDay();
LocalDateTime endOfDay = date.atTime(23, 59, 59);
return appointmentRepository.findAppointmentsForCompanies(user.getCompanies(), startOfDay, endOfDay);
List<Appointment> appointmentsForCompanies = appointmentRepository.findAppointmentsForCompaniesForDay(companies, startOfDay, endOfDay);
return appointmentsForCompanies.stream().map(AppointmentMapper::toDto).toList();
}
@Transactional
public List<AppointmentDTO> getAppointmentsByWeek(String start, String end, String username) {
LocalDate startDate = LocalDate.parse(start);
LocalDate endDate = LocalDate.parse(end);
AppUser user = userRepository.findByUsername(username);
AppUserDTO appUserDTO = AppUserMapper.toDTO(user);
Set<Long> companies = appUserDTO.getCompanies().stream().map(UserCompanyDTO::getCompany).map(CompanyDTO::getId).collect(Collectors.toSet());
LocalDateTime startOfStartDay = startDate.atStartOfDay();
LocalDateTime endOfEndDate = endDate.atTime(23, 59, 59);
List<Appointment> appointmentsForCompanies = appointmentRepository.findAppointmentsForCompaniesForWeek(companies, startOfStartDay, endOfEndDate);
System.out.println(appointmentsForCompanies.size());
return appointmentsForCompanies.stream().map(AppointmentMapper::toDto).toList();
}
@Transactional
@@ -67,25 +93,23 @@ public class AppointmentService {
}
@Transactional
public Appointment update(Appointment appointment) {
Appointment appointmentToUpdate = appointmentRepository.findById(appointment.getId());
public AppointmentDTO update(AppointmentDTO appointment) {
Appointment appointmentToUpdate = appointmentRepository.findByUUID(appointment.getId());
appointmentToUpdate.setTitle(appointment.getTitle());
appointmentToUpdate.setStartDate(appointment.getStartDate());
appointmentToUpdate.setStartDateTime(appointment.getStartDateTime());
appointmentToUpdate.setEndDateTime(appointment.getEndDateTime());
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);
return appointmentToUpdate;
return AppointmentMapper.toDto(appointmentToUpdate);
}
@Transactional
public Appointment getAppointment(Long id) {
return appointmentRepository.findById(id);
public AppointmentDTO getAppointment(Long id) {
Appointment byId = appointmentRepository.findById(id);
return AppointmentMapper.toDto(byId);
}
@Transactional

View File

@@ -3,38 +3,57 @@ package nl.veenm.paypoint.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.domain.Company;
import nl.veenm.paypoint.domain.*;
import nl.veenm.paypoint.domain.dto.CompanyDTO;
import nl.veenm.paypoint.domain.mapper.CompanyMapper;
import nl.veenm.paypoint.repository.CompanyRepository;
import nl.veenm.paypoint.repository.InviteRepository;
import nl.veenm.paypoint.repository.UserCompanyRepository;
import nl.veenm.paypoint.repository.UserRepository;
import java.util.Set;
import java.util.List;
@ApplicationScoped
public class CompanyService {
@Inject
CompanyRepository companyRepository;
@Inject
UserCompanyRepository userCompanyRepository;
@Inject
UserRepository userRepository;
public Set<Company> getCompanies(String username) {
AppUser user = this.userRepository.findByUsername(username);
return user.getCompanies();
@Inject
InviteRepository inviteRepository;
public List<UserCompany> getCompanies(String username) {
AppUser user = userRepository.findByUsername(username);
return userCompanyRepository.getAllByUserId(user);
}
public CompanyDTO getCompanyById(Long id) {
return CompanyMapper.toDto(companyRepository.findById(id));
}
@Transactional
public void linkCompanyWithInvite(Long userId, String token) {
InviteEntity invite = inviteRepository.findByToken(token);
linkCompany(userId, invite.company_id);
invite.used = true;
inviteRepository.persist(invite);
}
@Transactional
public void linkCompany(Long userId, Long companyId) {
AppUser user = this.userRepository.findById(userId);
Company company = this.companyRepository.findById(companyId);
AppUser user = userRepository.findById(userId);
Company company = companyRepository.findById(companyId);
System.out.println(user.getCompanies());
user.getCompanies().add(company);
company.getUsers().add(user);
System.out.println(user.getCompanies());
userRepository.persist(user);
companyRepository.persist(company);
UserCompany userCompany = new UserCompany();
userCompany.setUser(user);
userCompany.setCompany(company);
userCompany.setAccessLevel(AccessLevel.USER);
userCompanyRepository.persist(userCompany);
}
}

View File

@@ -3,6 +3,7 @@ package nl.veenm.paypoint.service;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import nl.veenm.paypoint.domain.AppUser;
import nl.veenm.paypoint.domain.Appointment;
import nl.veenm.paypoint.domain.Company;
@@ -11,12 +12,16 @@ import nl.veenm.paypoint.helper.EmailHelper;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Map;
@ApplicationScoped
public class EmailService {
private final Mailer mailer;
@Inject
EmailTemplateService emailTemplateService;
public EmailService(Mailer mailer) {
this.mailer = mailer;
}
@@ -25,14 +30,14 @@ public class EmailService {
Company company = appointment.getCompany();
String location = String.format("<br>%s<br>%s %s", company.getAddress(), company.getPostal_code(), company.getCity());
String imageUrl = appointment.getCompany().getImg_href();
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedDate = appointment.getStartDateTime().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 formattedHour = String.valueOf(appointment.getStartDateTime().getHour()).length() == 1 ? "0" + appointment.getStartDateTime().getHour() : String.valueOf(appointment.getStartDateTime().getHour());
String formattedMinute = String.valueOf(appointment.getStartDateTime().getMinute()).length() == 1 ? "0" + appointment.getStartDateTime().getMinute() : String.valueOf(appointment.getStartDateTime().getMinute());
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 formattedEndHour = String.valueOf(appointment.getEndDateTime().getHour()).length() == 1 ? "0" + appointment.getEndDateTime().getHour() : String.valueOf(appointment.getEndDateTime().getHour());
String formattedEndMinute = String.valueOf(appointment.getEndDateTime().getMinute()).length() == 1 ? "0" + appointment.getEndDateTime().getMinute() : String.valueOf(appointment.getEndDateTime().getMinute());
String formattedEndTime = formattedEndHour + ":" + formattedEndMinute;
// Bouw de HTML-inhoud van de e-mail
@@ -110,8 +115,8 @@ public class EmailService {
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());
LocalDateTime date = appointment.getStartDateTime();
date = date.withHour(appointment.getStartDateTime().getHour()).withMinute(appointment.getStartDateTime().getMinute());
String dtStamp = date.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));
String sender = appointment.getCompany().getName() + " <paypoint@melvanveen.nl>";
@@ -125,7 +130,7 @@ public class EmailService {
public void stuurVerwijdering(Appointment appointment) {
Company company = appointment.getCompany();
String imageUrl = company.getImg_href();
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedDate = appointment.getStartDateTime().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
// Bouw de HTML-inhoud van de e-mail
String emailBody = """
@@ -206,9 +211,9 @@ public class EmailService {
Company company = appointment.getCompany();
String location = String.format("<br>%s<br>%s %s", company.getAddress(), company.getPostal_code(), company.getCity());
String imageUrl = appointment.getCompany().getImg_href();
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedDate = appointment.getStartDateTime().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());
String formattedTime = String.format("%s:%s", appointment.getStartDateTime().getHour(), String.valueOf(appointment.getStartDateTime().getMinute()).length() == 1 ? "0" + appointment.getStartDateTime().getMinute() : appointment.getStartDateTime().getMinute());
// Bouw de HTML-inhoud van de e-mail
@@ -294,9 +299,9 @@ public class EmailService {
Company company = appointment.getCompany();
String location = String.format("<br>%s<br>%s %s", company.getAddress(), company.getPostal_code(), company.getCity());
String imageUrl = appointment.getCompany().getImg_href();
String formattedDate = appointment.getStartDate().toLocalDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.forLanguageTag("nl")));
String formattedDate = appointment.getStartDateTime().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());
String formattedTime = String.format("%s:%s", appointment.getStartDateTime().getHour(), String.valueOf(appointment.getStartDateTime().getMinute()).length() == 1 ? "0" + appointment.getStartDateTime().getMinute() : appointment.getStartDateTime().getMinute());
// Bouw de HTML-inhoud van de e-mail
@@ -378,7 +383,18 @@ public class EmailService {
mailer.send(Mail.withHtml(recipient, subject, emailBody).setFrom("Hairstyling By Daan <paypoint@melvanveen.nl>").setReplyTo(company.getEmail()));
}
public void stuurUitnodiging(String recipient, String agendaLink) {
Map<String, String> replacements = Map.of(
"link", agendaLink
);
String templatePath = "templates/agenda-invite.html";
String htmlBody = emailTemplateService.loadTemplate(templatePath, replacements);
mailer.send(Mail.withHtml(recipient, "Uitnodiging om agenda te bekijken", htmlBody).setFrom("PayPoint <paypoint@melvanveen.nl>"));
}
}

View File

@@ -0,0 +1,34 @@
package nl.veenm.paypoint.service;
import jakarta.enterprise.context.ApplicationScoped;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
@ApplicationScoped
public class EmailTemplateService {
public String loadTemplate(String templatePath, Map<String, String> replacements) {
try (InputStream is = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(templatePath)) {
if (is == null) {
throw new RuntimeException("Kon e-mailtemplate niet vinden!");
}
String template = new String(is.readAllBytes(), StandardCharsets.UTF_8);
for (Map.Entry<String, String> entry : replacements.entrySet()) {
template = template.replace("{{" + entry.getKey() + "}}", entry.getValue());
}
return template;
} catch (IOException e) {
throw new RuntimeException("Kon e-mailtemplate niet inladen: " + templatePath, e);
}
}
}

View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8"/>
<title>Agenda-uitnodiging</title>
</head>
<body style="margin:0;padding:0;background-color:#f4f4f4;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f4f4;padding:20px 0;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0"
style="background-color:#ffffff;border-radius:8px;padding:30px;font-family:sans-serif;">
<!-- Logo -->
<tr>
<td style="text-align:center;padding-bottom:20px;">
<img
src="https://paypoint.melvanveen.nl/assets/logo-minimal.png"
alt="Agenda logo"
style="max-width:200px;height:auto;margin-bottom:10px;"
/>
</td>
</tr>
<!-- Titel -->
<tr>
<td style="text-align:center;padding-bottom:20px;">
<h2 style="margin:0;color:#1f2937;">Uitnodiging om een agenda te bekijken</h2>
</td>
</tr>
<!-- Inhoud -->
<tr>
<td style="font-size:16px;color:#374151;line-height:1.6;text-align: center;">
<p>
Je bent uitgenodigd om een agenda te bekijken of eraan deel te nemen.
</p>
<p>
Klik op onderstaande knop om toegang te krijgen:
</p>
<p style="text-align:center;margin:30px 0;">
<a
href="{{link}}"
style="background-color:#3b82f6;color:#ffffff;padding:12px 24px;border-radius:6px;text-decoration:none;display:inline-block;font-weight:bold;"
>
Bekijk de agenda
</a>
</p>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="font-size:12px;text-align:center;color:#9ca3af;padding-top:30px;">
Als je deze uitnodiging niet verwachtte, kun je deze e-mail negeren.
<p>&copy; 2025 PayPoint. Alle rechten voorbehouden.</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>