From 85d7162419a0be6b695919e80591bbe201650254 Mon Sep 17 00:00:00 2001 From: Jonas Weissengruber Date: Wed, 20 Dec 2023 00:41:20 +0100 Subject: [PATCH] beidl message --- http-requests/cart.http | 21 +++++++ http-requests/category.http | 34 ++++++++++ http-requests/event.http | 53 ++++++++++++++++ http-requests/http-client.env.json | 3 +- http-requests/user.http | 24 +++++-- pom.xml | 4 ++ .../jweissen/aeticket/aspect/AdminOnly.java | 11 ++++ .../jweissen/aeticket/aspect/AuthAspect.java | 63 +++++++++++++++++++ .../me/jweissen/aeticket/aspect/UserOnly.java | 11 ++++ .../aeticket/controller/CartController.java | 29 ++++----- .../controller/CategoryController.java | 14 ++--- .../aeticket/controller/EventController.java | 8 +-- .../aeticket/controller/UserController.java | 14 ++--- .../dto/request/EventUpdateRequestDto.java | 5 +- .../dto/request/UserUpdateRequestDto.java | 2 +- .../java/me/jweissen/aeticket/model/Cart.java | 3 + .../me/jweissen/aeticket/model/CartEntry.java | 10 ++- .../me/jweissen/aeticket/model/Event.java | 6 +- .../java/me/jweissen/aeticket/model/User.java | 9 +++ .../aeticket/repository/CartRepository.java | 4 ++ .../repository/CategoryRepository.java | 4 ++ .../aeticket/repository/UserRepository.java | 2 +- .../aeticket/service/AuthService.java | 43 ++++++++++--- .../aeticket/service/CartService.java | 50 +++++++++++++-- .../aeticket/service/CategoryService.java | 5 ++ .../aeticket/service/EventService.java | 36 +++++++---- .../jweissen/aeticket/service/JwtService.java | 21 ++++--- .../aeticket/service/UserService.java | 8 ++- 28 files changed, 413 insertions(+), 84 deletions(-) create mode 100644 http-requests/cart.http create mode 100644 http-requests/category.http create mode 100644 http-requests/event.http create mode 100644 src/main/java/me/jweissen/aeticket/aspect/AdminOnly.java create mode 100644 src/main/java/me/jweissen/aeticket/aspect/AuthAspect.java create mode 100644 src/main/java/me/jweissen/aeticket/aspect/UserOnly.java diff --git a/http-requests/cart.http b/http-requests/cart.http new file mode 100644 index 0000000..1556750 --- /dev/null +++ b/http-requests/cart.http @@ -0,0 +1,21 @@ +### add +POST {{url}}/cart/add +Content-Type: application/json + +{ + "id": 9999999, + "cartEntries": [ + { + "id": 2, + "amount": 20 + } + ] +} + +### list +GET {{url}}/user/signin + + +### checkout +GET {{url}}/user/update +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/http-requests/category.http b/http-requests/category.http new file mode 100644 index 0000000..0df78f1 --- /dev/null +++ b/http-requests/category.http @@ -0,0 +1,34 @@ +### create +POST {{url}}/category/create +Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "name": "Erwachsene", + "price": 25.5, + "stock": 100 +} + +### update +PUT {{url}}/category/update +Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "id": 1, + "name": "Erwachsene", + "price": 25.5, + "stock": 100 +} + +### delete +DELETE {{url}}/category/delete/1 +Authorization: Bearer {{token}} + +### list +GET {{url}}/category/list +Authorization: Bearer {{token}} + +### load +GET {{url}}/category/2 +Authorization: Bearer {{token}} diff --git a/http-requests/event.http b/http-requests/event.http new file mode 100644 index 0000000..f83572a --- /dev/null +++ b/http-requests/event.http @@ -0,0 +1,53 @@ +### create +POST {{url}}/event/create +# Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "name": "Maturaball HTL Steyr 2024", + "from": "2023-03-02T20:00:00.000Z", + "to": "2023-03-03T05:00:00.000Z", + "description": "Maturaball der Abteilungen EL, IT, ME, Y", + "ticketCategories": [ + { + "name": "Normal", + "price": 25, + "stock": 500 + }, + { + "name": "Maturanten", + "price": 0, + "stock": 500 + }, + { + "name": "Prechtl", + "price": 50, + "stock": 1 + } + ] +} + +### update +PUT {{url}}/event/update +Authorization: Bearer {{token}} +Content-Type: application/json + +{ + "id": 2, + "name": "Maturaball HTL Steyr 2024", + "from": "2024-03-02T20:00:00.000Z", + "to": "2024-03-03T05:00:00.000Z", + "description": "Fluch der HTL" +} + +### delete +DELETE {{url}}/event/delete/3 +Authorization: Bearer {{token}} + +### list future events +GET {{url}}/event/list +Authorization: Bearer {{token}} + +### load +GET {{url}}/event/2 +Authorization: Bearer {{token}} diff --git a/http-requests/http-client.env.json b/http-requests/http-client.env.json index 9dd49de..1d5dd67 100644 --- a/http-requests/http-client.env.json +++ b/http-requests/http-client.env.json @@ -1,5 +1,6 @@ { "dev": { - "url": "http://localhost:8080/api/v1" + "url": "http://localhost:8080/api/v1", + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZXRpY2tldCB1c2VyIHRva2VuIiwiZXhwIjoxNzAzMTAwNzQyLCJ1c2VySWQiOjIsImlhdCI6MTcwMzAxNDM0Mn0.D9HBxWy1vIP82XOh_ocjLO9HB0lK_rQGjgD3a7KQrOE" } } \ No newline at end of file diff --git a/http-requests/user.http b/http-requests/user.http index bed80a3..ec6afb4 100644 --- a/http-requests/user.http +++ b/http-requests/user.http @@ -10,22 +10,34 @@ Content-Type: application/json } ### signin -GET {{url}}/user/signin +POST {{url}}/user/signin +Content-Type: application/json + +{ + "email": "admin@email.com", + "password": "a" +} + ### update PUT {{url}}/user/update +Authorization: Bearer {{token}} +Content-Type: application/json { - + "email": "ratp@htl-steyr.ac.at", + "firstname": "Peter Chef", + "lastname": "Rathgeb" } ### delete -DELETE {{url}}/user/delete/99 +DELETE {{url}}/user/delete/1 +Authorization: Bearer {{token}} ### list GET {{url}}/user/list +Authorization: Bearer {{token}} ### load -GET {{url}}/user/load/1 - -# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZXRpY2tldCB1c2VyIHRva2VuIiwiZXhwIjoxNzAzMDU4MjczLCJ1c2VySWQiOjEsImlhdCI6MTcwMjk3MTg3M30.hC1N7hKYSIT-fqmaZ9bv3-YXOxQWdp-Sb5rZi4rARc0 +GET {{url}}/user/load/2 +Authorization: Bearer {{token}} diff --git a/pom.xml b/pom.xml index 3ac91fc..47d3379 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-aop + com.auth0 java-jwt diff --git a/src/main/java/me/jweissen/aeticket/aspect/AdminOnly.java b/src/main/java/me/jweissen/aeticket/aspect/AdminOnly.java new file mode 100644 index 0000000..6c9aea9 --- /dev/null +++ b/src/main/java/me/jweissen/aeticket/aspect/AdminOnly.java @@ -0,0 +1,11 @@ +package me.jweissen.aeticket.aspect; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AdminOnly { +} diff --git a/src/main/java/me/jweissen/aeticket/aspect/AuthAspect.java b/src/main/java/me/jweissen/aeticket/aspect/AuthAspect.java new file mode 100644 index 0000000..5c26608 --- /dev/null +++ b/src/main/java/me/jweissen/aeticket/aspect/AuthAspect.java @@ -0,0 +1,63 @@ +package me.jweissen.aeticket.aspect; + +import jakarta.servlet.http.HttpServletRequest; +import me.jweissen.aeticket.model.Role; +import me.jweissen.aeticket.model.User; +import me.jweissen.aeticket.service.AuthService; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Aspect +@Component +public class AuthAspect { + private final HttpServletRequest request; + private final AuthService authService; + + public AuthAspect(HttpServletRequest request, AuthService authService) { + this.request = request; + this.authService = authService; + } + + @Pointcut("@annotation(UserOnly)") + public void userOnly() { } + + @Pointcut("@annotation(AdminOnly)") + public void adminOnly() { } + + public Optional authenticate() { + String tokenPrefix = "Bearer "; + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith(tokenPrefix)) { + return Optional.empty(); + } + String token = authHeader.substring(tokenPrefix.length()); + return authService.authenticate(token); + } + + @Around("userOnly()") + public Object checkUser(ProceedingJoinPoint pjp) throws Throwable { + Optional user = authenticate(); + if (user.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + return pjp.proceed(); + } + + @Around("adminOnly()") + public Object checkAdmin(ProceedingJoinPoint pjp) throws Throwable { + Optional user = authenticate(); + if (user.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else if (user.get().getRole() != Role.ADMIN) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + return pjp.proceed(); + } +} diff --git a/src/main/java/me/jweissen/aeticket/aspect/UserOnly.java b/src/main/java/me/jweissen/aeticket/aspect/UserOnly.java new file mode 100644 index 0000000..043470c --- /dev/null +++ b/src/main/java/me/jweissen/aeticket/aspect/UserOnly.java @@ -0,0 +1,11 @@ +package me.jweissen.aeticket.aspect; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface UserOnly { +} diff --git a/src/main/java/me/jweissen/aeticket/controller/CartController.java b/src/main/java/me/jweissen/aeticket/controller/CartController.java index c8bcc52..104669c 100644 --- a/src/main/java/me/jweissen/aeticket/controller/CartController.java +++ b/src/main/java/me/jweissen/aeticket/controller/CartController.java @@ -1,15 +1,13 @@ package me.jweissen.aeticket.controller; import me.jweissen.aeticket.dto.request.CartAddRequestDto; -import me.jweissen.aeticket.dto.response.CartEntryResponseDto; +import me.jweissen.aeticket.dto.response.CartEventResponseDto; import me.jweissen.aeticket.dto.response.CheckoutResponseDto; +import me.jweissen.aeticket.service.AuthService; import me.jweissen.aeticket.service.CartService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -17,26 +15,29 @@ import java.util.List; @RequestMapping("/cart") public class CartController { private final CartService cartService; + private final AuthService authService; - public CartController(CartService cartService) { + public CartController(CartService cartService, AuthService authService) { this.cartService = cartService; + this.authService = authService; } @PostMapping("/add") - public ResponseEntity addEntry(CartAddRequestDto dto) { - // TODO - return ResponseEntity.noContent().build(); + public ResponseEntity addEntry(@RequestBody CartAddRequestDto dto) { + if (!cartService.add(dto, authService.getCurrentUser().getCurrentCart())) { + // user gave invalid category id(s) + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @GetMapping("/list") - public ResponseEntity> getCartEntries() { - // TODO - return new ResponseEntity<>(null, HttpStatus.OK); + public ResponseEntity> getCartEntries() { + return new ResponseEntity<>(cartService.toDto(authService.getCurrentUser().getCurrentCart()), HttpStatus.OK); } @GetMapping("/checkout") public ResponseEntity checkout() { - // TODO - return ResponseEntity.badRequest().build(); + return new ResponseEntity<>(cartService.checkout(authService.getCurrentUser().getCurrentCart()), HttpStatus.OK); } } diff --git a/src/main/java/me/jweissen/aeticket/controller/CategoryController.java b/src/main/java/me/jweissen/aeticket/controller/CategoryController.java index 969e6bb..aabfc5f 100644 --- a/src/main/java/me/jweissen/aeticket/controller/CategoryController.java +++ b/src/main/java/me/jweissen/aeticket/controller/CategoryController.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; @RestController -@RequestMapping("/eventcategory") +@RequestMapping("/category") public class CategoryController { private final CategoryService categoryService; @@ -26,35 +26,35 @@ public class CategoryController { public ResponseEntity create(@RequestBody CategoryRequestDto dto) { // TODO admin only categoryService.create(dto); - return new ResponseEntity<>(HttpStatus.CREATED); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping("/update") public ResponseEntity update(@RequestBody CategoryUpdateRequestDto dto) { // TODO admin only + System.out.println(dto); if (!categoryService.update(dto)) { - return ResponseEntity.notFound().build(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } - return new ResponseEntity<>(HttpStatus.CREATED); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @DeleteMapping("/delete/{id}") public ResponseEntity delete(@PathVariable Long id) { // TODO admin only categoryService.delete(id); - return ResponseEntity.noContent().build(); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @GetMapping("/{id}") public ResponseEntity getById(@PathVariable Long id) { return categoryService.getById(id) .map(categoryResponseDto -> new ResponseEntity<>(categoryResponseDto, HttpStatus.OK)) - .orElseGet(() -> ResponseEntity.notFound().build()); + .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build()); } @GetMapping("/list") public ResponseEntity> getAll() { return new ResponseEntity<>(categoryService.getAll(), HttpStatus.OK); } - } diff --git a/src/main/java/me/jweissen/aeticket/controller/EventController.java b/src/main/java/me/jweissen/aeticket/controller/EventController.java index 752c1c8..623a911 100644 --- a/src/main/java/me/jweissen/aeticket/controller/EventController.java +++ b/src/main/java/me/jweissen/aeticket/controller/EventController.java @@ -23,28 +23,28 @@ public class EventController { public ResponseEntity create(@RequestBody EventRequestDto event) { // TODO admin only eventService.create(event); - return new ResponseEntity<>(HttpStatus.CREATED); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping("/update") public ResponseEntity update(@RequestBody EventUpdateRequestDto event) { // TODO admin only eventService.update(event); - return new ResponseEntity<>(HttpStatus.CREATED); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @DeleteMapping("/delete/{id}") public ResponseEntity delete(@PathVariable Long id) { // TODO admin only eventService.delete(id); - return ResponseEntity.noContent().build(); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @GetMapping("/{id}") public ResponseEntity getById(@PathVariable Long id) { return eventService.getById(id) .map(eventResponseDto -> new ResponseEntity<>(eventResponseDto, HttpStatus.OK)) - .orElseGet(() -> ResponseEntity.notFound().build()); + .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build()); } @GetMapping("/list") diff --git a/src/main/java/me/jweissen/aeticket/controller/UserController.java b/src/main/java/me/jweissen/aeticket/controller/UserController.java index 577eee6..e1d1d32 100644 --- a/src/main/java/me/jweissen/aeticket/controller/UserController.java +++ b/src/main/java/me/jweissen/aeticket/controller/UserController.java @@ -1,6 +1,5 @@ package me.jweissen.aeticket.controller; -import me.jweissen.aeticket.aspect.Permissions; import me.jweissen.aeticket.dto.request.LoginRequestDto; import me.jweissen.aeticket.dto.request.SignupRequestDto; import me.jweissen.aeticket.dto.request.UserUpdateRequestDto; @@ -39,30 +38,29 @@ public class UserController { public ResponseEntity update(@RequestBody UserUpdateRequestDto user) { // TODO admin only if (!userService.update(user)) { - return ResponseEntity.notFound().build(); + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } - return ResponseEntity.noContent().build(); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @DeleteMapping("/delete/{id}") - public ResponseEntity delete(@PathVariable Integer id) { + public ResponseEntity delete(@PathVariable Long id) { // TODO admin only userService.delete(id); - return ResponseEntity.noContent().build(); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @GetMapping("/list") - @Permissions(user = false) public ResponseEntity> getAll() { // TODO admin only return new ResponseEntity<>(userService.getAll(), HttpStatus.OK); } @GetMapping("/load/{id}") - public ResponseEntity getById(@PathVariable Integer id) { + public ResponseEntity getById(@PathVariable Long id) { // TODO admin only return userService.getById(id) .map(userResponseDto -> new ResponseEntity<>(userResponseDto, HttpStatus.OK)) - .orElseGet(() -> new ResponseEntity<>(null, HttpStatus.NOT_FOUND)); + .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build()); } } diff --git a/src/main/java/me/jweissen/aeticket/dto/request/EventUpdateRequestDto.java b/src/main/java/me/jweissen/aeticket/dto/request/EventUpdateRequestDto.java index a64cc32..c12d7c3 100644 --- a/src/main/java/me/jweissen/aeticket/dto/request/EventUpdateRequestDto.java +++ b/src/main/java/me/jweissen/aeticket/dto/request/EventUpdateRequestDto.java @@ -1,4 +1,7 @@ package me.jweissen.aeticket.dto.request; -public record EventUpdateRequestDto(Long id) { +import java.util.Date; + +// no ticketCategories to prevent undefined behaviour +public record EventUpdateRequestDto(Long id, String name, Date from, Date to, String description) { } diff --git a/src/main/java/me/jweissen/aeticket/dto/request/UserUpdateRequestDto.java b/src/main/java/me/jweissen/aeticket/dto/request/UserUpdateRequestDto.java index 70c7265..157bee9 100644 --- a/src/main/java/me/jweissen/aeticket/dto/request/UserUpdateRequestDto.java +++ b/src/main/java/me/jweissen/aeticket/dto/request/UserUpdateRequestDto.java @@ -1,4 +1,4 @@ package me.jweissen.aeticket.dto.request; -public record UserUpdateRequestDto(Integer id, String firstname, String lastname, String email) { +public record UserUpdateRequestDto(Long id, String firstname, String lastname, String email) { } diff --git a/src/main/java/me/jweissen/aeticket/model/Cart.java b/src/main/java/me/jweissen/aeticket/model/Cart.java index 50c97d3..06c1459 100644 --- a/src/main/java/me/jweissen/aeticket/model/Cart.java +++ b/src/main/java/me/jweissen/aeticket/model/Cart.java @@ -15,6 +15,9 @@ public class Cart { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false) + private Boolean checkedOut = false; + @OneToMany(mappedBy = "cart") @Column(nullable = false) private List cartEntries; diff --git a/src/main/java/me/jweissen/aeticket/model/CartEntry.java b/src/main/java/me/jweissen/aeticket/model/CartEntry.java index d334034..1f9740c 100644 --- a/src/main/java/me/jweissen/aeticket/model/CartEntry.java +++ b/src/main/java/me/jweissen/aeticket/model/CartEntry.java @@ -1,26 +1,30 @@ package me.jweissen.aeticket.model; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Entity @Table @Getter @Setter +@NoArgsConstructor +@RequiredArgsConstructor public class CartEntry { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @ManyToOne + @NonNull @JoinColumn(nullable = false) private Cart cart; @ManyToOne + @NonNull @JoinColumn(nullable = false) private Category category; @Column(nullable = false) - private int amount; + @NonNull + private Integer amount; } diff --git a/src/main/java/me/jweissen/aeticket/model/Event.java b/src/main/java/me/jweissen/aeticket/model/Event.java index b597b3c..5acabbb 100644 --- a/src/main/java/me/jweissen/aeticket/model/Event.java +++ b/src/main/java/me/jweissen/aeticket/model/Event.java @@ -13,7 +13,7 @@ import java.util.List; @Setter @NoArgsConstructor @AllArgsConstructor -@Builder +@RequiredArgsConstructor public class Event { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -21,7 +21,7 @@ public class Event { @Column(nullable = false) @NonNull - private String title; + private String name; @Column(nullable = false) @NonNull @@ -35,7 +35,7 @@ public class Event { @NonNull private Date end; - @OneToMany(mappedBy = "event") + @OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true) @Column(nullable = false) private List categories = new ArrayList<>(); diff --git a/src/main/java/me/jweissen/aeticket/model/User.java b/src/main/java/me/jweissen/aeticket/model/User.java index 6c41e4c..c068f7d 100644 --- a/src/main/java/me/jweissen/aeticket/model/User.java +++ b/src/main/java/me/jweissen/aeticket/model/User.java @@ -3,6 +3,7 @@ package me.jweissen.aeticket.model; import jakarta.persistence.*; import lombok.*; +import java.util.Date; import java.util.List; @Entity @@ -35,11 +36,19 @@ public class User { @Column private String token; + @Column + private Date tokenValidUntil; + @Column(nullable = false) @Enumerated(EnumType.ORDINAL) @NonNull private Role role; + @OneToOne + @JoinColumn(nullable = false) + @NonNull + private Cart currentCart; + @OneToMany(mappedBy = "user") private List carts; } \ No newline at end of file diff --git a/src/main/java/me/jweissen/aeticket/repository/CartRepository.java b/src/main/java/me/jweissen/aeticket/repository/CartRepository.java index 155d98b..316461c 100644 --- a/src/main/java/me/jweissen/aeticket/repository/CartRepository.java +++ b/src/main/java/me/jweissen/aeticket/repository/CartRepository.java @@ -2,6 +2,10 @@ package me.jweissen.aeticket.repository; import me.jweissen.aeticket.model.Cart; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CartRepository extends JpaRepository { + @Query("SELECT sum(ce.amount * c.price) FROM CartEntry ce INNER JOIN Category c ON ce.category = c WHERE ce.cart = :cart") + Integer getCheckoutPrice(@Param("cart") Cart cart); } diff --git a/src/main/java/me/jweissen/aeticket/repository/CategoryRepository.java b/src/main/java/me/jweissen/aeticket/repository/CategoryRepository.java index 9cca126..0328402 100644 --- a/src/main/java/me/jweissen/aeticket/repository/CategoryRepository.java +++ b/src/main/java/me/jweissen/aeticket/repository/CategoryRepository.java @@ -2,6 +2,10 @@ package me.jweissen.aeticket.repository; import me.jweissen.aeticket.model.Category; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CategoryRepository extends JpaRepository { + @Query("SELECT c.stock - sum(ce.amount) FROM Category c INNER JOIN CartEntry ce ON c = ce.category WHERE c = :category GROUP BY c") + Integer availableTickets(@Param("category") Category category); } diff --git a/src/main/java/me/jweissen/aeticket/repository/UserRepository.java b/src/main/java/me/jweissen/aeticket/repository/UserRepository.java index 54a309f..e398393 100644 --- a/src/main/java/me/jweissen/aeticket/repository/UserRepository.java +++ b/src/main/java/me/jweissen/aeticket/repository/UserRepository.java @@ -3,7 +3,7 @@ package me.jweissen.aeticket.repository; import me.jweissen.aeticket.model.User; import org.springframework.data.jpa.repository.JpaRepository; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { User findByToken(String token); User findByEmail(String email); } diff --git a/src/main/java/me/jweissen/aeticket/service/AuthService.java b/src/main/java/me/jweissen/aeticket/service/AuthService.java index 4958454..ba17523 100644 --- a/src/main/java/me/jweissen/aeticket/service/AuthService.java +++ b/src/main/java/me/jweissen/aeticket/service/AuthService.java @@ -1,29 +1,56 @@ package me.jweissen.aeticket.service; +import lombok.Getter; import me.jweissen.aeticket.model.Role; import me.jweissen.aeticket.model.User; import me.jweissen.aeticket.repository.UserRepository; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.util.Date; import java.util.Optional; @Service public class AuthService { private final JwtService jwtService; private final UserRepository userRepository; + private final Long tokenValidForMillis; + @Getter + private User currentUser; - public AuthService(JwtService jwtService, UserRepository userRepository) { + public AuthService( + JwtService jwtService, + UserRepository userRepository, + @Value("${token.validForHours}") Long tokenValidForHours) { + this.tokenValidForMillis = 1000L * 3600 * tokenValidForHours; this.jwtService = jwtService; this.userRepository = userRepository; } - public Optional getRole(String token) { - User user = userRepository.findByToken(token); - if (user == null) { - return Optional.empty(); - } - return Optional.of(user.getRole()); + public void extendToken(User user) { + user.setTokenValidUntil(new Date(System.currentTimeMillis() + tokenValidForMillis)); + userRepository.save(user); } - + public Optional authenticate(String token) { + Optional userIdOptional = jwtService.getUserId(token); + // token not valid or userId + if (userIdOptional.isEmpty()) { + return Optional.empty(); + } + Optional userOptional = userRepository.findById(userIdOptional.get()); + if (userOptional.isEmpty()) { + // user with id not found + return Optional.empty(); + } + User user = userOptional.get(); + if (user.getTokenValidUntil().before(new Date())) { + // token expired + return Optional.empty(); + } + // success + extendToken(user); + currentUser = user; + return Optional.of(user); + } } diff --git a/src/main/java/me/jweissen/aeticket/service/CartService.java b/src/main/java/me/jweissen/aeticket/service/CartService.java index eb18f7a..fff2d27 100644 --- a/src/main/java/me/jweissen/aeticket/service/CartService.java +++ b/src/main/java/me/jweissen/aeticket/service/CartService.java @@ -1,23 +1,36 @@ package me.jweissen.aeticket.service; -import me.jweissen.aeticket.dto.response.CartEntryResponseDto; +import me.jweissen.aeticket.dto.request.CartAddRequestDto; +import me.jweissen.aeticket.dto.request.CartEntryRequestDto; import me.jweissen.aeticket.dto.response.CartEventResponseDto; +import me.jweissen.aeticket.dto.response.CheckoutResponseDto; import me.jweissen.aeticket.model.Cart; +import me.jweissen.aeticket.model.CartEntry; +import me.jweissen.aeticket.model.Category; import me.jweissen.aeticket.model.Event; import me.jweissen.aeticket.repository.CartEntryRepository; import me.jweissen.aeticket.repository.CartRepository; +import me.jweissen.aeticket.repository.CategoryRepository; +import me.jweissen.aeticket.repository.UserRepository; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @Service public class CartService { private final CartRepository cartRepository; private final CartEntryRepository cartEntryRepository; + private final CategoryRepository categoryRepository; + private final UserRepository userRepository; - public CartService(CartRepository cartRepository, CartEntryRepository cartEntryRepository) { + public CartService(CartRepository cartRepository, CartEntryRepository cartEntryRepository, + CategoryRepository categoryRepository, + UserRepository userRepository) { this.cartRepository = cartRepository; this.cartEntryRepository = cartEntryRepository; + this.categoryRepository = categoryRepository; + this.userRepository = userRepository; } public List toDto(Cart cart) { @@ -27,7 +40,7 @@ public class CartService { return distinctEvents.stream().map(event -> new CartEventResponseDto( event.getId(), - event.getTitle(), + event.getName(), event.getStart(), event.getEnd(), event.getDescription(), @@ -35,6 +48,35 @@ public class CartService { )).toList(); } - //public List getCartByAuthToken() { + public boolean add(CartAddRequestDto dto, Cart cart) { + // posting the eventIds is redundant so, those are ignored + for (CartEntryRequestDto cartEntryDto: dto.cartEntries()) { + Optional categoryOptional = categoryRepository.findById(cartEntryDto.id()); + if (categoryOptional.isEmpty()) { + // category id not found + return false; + } + Category category = categoryOptional.get(); + if (cartEntryDto.amount() > categoryRepository.availableTickets(category)) { + // wants to order more tickets than available + return false; + } + cart.getCartEntries().add(new CartEntry(cart, category, cartEntryDto.amount())); + } + cartRepository.save(cart); + return true; + } + public CheckoutResponseDto checkout(Cart cart) { + cart.setCheckedOut(true); + cartRepository.save(cart); + // reset current cart + Cart newCart = new Cart(); + newCart = cartRepository.save(newCart); + cart.getUser().setCurrentCart(newCart); + userRepository.save(cart.getUser()); + return new CheckoutResponseDto( + CategoryService.centsToEuros(cartRepository.getCheckoutPrice(cart)) + ); + } } diff --git a/src/main/java/me/jweissen/aeticket/service/CategoryService.java b/src/main/java/me/jweissen/aeticket/service/CategoryService.java index 594f9a2..8006854 100644 --- a/src/main/java/me/jweissen/aeticket/service/CategoryService.java +++ b/src/main/java/me/jweissen/aeticket/service/CategoryService.java @@ -27,6 +27,10 @@ public class CategoryService { ); } + public static List fromDtos(List dtos) { + return dtos.stream().map(CategoryService::fromDto).toList(); + } + public static CategoryResponseDto toDto(Category category) { return new CategoryResponseDto( category.getId(), @@ -54,6 +58,7 @@ public class CategoryService { } public boolean update(CategoryUpdateRequestDto dto) { + System.out.println(dto); return categoryRepository.findById(dto.id()) .map(category -> { category.setName(dto.name()); diff --git a/src/main/java/me/jweissen/aeticket/service/EventService.java b/src/main/java/me/jweissen/aeticket/service/EventService.java index 7684a0c..0d0da3b 100644 --- a/src/main/java/me/jweissen/aeticket/service/EventService.java +++ b/src/main/java/me/jweissen/aeticket/service/EventService.java @@ -3,7 +3,9 @@ package me.jweissen.aeticket.service; import me.jweissen.aeticket.dto.request.EventRequestDto; import me.jweissen.aeticket.dto.request.EventUpdateRequestDto; import me.jweissen.aeticket.dto.response.EventResponseDto; +import me.jweissen.aeticket.model.Category; import me.jweissen.aeticket.model.Event; +import me.jweissen.aeticket.repository.CategoryRepository; import me.jweissen.aeticket.repository.EventRepository; import org.springframework.stereotype.Service; @@ -13,24 +15,27 @@ import java.util.Optional; @Service public class EventService { private final EventRepository eventRepository; + private final CategoryRepository categoryRepository; - public EventService(EventRepository eventRepository) { + public EventService(EventRepository eventRepository, + CategoryRepository categoryRepository) { this.eventRepository = eventRepository; + this.categoryRepository = categoryRepository; } public static Event fromDto(EventRequestDto dto) { - return Event.builder() - .title(dto.name()) - .description(dto.description()) - .start(dto.from()) - .end(dto.to()) - .build(); + return new Event( + dto.name(), + dto.description(), + dto.from(), + dto.to() + ); } public static EventResponseDto toDto(Event event) { return new EventResponseDto( event.getId(), - event.getTitle(), + event.getName(), event.getStart(), event.getEnd(), event.getDescription(), @@ -42,14 +47,23 @@ public class EventService { return events.stream().map(EventService::toDto).toList(); } - public void create(EventRequestDto event) { - eventRepository.save(EventService.fromDto(event)); + public void create(EventRequestDto dto) { + Event event = EventService.fromDto(dto); + event = eventRepository.save(event); + Event finalEvent = event; + List categories = CategoryService.fromDtos(dto.ticketCategories()); + categories.forEach(c -> c.setEvent(finalEvent)); + categoryRepository.saveAll(categories); } public boolean update(EventUpdateRequestDto dto) { return eventRepository.findById(dto.id()) .map(event -> { - // dto auf object assignen + event.setName(dto.name()); + event.setDescription(dto.description()); + event.setStart(dto.from()); + event.setEnd(dto.to()); + eventRepository.save(event); return true; }) .orElse(false); diff --git a/src/main/java/me/jweissen/aeticket/service/JwtService.java b/src/main/java/me/jweissen/aeticket/service/JwtService.java index 076b8e7..574d1ad 100644 --- a/src/main/java/me/jweissen/aeticket/service/JwtService.java +++ b/src/main/java/me/jweissen/aeticket/service/JwtService.java @@ -6,6 +6,8 @@ import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; +import me.jweissen.aeticket.model.User; +import me.jweissen.aeticket.repository.UserRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -17,10 +19,8 @@ public class JwtService { private final JWTVerifier jwtVerifier; private final Algorithm algorithm; private final String userIdClaimKey = "userId"; - private final Long tokenValidForMillis; - public JwtService(@Value("${token.secret}") String secret, @Value("${token.validForHours}") Long tokenValidForHours) { - tokenValidForMillis = 1000L * 3600 * tokenValidForHours; + public JwtService(@Value("${token.secret}") String secret) { algorithm = Algorithm.HMAC256(secret); jwtVerifier = JWT.require(algorithm).build(); } @@ -29,8 +29,6 @@ public class JwtService { return JWT.create() .withSubject("aeticket user token") .withClaim(userIdClaimKey, userId) - .withIssuedAt(new Date()) - .withExpiresAt(new Date(System.currentTimeMillis() + tokenValidForMillis)) .sign(algorithm); } @@ -42,11 +40,16 @@ public class JwtService { // token not valid return Optional.empty(); } - // token expired - if (decodedJWT.getExpiresAt().before(new Date())) { + Claim userIdClaim = decodedJWT.getClaim(userIdClaimKey); + if (userIdClaim.isNull()) { + // userId claim not present return Optional.empty(); } - Claim claim = decodedJWT.getClaim(userIdClaimKey); - return Optional.of(claim.asLong()); + Long userId = userIdClaim.asLong(); + if (userId == null) { + // userId claim not a long + return Optional.empty(); + } + return Optional.of(userId); } } diff --git a/src/main/java/me/jweissen/aeticket/service/UserService.java b/src/main/java/me/jweissen/aeticket/service/UserService.java index e0bf4d9..c05d1d2 100644 --- a/src/main/java/me/jweissen/aeticket/service/UserService.java +++ b/src/main/java/me/jweissen/aeticket/service/UserService.java @@ -5,6 +5,7 @@ import me.jweissen.aeticket.dto.request.SignupRequestDto; import me.jweissen.aeticket.dto.request.UserUpdateRequestDto; import me.jweissen.aeticket.dto.response.TokenResponseDto; import me.jweissen.aeticket.dto.response.UserResponseDto; +import me.jweissen.aeticket.model.Cart; import me.jweissen.aeticket.model.Role; import me.jweissen.aeticket.model.User; import me.jweissen.aeticket.repository.UserRepository; @@ -38,7 +39,8 @@ public class UserService { dto.lastname(), dto.email(), dto.password(), - Role.USER + Role.USER, + new Cart() ); } @@ -62,7 +64,7 @@ public class UserService { return new TokenResponseDto(generateToken(user)); } - public void delete(Integer id) { + public void delete(Long id) { userRepository.deleteById(id); } @@ -87,7 +89,7 @@ public class UserService { return true; } - public Optional getById(Integer id) { + public Optional getById(Long id) { return userRepository.findById(id).map(UserService::toDto); } }