diff --git a/config/application-test.yml b/config/application-test.yml index ef4101ca..989b1293 100644 --- a/config/application-test.yml +++ b/config/application-test.yml @@ -82,4 +82,5 @@ management: health.enabled: true links: - gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf \ No newline at end of file + gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf + passes: passes \ No newline at end of file diff --git a/config/application.yml.example b/config/application.yml.example index 0174c804..7c682f19 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -86,4 +86,8 @@ wisvch.payments: # CH mollie api key mollie: apikey: test - clientUri: http://localhost:8080/events \ No newline at end of file + clientUri: http://localhost:8080/events + +links: + gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf + passes: https://ch.tudelft.nl/passes \ No newline at end of file diff --git a/src/main/java/ch/wisv/events/core/exception/normal/TicketPassFailedException.java b/src/main/java/ch/wisv/events/core/exception/normal/TicketPassFailedException.java new file mode 100644 index 00000000..1e90f3d7 --- /dev/null +++ b/src/main/java/ch/wisv/events/core/exception/normal/TicketPassFailedException.java @@ -0,0 +1,25 @@ +package ch.wisv.events.core.exception.normal; + +import ch.wisv.events.core.exception.LogLevelEnum; + +/** + * TicketPassFailedException class. + */ +public class TicketPassFailedException extends EventsException { + + /** + * TicketPassFailedException. + */ + public TicketPassFailedException() { + super(LogLevelEnum.DEBUG, "Ticket has not been found!"); + } + + /** + * TicketPassFailedException. + * + * @param message of type String + */ + public TicketPassFailedException(String message) { + super(LogLevelEnum.DEBUG, message); + } +} diff --git a/src/main/java/ch/wisv/events/core/model/event/Event.java b/src/main/java/ch/wisv/events/core/model/event/Event.java index 759e8786..b7377781 100644 --- a/src/main/java/ch/wisv/events/core/model/event/Event.java +++ b/src/main/java/ch/wisv/events/core/model/event/Event.java @@ -252,4 +252,12 @@ public boolean isSoldOut() { public boolean hasExternalProductUrl() { return this.externalProductUrl != null && this.externalProductUrl.length() > 0; } + + public String toString() { + return this.title; + } + + public int hashCode() { + return this.id; + } } diff --git a/src/main/java/ch/wisv/events/core/model/product/Product.java b/src/main/java/ch/wisv/events/core/model/product/Product.java index 080e1b40..dc95d8ee 100644 --- a/src/main/java/ch/wisv/events/core/model/product/Product.java +++ b/src/main/java/ch/wisv/events/core/model/product/Product.java @@ -8,6 +8,7 @@ import java.util.UUID; import javax.persistence.*; +import ch.wisv.events.core.model.event.Event; import ch.wisv.events.core.util.VatRate; import lombok.AccessLevel; import lombok.Data; @@ -95,6 +96,14 @@ public class Product { */ public String redirectUrl; + /** + * Field event. + */ + + @ManyToOne + @JoinTable(name = "event_products", joinColumns = @JoinColumn(name = "products_id")) + public Event event; + /** * Field productList. */ @@ -222,4 +231,5 @@ public void increaseReserved(int amount) { public boolean isSoldOut() { return this.maxSold != null && this.sold >= this.maxSold; } + } diff --git a/src/main/java/ch/wisv/events/core/model/ticket/Ticket.java b/src/main/java/ch/wisv/events/core/model/ticket/Ticket.java index 289bba73..866cbef2 100644 --- a/src/main/java/ch/wisv/events/core/model/ticket/Ticket.java +++ b/src/main/java/ch/wisv/events/core/model/ticket/Ticket.java @@ -39,7 +39,7 @@ public class Ticket { /** * Customer which owns the Ticket. */ - @ManyToOne + @ManyToOne(cascade = {javax.persistence.CascadeType.ALL}) public Order order; /** diff --git a/src/main/java/ch/wisv/events/core/service/ticket/TicketService.java b/src/main/java/ch/wisv/events/core/service/ticket/TicketService.java index 90413b1f..b6040816 100644 --- a/src/main/java/ch/wisv/events/core/service/ticket/TicketService.java +++ b/src/main/java/ch/wisv/events/core/service/ticket/TicketService.java @@ -2,6 +2,7 @@ import ch.wisv.events.core.exception.normal.TicketNotFoundException; import ch.wisv.events.core.exception.normal.TicketNotTransferableException; +import ch.wisv.events.core.exception.normal.TicketPassFailedException; import ch.wisv.events.core.model.customer.Customer; import ch.wisv.events.core.model.order.Order; import ch.wisv.events.core.model.product.Product; @@ -124,4 +125,6 @@ public interface TicketService { */ BufferedImage generateQrCode(Ticket ticket) throws WriterException, IllegalArgumentException; + byte[] getApplePass(Ticket ticket) throws TicketPassFailedException; + } diff --git a/src/main/java/ch/wisv/events/core/service/ticket/TicketServiceImpl.java b/src/main/java/ch/wisv/events/core/service/ticket/TicketServiceImpl.java index 973adf13..98e9cbf3 100644 --- a/src/main/java/ch/wisv/events/core/service/ticket/TicketServiceImpl.java +++ b/src/main/java/ch/wisv/events/core/service/ticket/TicketServiceImpl.java @@ -3,6 +3,7 @@ import ch.wisv.events.core.exception.normal.EventNotFoundException; import ch.wisv.events.core.exception.normal.TicketNotFoundException; import ch.wisv.events.core.exception.normal.TicketNotTransferableException; +import ch.wisv.events.core.exception.normal.TicketPassFailedException; import ch.wisv.events.core.model.customer.Customer; import ch.wisv.events.core.model.event.Event; import ch.wisv.events.core.model.order.Order; @@ -13,25 +14,32 @@ import ch.wisv.events.core.repository.TicketRepository; import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.time.format.DateTimeFormatter; +import java.util.*; import ch.wisv.events.core.service.event.EventService; import ch.wisv.events.core.service.mail.MailService; import ch.wisv.events.core.util.QrCode; import com.google.zxing.WriterException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import javax.validation.constraints.NotNull; /** * TicketServiceImpl class. */ @Service public class TicketServiceImpl implements TicketService { - /** TicketRepository. */ + /** + * TicketRepository. + */ private final TicketRepository ticketRepository; - /** EventService. */ + /** + * EventService. + */ private final EventService eventService; /** @@ -39,11 +47,15 @@ public class TicketServiceImpl implements TicketService { */ private final MailService mailService; + @Value("${links.passes}") + @NotNull + private String passesLink; + /** * TicketServiceImpl constructor. * * @param ticketRepository of type TicketRepository - * @param mailService of type MailService + * @param mailService of type MailService */ public TicketServiceImpl(TicketRepository ticketRepository, EventService eventService, MailService mailService) { this.ticketRepository = ticketRepository; @@ -56,7 +68,6 @@ public TicketServiceImpl(TicketRepository ticketRepository, EventService eventSe * * @param product of type Product * @param uniqueCode of type String - * * @return Ticket */ @Override @@ -69,9 +80,7 @@ public Ticket getByUniqueCode(Product product, String uniqueCode) throws TicketN * Get ticket by key. * * @param key of type String - * * @return Ticket - * * @throws TicketNotFoundException when ticket is not found */ @Override @@ -85,7 +94,6 @@ public Ticket getByKey(String key) throws TicketNotFoundException { * * @param product of type Product * @param customer of type Customer - * * @return List of Tickets */ @Override @@ -97,7 +105,6 @@ public List getAllByProductAndCustomer(Product product, Customer custome * Get all Ticket by a Product. * * @param product of type Product - * * @return List of Tickets */ @Override @@ -109,7 +116,6 @@ public List getAllByProduct(Product product) { * Get all Ticket by a Customer. * * @param customer of type Customer - * * @return List of Tickets */ @Override @@ -121,7 +127,6 @@ public List getAllByCustomer(Customer customer) { * Get all Tickets of an Order. * * @param order of type Order - * * @return List of Tickets */ @Override @@ -167,7 +172,6 @@ public List getAll() { * Create a Ticket by an OrderProduct. * * @param order of type Order - * * @return List of Ticket */ @Override @@ -199,7 +203,6 @@ public List createByOrder(Order order) { * Generate a Ticket unique String. * * @param product of type Product - * * @return String */ private String generateUniqueString(Product product) { @@ -215,10 +218,11 @@ private String generateUniqueString(Product product) { /** * Generate a QR code from the uniqueCode. + * * @param ticket of type Ticket - * @throws WriterException when QR code is not generated - * @throws IllegalArgumentException when uniqueCode is not a valid UUID. * @return BufferedImage + * @throws WriterException when QR code is not generated + * @throws IllegalArgumentException when uniqueCode is not a valid UUID. */ public BufferedImage generateQrCode(Ticket ticket) throws IllegalArgumentException, WriterException { // Assert that the uniqueCode is a UUID (LEGACY CHECK) @@ -231,8 +235,9 @@ public BufferedImage generateQrCode(Ticket ticket) throws IllegalArgumentExcepti /** * Transfer a Ticket to another Customer. + * * @param currentCustomer of type Customer - * @param newCustomer of type Customer + * @param newCustomer of type Customer */ public void transfer(Ticket ticket, Customer currentCustomer, Customer newCustomer) throws TicketNotTransferableException { // Get event from ticket product @@ -258,4 +263,26 @@ public void transfer(Ticket ticket, Customer currentCustomer, Customer newCustom mailService.sendTransferConfirmation(ticket, currentCustomer, newCustomer); } + public byte[] getApplePass(Ticket ticket) throws TicketPassFailedException { + try { + RestTemplate restTemplate = new RestTemplate(); + + + Map params = new HashMap<>(); + params.put("name", ticket.getProduct().getTitle()); + params.put("description", ticket.getProduct().getDescription()); + // Format date as yyyy-MM-dd + params.put("date", ticket.getProduct().getEvent().getStart().format(DateTimeFormatter.ISO_LOCAL_DATE)); + params.put("time", ticket.getProduct().getEvent().getStart().format(DateTimeFormatter.ISO_LOCAL_TIME)); + params.put("location", ticket.getProduct().getEvent().getLocation()); + params.put("code", ticket.getUniqueCode()); + + return restTemplate.getForObject(passesLink + + "?name={name}&description={description}&date={date}&time={time}&location={location}&code={code}" + , byte[].class, params); + } catch (Exception e) { + e.printStackTrace(); + throw new TicketPassFailedException(e.getMessage()); + } + } } diff --git a/src/main/java/ch/wisv/events/utils/dev/data/ProductTestDataRunner.java b/src/main/java/ch/wisv/events/utils/dev/data/ProductTestDataRunner.java index 4c123e08..f081a840 100644 --- a/src/main/java/ch/wisv/events/utils/dev/data/ProductTestDataRunner.java +++ b/src/main/java/ch/wisv/events/utils/dev/data/ProductTestDataRunner.java @@ -44,7 +44,7 @@ private Product createProduct(JSONObject jsonObject) { (String) jsonObject.get("title"), (String) jsonObject.get("description"), (Double) jsonObject.get("cost"), - (VatRate) jsonObject.get("vatRate"), + VatRate.valueOf((String) jsonObject.get("vatRate")), ((Long) jsonObject.get("maxSold")).intValue(), LocalDateTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES), LocalDateTime.now().plusDays(days).truncatedTo(ChronoUnit.MINUTES) diff --git a/src/main/java/ch/wisv/events/webshop/controller/WebshopTicketController.java b/src/main/java/ch/wisv/events/webshop/controller/WebshopTicketController.java index b3b91dc5..98af38f8 100644 --- a/src/main/java/ch/wisv/events/webshop/controller/WebshopTicketController.java +++ b/src/main/java/ch/wisv/events/webshop/controller/WebshopTicketController.java @@ -3,6 +3,7 @@ import ch.wisv.events.core.exception.normal.CustomerNotFoundException; import ch.wisv.events.core.exception.normal.TicketNotFoundException; import ch.wisv.events.core.exception.normal.TicketNotTransferableException; +import ch.wisv.events.core.exception.normal.TicketPassFailedException; import ch.wisv.events.core.model.customer.Customer; import ch.wisv.events.core.model.ticket.Ticket; import ch.wisv.events.core.service.auth.AuthenticationService; @@ -158,4 +159,33 @@ public void getQrCode(HttpServletResponse response, @PathVariable String key) th response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } + + /** + * Get wallet pass of ticket. + */ + @GetMapping("/{key}/wallet.pkpass") + public void getApplePass(HttpServletResponse response, @PathVariable String key) throws IOException { + Customer customer = authenticationService.getCurrentCustomer(); + try { + Ticket ticket = ticketService.getByKey(key); + + if (!ticket.owner.equals(customer)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + byte[] bytes = ticketService.getApplePass(ticket); + + response.setContentType("application/vnd.apple.pkpass"); + response.setContentLength(bytes.length); + response.getOutputStream().write(bytes); + response.getOutputStream().close(); + } + catch (TicketNotFoundException e) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + catch (TicketPassFailedException | IOException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } } diff --git a/src/main/resources/dev/data/products.json b/src/main/resources/dev/data/products.json index 13e55287..5cad3010 100644 --- a/src/main/resources/dev/data/products.json +++ b/src/main/resources/dev/data/products.json @@ -3,6 +3,7 @@ "title": "T.U.E.S.Day lecture ticket", "description": "Ticket for the T.U.E.S.Day lecture.", "cost": 0.00, + "vatRate": "VAT_HIGH", "maxSold": 5, "maxSoldPerCustomer": 1, "key": "dd729c96-dacf-4eab-a2e8-19d2a1ad917c" @@ -11,6 +12,7 @@ "title": "Symposium ticket", "description": "Ticket for the Symposium.", "cost": 5.00, + "vatRate": "VAT_HIGH", "maxSold": 5, "maxSoldPerCustomer": 5, "key": "cfc66e3d-cf77-441d-a04a-9e81bd3e1117" @@ -19,6 +21,7 @@ "title": "WIFI Rally ticket", "description": "Ticket for the WIFI Rally.", "cost": 15.00, + "vatRate": "VAT_HIGH", "maxSold": 5, "maxSoldPerCustomer": 1, "key": "f0d3a2a5-150e-4799-8679-db6778edb2a9" @@ -27,6 +30,7 @@ "title": "MatCH ticket", "description": "Ticket for the MatCH activity.", "cost": 1.00, + "vatRate": "VAT_HIGH", "maxSold": 5, "maxSoldPerCustomer": 5, "key": "8d7900db-07d6-4922-91da-55e80daec441" @@ -35,6 +39,7 @@ "title": "Annuarium", "description": "", "cost": 1.00, + "vatRate": "VAT_HIGH", "maxSold": 250, "maxSoldPerCustomer": 25, "key": "7a5b9158-a9e4-40f5-ad7f-586092274e63" diff --git a/src/main/resources/static/images/apple-wallet.svg b/src/main/resources/static/images/apple-wallet.svg new file mode 100644 index 00000000..6084d20b --- /dev/null +++ b/src/main/resources/static/images/apple-wallet.svg @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/webshop/overview/index.html b/src/main/resources/templates/webshop/overview/index.html index bd9713e9..b500374b 100644 --- a/src/main/resources/templates/webshop/overview/index.html +++ b/src/main/resources/templates/webshop/overview/index.html @@ -216,6 +216,10 @@
Open tickets