From a497b8162b02734ffcc7ce3413ad861e20be380e Mon Sep 17 00:00:00 2001 From: veenm Date: Fri, 18 Apr 2025 22:41:17 +0200 Subject: [PATCH] Update: -delen van agenda toegevoegd -popup aangepast -accepteren van uitnodiging toegevoegd -koppelen van gebruiker aan bedrijf -versturen van uitnodiging via mail --- .github/workflows/deploy-docker.yml | 90 ++++++++++++++++ .github/workflows/increase-version.yml | 44 ++++++++ Dockerfile | 4 +- .../nl/veenm/paypoint/domain/AccessLevel.java | 7 ++ .../nl/veenm/paypoint/domain/AppUser.java | 17 ++- .../nl/veenm/paypoint/domain/Company.java | 14 ++- .../veenm/paypoint/domain/InviteEntity.java | 100 ++++++++++++++++++ .../veenm/paypoint/domain/InviteRequest.java | 7 ++ .../nl/veenm/paypoint/domain/UrlRequest.java | 5 + .../nl/veenm/paypoint/domain/UserCompany.java | 60 +++++++++++ .../veenm/paypoint/domain/dto/AppUserDTO.java | 78 ++++++++++++++ .../veenm/paypoint/domain/dto/CompanyDTO.java | 67 ++++++++++++ .../paypoint/domain/dto/UserCompanyDTO.java | 33 ++++++ .../paypoint/domain/mapper/AppUserMapper.java | 55 ++++++++++ .../paypoint/domain/mapper/CompanyMapper.java | 39 +++++++ .../repository/AppointmentRepository.java | 7 +- .../paypoint/repository/InviteRepository.java | 12 +++ .../repository/UserCompanyRepository.java | 15 +++ .../paypoint/resource/AgendaResource.java | 40 +++++++ .../resource/AppointmentResource.java | 2 + .../veenm/paypoint/resource/AuthResource.java | 12 +-- .../paypoint/resource/CompanyResource.java | 20 ++-- .../veenm/paypoint/service/AgendaService.java | 65 ++++++++++++ .../paypoint/service/AppointmentService.java | 15 ++- .../paypoint/service/CompanyService.java | 45 +++++--- .../veenm/paypoint/service/EmailService.java | 16 +++ .../service/EmailTemplateService.java | 28 +++++ .../resources/templates/agenda-invite.html | 66 ++++++++++++ 28 files changed, 908 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/deploy-docker.yml create mode 100644 .github/workflows/increase-version.yml create mode 100644 src/main/java/nl/veenm/paypoint/domain/AccessLevel.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/InviteEntity.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/InviteRequest.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/UrlRequest.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/UserCompany.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/dto/AppUserDTO.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/dto/CompanyDTO.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/dto/UserCompanyDTO.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/mapper/AppUserMapper.java create mode 100644 src/main/java/nl/veenm/paypoint/domain/mapper/CompanyMapper.java create mode 100644 src/main/java/nl/veenm/paypoint/repository/InviteRepository.java create mode 100644 src/main/java/nl/veenm/paypoint/repository/UserCompanyRepository.java create mode 100644 src/main/java/nl/veenm/paypoint/resource/AgendaResource.java create mode 100644 src/main/java/nl/veenm/paypoint/service/AgendaService.java create mode 100644 src/main/java/nl/veenm/paypoint/service/EmailTemplateService.java create mode 100644 src/main/resources/templates/agenda-invite.html diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml new file mode 100644 index 0000000..bc2a988 --- /dev/null +++ b/.github/workflows/deploy-docker.yml @@ -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 diff --git a/.github/workflows/increase-version.yml b/.github/workflows/increase-version.yml new file mode 100644 index 0000000..9cd06fd --- /dev/null +++ b/.github/workflows/increase-version.yml @@ -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 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index fa7f4e0..c9ff1fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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" ] diff --git a/src/main/java/nl/veenm/paypoint/domain/AccessLevel.java b/src/main/java/nl/veenm/paypoint/domain/AccessLevel.java new file mode 100644 index 0000000..63a5b45 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/AccessLevel.java @@ -0,0 +1,7 @@ +package nl.veenm.paypoint.domain; + +public enum AccessLevel { + READ_ONLY, + USER +} + diff --git a/src/main/java/nl/veenm/paypoint/domain/AppUser.java b/src/main/java/nl/veenm/paypoint/domain/AppUser.java index d098fd4..89ee7ae 100644 --- a/src/main/java/nl/veenm/paypoint/domain/AppUser.java +++ b/src/main/java/nl/veenm/paypoint/domain/AppUser.java @@ -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 companies = new HashSet<>(); + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Set userCompanies = new HashSet<>(); public Long getId() { return id; @@ -81,12 +76,12 @@ public class AppUser { this.lastName = lastName; } - public Set getCompanies() { - return companies; + public Set getUserCompanies() { + return userCompanies; } - public void setCompanies(Set companies) { - this.companies = companies; + public void setUserCompanies(Set userCompanies) { + this.userCompanies = userCompanies; } @Override diff --git a/src/main/java/nl/veenm/paypoint/domain/Company.java b/src/main/java/nl/veenm/paypoint/domain/Company.java index 900e237..ccf7925 100644 --- a/src/main/java/nl/veenm/paypoint/domain/Company.java +++ b/src/main/java/nl/veenm/paypoint/domain/Company.java @@ -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 users = new HashSet<>(); + @OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval = true) + private Set userCompanies = new HashSet<>(); public void setId(Long id) { this.id = id; @@ -78,11 +76,11 @@ public class Company { this.city = city; } - public Set getUsers() { - return users; + public Set getUserCompanies() { + return userCompanies; } - public void setUsers(Set users) { - this.users = users; + public void setUserCompanies(Set userCompanies) { + this.userCompanies = userCompanies; } } diff --git a/src/main/java/nl/veenm/paypoint/domain/InviteEntity.java b/src/main/java/nl/veenm/paypoint/domain/InviteEntity.java new file mode 100644 index 0000000..abcb586 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/InviteEntity.java @@ -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 diff --git a/src/main/java/nl/veenm/paypoint/domain/InviteRequest.java b/src/main/java/nl/veenm/paypoint/domain/InviteRequest.java new file mode 100644 index 0000000..2bf7b66 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/InviteRequest.java @@ -0,0 +1,7 @@ +package nl.veenm.paypoint.domain; + +public class InviteRequest { + public Long companyId; + public String email; +} + diff --git a/src/main/java/nl/veenm/paypoint/domain/UrlRequest.java b/src/main/java/nl/veenm/paypoint/domain/UrlRequest.java new file mode 100644 index 0000000..f71a914 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/UrlRequest.java @@ -0,0 +1,5 @@ +package nl.veenm.paypoint.domain; + +public class UrlRequest { + public String url; +} diff --git a/src/main/java/nl/veenm/paypoint/domain/UserCompany.java b/src/main/java/nl/veenm/paypoint/domain/UserCompany.java new file mode 100644 index 0000000..be77549 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/UserCompany.java @@ -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; + } +} + diff --git a/src/main/java/nl/veenm/paypoint/domain/dto/AppUserDTO.java b/src/main/java/nl/veenm/paypoint/domain/dto/AppUserDTO.java new file mode 100644 index 0000000..3c5d96c --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/dto/AppUserDTO.java @@ -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 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 getCompanies() { + return companies; + } + + public void setCompanies(Set companies) { + this.companies = companies; + } +} diff --git a/src/main/java/nl/veenm/paypoint/domain/dto/CompanyDTO.java b/src/main/java/nl/veenm/paypoint/domain/dto/CompanyDTO.java new file mode 100644 index 0000000..d437a3b --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/dto/CompanyDTO.java @@ -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; + } +} diff --git a/src/main/java/nl/veenm/paypoint/domain/dto/UserCompanyDTO.java b/src/main/java/nl/veenm/paypoint/domain/dto/UserCompanyDTO.java new file mode 100644 index 0000000..0f7f452 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/dto/UserCompanyDTO.java @@ -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; + } +} diff --git a/src/main/java/nl/veenm/paypoint/domain/mapper/AppUserMapper.java b/src/main/java/nl/veenm/paypoint/domain/mapper/AppUserMapper.java new file mode 100644 index 0000000..bbd6a35 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/mapper/AppUserMapper.java @@ -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 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; + } + +} diff --git a/src/main/java/nl/veenm/paypoint/domain/mapper/CompanyMapper.java b/src/main/java/nl/veenm/paypoint/domain/mapper/CompanyMapper.java new file mode 100644 index 0000000..d32e4d2 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/domain/mapper/CompanyMapper.java @@ -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 toDtoList(Collection companies) { + if (companies == null) { + return Collections.emptyList(); + } + + return companies.stream() + .map(CompanyMapper::toDto) + .collect(Collectors.toList()); + } +} + diff --git a/src/main/java/nl/veenm/paypoint/repository/AppointmentRepository.java b/src/main/java/nl/veenm/paypoint/repository/AppointmentRepository.java index 9f2b5c9..418d1c3 100644 --- a/src/main/java/nl/veenm/paypoint/repository/AppointmentRepository.java +++ b/src/main/java/nl/veenm/paypoint/repository/AppointmentRepository.java @@ -26,10 +26,9 @@ public class AppointmentRepository implements PanacheRepository { return find("company = ?1", company).list(); } - public List findAppointmentsForCompanies(Set 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 findAppointmentsForCompanies(Set companyIds, LocalDateTime startDate, LocalDateTime endDate) { + return find("SELECT a FROM Appointment a WHERE a.company.id IN :companyIds AND a.startDate BETWEEN :start AND :end", + Parameters.with("companyIds", companyIds).and("start", startDate).and("end", endDate)) .list(); - } } diff --git a/src/main/java/nl/veenm/paypoint/repository/InviteRepository.java b/src/main/java/nl/veenm/paypoint/repository/InviteRepository.java new file mode 100644 index 0000000..1375626 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/repository/InviteRepository.java @@ -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 { + public InviteEntity findByToken(String token) { + return find("token", token).firstResult(); + } +} diff --git a/src/main/java/nl/veenm/paypoint/repository/UserCompanyRepository.java b/src/main/java/nl/veenm/paypoint/repository/UserCompanyRepository.java new file mode 100644 index 0000000..d5c252b --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/repository/UserCompanyRepository.java @@ -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 { + public List getAllByUserId(AppUser user) { + return find("user", user).list(); + } +} diff --git a/src/main/java/nl/veenm/paypoint/resource/AgendaResource.java b/src/main/java/nl/veenm/paypoint/resource/AgendaResource.java new file mode 100644 index 0000000..db9fd67 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/resource/AgendaResource.java @@ -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); + } + + +} diff --git a/src/main/java/nl/veenm/paypoint/resource/AppointmentResource.java b/src/main/java/nl/veenm/paypoint/resource/AppointmentResource.java index ef62ebe..edaadbe 100644 --- a/src/main/java/nl/veenm/paypoint/resource/AppointmentResource.java +++ b/src/main/java/nl/veenm/paypoint/resource/AppointmentResource.java @@ -31,7 +31,9 @@ public class AppointmentResource { @Produces(MediaType.APPLICATION_JSON) @Path("/date") public List getAppointmentsByDate(@QueryParam("start") String start) { + System.out.println("getting appointments from " + start); String user = jwt.getClaim("username"); + System.out.println("user " + user); return appointmentService.getAppointmentsByDate(start, user); } diff --git a/src/main/java/nl/veenm/paypoint/resource/AuthResource.java b/src/main/java/nl/veenm/paypoint/resource/AuthResource.java index a9c6295..3cec84c 100644 --- a/src/main/java/nl/veenm/paypoint/resource/AuthResource.java +++ b/src/main/java/nl/veenm/paypoint/resource/AuthResource.java @@ -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; @@ -46,15 +47,12 @@ public class AuthResource { @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(); } diff --git a/src/main/java/nl/veenm/paypoint/resource/CompanyResource.java b/src/main/java/nl/veenm/paypoint/resource/CompanyResource.java index d2a075c..e5f0751 100644 --- a/src/main/java/nl/veenm/paypoint/resource/CompanyResource.java +++ b/src/main/java/nl/veenm/paypoint/resource/CompanyResource.java @@ -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,19 @@ public class CompanyResource { JsonWebToken jwt; @GET - public Set getCompanies() { + public List 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); - this.companyService.linkCompany(userId, companyId); + @Path("/link") + public void linkCompany(@QueryParam("user") Long userId, @QueryParam("token") String token) { + this.companyService.linkCompany(userId, token); } } diff --git a/src/main/java/nl/veenm/paypoint/service/AgendaService.java b/src/main/java/nl/veenm/paypoint/service/AgendaService.java new file mode 100644 index 0000000..65eeb80 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/service/AgendaService.java @@ -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(); + } +} diff --git a/src/main/java/nl/veenm/paypoint/service/AppointmentService.java b/src/main/java/nl/veenm/paypoint/service/AppointmentService.java index 64800a1..00106b0 100644 --- a/src/main/java/nl/veenm/paypoint/service/AppointmentService.java +++ b/src/main/java/nl/veenm/paypoint/service/AppointmentService.java @@ -7,6 +7,10 @@ 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.CompanyDTO; +import nl.veenm.paypoint.domain.dto.UserCompanyDTO; +import nl.veenm.paypoint.domain.mapper.AppUserMapper; import nl.veenm.paypoint.repository.AppointmentRepository; import nl.veenm.paypoint.repository.CompanyRepository; import nl.veenm.paypoint.repository.UserRepository; @@ -14,6 +18,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 { @@ -52,12 +58,19 @@ public class AppointmentService { public List getAppointmentsByDate(String start, String username) { LocalDate date = LocalDate.parse(start); AppUser user = userRepository.findByUsername(username); + System.out.println("user " + user); + AppUserDTO appUserDTO = AppUserMapper.toDTO(user); + System.out.println("appUserDTO " + appUserDTO); + Set companies = appUserDTO.getCompanies().stream().map(UserCompanyDTO::getCompany).map(CompanyDTO::getId).collect(Collectors.toSet()); + System.out.println("companies " + companies); LocalDateTime startOfDay = date.atStartOfDay(); LocalDateTime endOfDay = date.atTime(23, 59, 59); - return appointmentRepository.findAppointmentsForCompanies(user.getCompanies(), startOfDay, endOfDay); + List appointmentsForCompanies = appointmentRepository.findAppointmentsForCompanies(companies, startOfDay, endOfDay); + System.out.println(appointmentsForCompanies); + return appointmentsForCompanies; } @Transactional diff --git a/src/main/java/nl/veenm/paypoint/service/CompanyService.java b/src/main/java/nl/veenm/paypoint/service/CompanyService.java index 1856e3f..a3cd69f 100644 --- a/src/main/java/nl/veenm/paypoint/service/CompanyService.java +++ b/src/main/java/nl/veenm/paypoint/service/CompanyService.java @@ -3,38 +3,53 @@ 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 getCompanies(String username) { - AppUser user = this.userRepository.findByUsername(username); - return user.getCompanies(); + @Inject + InviteRepository inviteRepository; + + public List 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 linkCompany(Long userId, Long companyId) { - AppUser user = this.userRepository.findById(userId); - Company company = this.companyRepository.findById(companyId); + public void linkCompany(Long userId, String token) { + AppUser user = userRepository.findById(userId); + InviteEntity invite = inviteRepository.findByToken(token); + Company company = companyRepository.findById(invite.company_id); - System.out.println(user.getCompanies()); - user.getCompanies().add(company); - company.getUsers().add(user); + UserCompany userCompany = new UserCompany(); + userCompany.setUser(user); + userCompany.setCompany(company); + userCompany.setAccessLevel(AccessLevel.USER); + userCompanyRepository.persist(userCompany); - System.out.println(user.getCompanies()); + invite.used = true; + inviteRepository.persist(invite); - userRepository.persist(user); - companyRepository.persist(company); } } diff --git a/src/main/java/nl/veenm/paypoint/service/EmailService.java b/src/main/java/nl/veenm/paypoint/service/EmailService.java index 79ef9e9..402a831 100644 --- a/src/main/java/nl/veenm/paypoint/service/EmailService.java +++ b/src/main/java/nl/veenm/paypoint/service/EmailService.java @@ -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; } @@ -378,7 +383,18 @@ public class EmailService { mailer.send(Mail.withHtml(recipient, subject, emailBody).setFrom("Hairstyling By Daan ").setReplyTo(company.getEmail())); } + public void stuurUitnodiging(String recipient, String agendaLink) { + Map replacements = Map.of( + "link", agendaLink + ); + + String templatePath = "src/main/resources/templates/agenda-invite.html"; + String htmlBody = emailTemplateService.loadTemplate(templatePath, replacements); + + mailer.send(Mail.withHtml(recipient, "Uitnodiging om agenda te bekijken", htmlBody).setFrom("PayPoint ")); + } } + diff --git a/src/main/java/nl/veenm/paypoint/service/EmailTemplateService.java b/src/main/java/nl/veenm/paypoint/service/EmailTemplateService.java new file mode 100644 index 0000000..4f1c9b0 --- /dev/null +++ b/src/main/java/nl/veenm/paypoint/service/EmailTemplateService.java @@ -0,0 +1,28 @@ +package nl.veenm.paypoint.service; + +import jakarta.enterprise.context.ApplicationScoped; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@ApplicationScoped +public class EmailTemplateService { + + public String loadTemplate(String templatePath, Map replacements) { + try { + String content = Files.readString(Path.of(templatePath), StandardCharsets.UTF_8); + + for (Map.Entry entry : replacements.entrySet()) { + content = content.replace("{{" + entry.getKey() + "}}", entry.getValue()); + } + + return content; + } catch (IOException e) { + throw new RuntimeException("Kon e-mailtemplate niet inladen: " + templatePath, e); + } + } +} + diff --git a/src/main/resources/templates/agenda-invite.html b/src/main/resources/templates/agenda-invite.html new file mode 100644 index 0000000..50b4583 --- /dev/null +++ b/src/main/resources/templates/agenda-invite.html @@ -0,0 +1,66 @@ + + + + + Agenda-uitnodiging + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ Agenda logo +
+

Uitnodiging om een agenda te bekijken

+
+

+ Je bent uitgenodigd om een agenda te bekijken of eraan deel te nemen. +

+

+ Klik op onderstaande knop om toegang te krijgen: +

+ +

+ + Bekijk de agenda + +

+
+ Als je deze uitnodiging niet verwachtte, kun je deze e-mail negeren. +

© 2025 PayPoint. Alle rechten voorbehouden.

+
+
+ +