From 336f0a9c42b2b4eeaed06cb47d3f60b8bed780e4 Mon Sep 17 00:00:00 2001 From: shyye Date: Thu, 12 Sep 2024 14:28:32 +0200 Subject: [PATCH 01/11] Add lombok dependency --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 3d7f7607..c5d29b6e 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,9 @@ dependencies { runtimeOnly 'org.postgresql:postgresql' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // Add lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { From 09f184ab87041b6342f4cbc183ecfc2a715cc56d Mon Sep 17 00:00:00 2001 From: shyye Date: Thu, 12 Sep 2024 21:01:34 +0200 Subject: [PATCH 02/11] Add Customer --- .../controllers/CustomerController.java | 71 +++++++++++++++++++ .../booleanuk/api/cinema/models/Customer.java | 49 +++++++++++++ .../repositories/CustomerRepository.java | 7 ++ 3 files changed, 127 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Customer.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repositories/CustomerRepository.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java b/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java new file mode 100644 index 00000000..02914f41 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java @@ -0,0 +1,71 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.models.Customer; +import com.booleanuk.api.cinema.repositories.CustomerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; + +@RestController +@RequestMapping("customers") +public class CustomerController { + @Autowired + CustomerRepository repository; + + @PostMapping + public ResponseEntity create(@RequestBody Customer customer) { + return new ResponseEntity<>(this.repository.save(customer), HttpStatus.CREATED); + } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(this.repository.findAll()); + } + + @PutMapping("{id}") + public ResponseEntity update( + @PathVariable int id, + @RequestBody Customer customer) + { + Customer originalCustomer = this.getObjectById(id); + customer.setId(id); + customer.setCreatedAt(originalCustomer.getCreatedAt()); + return new ResponseEntity<>(this.repository.save(customer), HttpStatus.CREATED); + } + + @DeleteMapping("{id}") + public ResponseEntity delete(@PathVariable int id) { + Customer customer = getObjectById(id); + try { + this.repository.deleteById(id); + } catch (Exception e) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Could not delete customer. Detailed information: "+e.getMessage() + ); + } + return ResponseEntity.ok(customer); + } + + /** + * Get object by id. + * Can be used to check for valid id (throws exception if id doesn't exist). + * @param id . + * @return Customer + */ + private Customer getObjectById(int id) { + Customer customer = this.repository + .findById(id) + .orElseThrow( + () -> new ResponseStatusException( + HttpStatus.NOT_FOUND, + "No customer with id #"+id+" found." + ) + ); + return customer; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/models/Customer.java b/src/main/java/com/booleanuk/api/cinema/models/Customer.java new file mode 100644 index 00000000..fceea6e1 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Customer.java @@ -0,0 +1,49 @@ +package com.booleanuk.api.cinema.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.cglib.core.Local; + +import java.time.LocalDateTime; +import java.util.Date; + +// TODO: Check if all annotations below is needed +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table +public class Customer { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private String name; + + @Column + private String email; + + @Column + private String phone; + + @Column(updatable = false) // Updatable = false, to prevent createdAt to be set to null on update + @CreationTimestamp + private LocalDateTime createdAt; + + @Column + @UpdateTimestamp + private LocalDateTime updatedAt; + + public Customer(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/repositories/CustomerRepository.java b/src/main/java/com/booleanuk/api/cinema/repositories/CustomerRepository.java new file mode 100644 index 00000000..24a8a4cc --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repositories/CustomerRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.repositories; + +import com.booleanuk.api.cinema.models.Customer; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomerRepository extends JpaRepository { +} From 7aa71451652aeb650f96b6a7be0b79c7cc3dd34a Mon Sep 17 00:00:00 2001 From: shyye Date: Thu, 12 Sep 2024 21:01:50 +0200 Subject: [PATCH 03/11] Add Movie --- .../cinema/controllers/MovieController.java | 72 +++++++++++++++++++ .../booleanuk/api/cinema/models/Movie.java | 51 +++++++++++++ .../cinema/repositories/MovieRepository.java | 7 ++ 3 files changed, 130 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Movie.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repositories/MovieRepository.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java b/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java new file mode 100644 index 00000000..0fff172b --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java @@ -0,0 +1,72 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.models.Customer; +import com.booleanuk.api.cinema.models.Movie; +import com.booleanuk.api.cinema.repositories.MovieRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; + +@RestController +@RequestMapping("movies") +public class MovieController { + @Autowired + private MovieRepository repository; + + @PostMapping + public ResponseEntity create(@RequestBody Movie movie) { + return new ResponseEntity<>(this.repository.save(movie), HttpStatus.CREATED); + } + + @GetMapping + public ResponseEntity> getAll() { + return ResponseEntity.ok(this.repository.findAll()); + } + + @PutMapping("{id}") + public ResponseEntity update( + @PathVariable int id, + @RequestBody Movie movie) + { + Movie originalMovie = getObjectById(id); + movie.setId(id); + movie.setCreatedAt(originalMovie.getCreatedAt()); + return new ResponseEntity<>(this.repository.save(movie), HttpStatus.CREATED); + } + + @DeleteMapping("{id}") + public ResponseEntity delete(@PathVariable int id) { + Movie movie = getObjectById(id); + try { + this.repository.deleteById(id); + } catch (Exception e) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "Could not delete customer. Detailed information: "+e.getMessage() + ); + } + return ResponseEntity.ok(movie); + } + + /** + * Get object by id. + * Can be used to check for valid id (throws exception if id doesn't exist). + * @param id . + * @return Customer + */ + private Movie getObjectById(int id) { + Movie movie = this.repository + .findById(id) + .orElseThrow( + () -> new ResponseStatusException( + HttpStatus.NOT_FOUND, + "No movie with id #"+id+" found." + ) + ); + return movie; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/models/Movie.java b/src/main/java/com/booleanuk/api/cinema/models/Movie.java new file mode 100644 index 00000000..ae017652 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Movie.java @@ -0,0 +1,51 @@ +package com.booleanuk.api.cinema.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +// TODO: Check if all annotations below is needed +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table +public class Movie { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private String title; + + @Column + private String rating; + + @Column + private String description; + + @Column + private int runtimeMins; + + @Column(updatable = false) + @CreationTimestamp + private LocalDateTime createdAt; + + @Column + @UpdateTimestamp + private LocalDateTime updatedAt; + + public Movie(String title, String rating, String description, int runtimeMins) { + this.title = title; + this.rating = rating; + this.description = description; + this.runtimeMins = runtimeMins; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/repositories/MovieRepository.java b/src/main/java/com/booleanuk/api/cinema/repositories/MovieRepository.java new file mode 100644 index 00000000..21fc1442 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repositories/MovieRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.repositories; + +import com.booleanuk.api.cinema.models.Movie; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MovieRepository extends JpaRepository { +} From 79cbf9dd3a4c678c824bf0583833bacafc881494 Mon Sep 17 00:00:00 2001 From: shyye Date: Thu, 12 Sep 2024 21:02:09 +0200 Subject: [PATCH 04/11] Add Screening --- .../controllers/ScreeningController.java | 32 ++++++++++++ .../api/cinema/models/Screening.java | 52 +++++++++++++++++++ .../repositories/ScreeningRepository.java | 10 ++++ 3 files changed, 94 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Screening.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repositories/ScreeningRepository.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java new file mode 100644 index 00000000..dade0e91 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java @@ -0,0 +1,32 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.models.Movie; +import com.booleanuk.api.cinema.models.Screening; +import com.booleanuk.api.cinema.repositories.ScreeningRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("movies") +public class ScreeningController { + @Autowired + private ScreeningRepository repository; + + @PostMapping("{id}/screenings") + public ResponseEntity create( + @PathVariable int id, + @RequestBody Screening screening) + { + screening.setMovieId(id); + return new ResponseEntity<>(this.repository.save(screening), HttpStatus.CREATED); + } + + @GetMapping("{id}/screenings") + public ResponseEntity> getAll(@PathVariable int id) { + return ResponseEntity.ok(this.repository.findAllByMovieId(id)); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/models/Screening.java b/src/main/java/com/booleanuk/api/cinema/models/Screening.java new file mode 100644 index 00000000..cd1847e8 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Screening.java @@ -0,0 +1,52 @@ +package com.booleanuk.api.cinema.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +// TODO: Check if all annotations below is needed +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table +public class Screening { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private int movieId; + + @Column + private int screenNumber; + + @Column + private LocalDateTime startsAt; + + @Column + private int capacity; + + @Column(updatable = false) + @CreationTimestamp + private LocalDateTime createdAt; + + @Column + @UpdateTimestamp + private LocalDateTime updatedAt; + + public Screening(int screenNumber, LocalDateTime startsAt, int capacity) { + this.screenNumber = screenNumber; + this.startsAt = startsAt; + this.capacity = capacity; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/repositories/ScreeningRepository.java b/src/main/java/com/booleanuk/api/cinema/repositories/ScreeningRepository.java new file mode 100644 index 00000000..91e3e6ea --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repositories/ScreeningRepository.java @@ -0,0 +1,10 @@ +package com.booleanuk.api.cinema.repositories; + +import com.booleanuk.api.cinema.models.Screening; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ScreeningRepository extends JpaRepository { + List findAllByMovieId(int movieId); +} From e9a394b57c73b7c7914acd849a02cfec6acf67aa Mon Sep 17 00:00:00 2001 From: shyye Date: Thu, 12 Sep 2024 21:03:09 +0200 Subject: [PATCH 05/11] Add Main --- src/main/java/com/booleanuk/api/cinema/Main.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/Main.java diff --git a/src/main/java/com/booleanuk/api/cinema/Main.java b/src/main/java/com/booleanuk/api/cinema/Main.java new file mode 100644 index 00000000..c4fb60ea --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/Main.java @@ -0,0 +1,11 @@ +package com.booleanuk.api.cinema; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} From 09a5de29445663efb1d70e7597ace86f8c52a0d1 Mon Sep 17 00:00:00 2001 From: shyye Date: Tue, 17 Sep 2024 21:21:11 +0200 Subject: [PATCH 06/11] Update Customer classes --- .../controllers/CustomerController.java | 71 ++++++++++++++----- .../booleanuk/api/cinema/models/Customer.java | 4 -- .../responses/CustomerListResponse.java | 8 +++ .../cinema/responses/CustomerResponse.java | 6 ++ 4 files changed, 69 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/CustomerListResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/CustomerResponse.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java b/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java index 02914f41..05a0da9b 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java @@ -2,6 +2,10 @@ import com.booleanuk.api.cinema.models.Customer; import com.booleanuk.api.cinema.repositories.CustomerRepository; +import com.booleanuk.api.cinema.responses.CustomerListResponse; +import com.booleanuk.api.cinema.responses.CustomerResponse; +import com.booleanuk.api.cinema.responses.ErrorResponse; +import com.booleanuk.api.cinema.responses.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,38 +21,73 @@ public class CustomerController { CustomerRepository repository; @PostMapping - public ResponseEntity create(@RequestBody Customer customer) { - return new ResponseEntity<>(this.repository.save(customer), HttpStatus.CREATED); + public ResponseEntity> create(@RequestBody Customer customer) { + CustomerResponse response = new CustomerResponse(); + response.set(customer); + try { + this.repository.save(customer); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Could not create a new customer, please check all fields are correct."); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + return new ResponseEntity<>(response, HttpStatus.CREATED); } @GetMapping - public ResponseEntity> getAll() { - return ResponseEntity.ok(this.repository.findAll()); + public ResponseEntity> getAll() { + CustomerListResponse response = new CustomerListResponse(); + List customers = this.repository.findAll(); + if (customers.isEmpty()) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("No customers found"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + response.set(this.repository.findAll()); + return ResponseEntity.ok(response); } @PutMapping("{id}") - public ResponseEntity update( + public ResponseEntity> update( @PathVariable int id, @RequestBody Customer customer) { - Customer originalCustomer = this.getObjectById(id); - customer.setId(id); - customer.setCreatedAt(originalCustomer.getCreatedAt()); - return new ResponseEntity<>(this.repository.save(customer), HttpStatus.CREATED); + CustomerResponse response = new CustomerResponse(); + try { + Customer originalCustomer = this.getObjectById(id); + customer.setId(id); + customer.setCreatedAt(originalCustomer.getCreatedAt()); + this.repository.save(customer); + + // TODO: inefficient to call getObjectById again, refactor? + // Get the updated value, added on table update + customer.setUpdatedAt(getObjectById(id).getUpdatedAt()); + response.set(customer); + } catch (Exception e) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set(e.getMessage()); + + // TODO: Duplicate code for HttpStatus.NOT_FOUND, both in exception and again here. Refactor? + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(response, HttpStatus.CREATED); } @DeleteMapping("{id}") - public ResponseEntity delete(@PathVariable int id) { - Customer customer = getObjectById(id); + public ResponseEntity> delete(@PathVariable int id) { + CustomerResponse response = new CustomerResponse(); try { + Customer customer = getObjectById(id); this.repository.deleteById(id); + response.set(customer); } catch (Exception e) { - throw new ResponseStatusException( - HttpStatus.BAD_REQUEST, - "Could not delete customer. Detailed information: "+e.getMessage() - ); + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set(e.getMessage()); + + // TODO: Duplicate code for HttpStatus.NOT_FOUND, both in exception and again here. Refactor? + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } - return ResponseEntity.ok(customer); + return ResponseEntity.ok(response); } /** diff --git a/src/main/java/com/booleanuk/api/cinema/models/Customer.java b/src/main/java/com/booleanuk/api/cinema/models/Customer.java index fceea6e1..900db1c2 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Customer.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Customer.java @@ -7,12 +7,8 @@ import lombok.Setter; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; -import org.springframework.cglib.core.Local; - import java.time.LocalDateTime; -import java.util.Date; -// TODO: Check if all annotations below is needed @Getter @Setter @AllArgsConstructor diff --git a/src/main/java/com/booleanuk/api/cinema/responses/CustomerListResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/CustomerListResponse.java new file mode 100644 index 00000000..922e02f0 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/CustomerListResponse.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.cinema.responses; + +import com.booleanuk.api.cinema.models.Customer; + +import java.util.List; + +public class CustomerListResponse extends Response>{ +} diff --git a/src/main/java/com/booleanuk/api/cinema/responses/CustomerResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/CustomerResponse.java new file mode 100644 index 00000000..fd857291 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/CustomerResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.responses; + +import com.booleanuk.api.cinema.models.Customer; + +public class CustomerResponse extends Response { +} From 7e338e1927b09ece10100ae2e9ff3cc9fc4dc56b Mon Sep 17 00:00:00 2001 From: shyye Date: Tue, 17 Sep 2024 21:21:32 +0200 Subject: [PATCH 07/11] Add ErrorResponse --- .../api/cinema/responses/ErrorResponse.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/ErrorResponse.java diff --git a/src/main/java/com/booleanuk/api/cinema/responses/ErrorResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/ErrorResponse.java new file mode 100644 index 00000000..279b8f47 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/ErrorResponse.java @@ -0,0 +1,14 @@ +package com.booleanuk.api.cinema.responses; + +import java.util.HashMap; +import java.util.Map; + +public class ErrorResponse extends Response>{ + + public void set(String message) { + this.status = "error"; + Map reply = new HashMap<>(); + reply.put("message", message); + this.data = reply; + } +} From fd4dfc6d051a8f09c3acd230ab350da9d2668a1b Mon Sep 17 00:00:00 2001 From: shyye Date: Tue, 17 Sep 2024 21:21:46 +0200 Subject: [PATCH 08/11] Add Response --- .../booleanuk/api/cinema/responses/Response.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/Response.java diff --git a/src/main/java/com/booleanuk/api/cinema/responses/Response.java b/src/main/java/com/booleanuk/api/cinema/responses/Response.java new file mode 100644 index 00000000..7bbb5f3c --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/Response.java @@ -0,0 +1,14 @@ +package com.booleanuk.api.cinema.responses; + +import lombok.Getter; + +@Getter +public class Response { + protected String status; + protected T data; + + public void set(T data) { + this.status = "success"; + this.data = data; + } +} From d4e0bac78277698d68e2b96a9b8b545b625a1167 Mon Sep 17 00:00:00 2001 From: shyye Date: Tue, 17 Sep 2024 22:29:45 +0200 Subject: [PATCH 09/11] Update movie classes --- .../cinema/controllers/MovieController.java | 32 +++++++++++++++++-- .../booleanuk/api/cinema/models/Movie.java | 10 +++++- .../api/cinema/responses/MovieResponse.java | 6 ++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/MovieResponse.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java b/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java index 0fff172b..a81b85af 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java @@ -2,7 +2,11 @@ import com.booleanuk.api.cinema.models.Customer; import com.booleanuk.api.cinema.models.Movie; +import com.booleanuk.api.cinema.models.Screening; import com.booleanuk.api.cinema.repositories.MovieRepository; +import com.booleanuk.api.cinema.repositories.ScreeningRepository; +import com.booleanuk.api.cinema.responses.MovieResponse; +import com.booleanuk.api.cinema.responses.Response; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,9 +21,32 @@ public class MovieController { @Autowired private MovieRepository repository; + @Autowired + private ScreeningRepository associatedScreeningRepository; + + // TODO: Handle case when the payload is a screening object instead of Id's @PostMapping - public ResponseEntity create(@RequestBody Movie movie) { - return new ResponseEntity<>(this.repository.save(movie), HttpStatus.CREATED); + public ResponseEntity> create(@RequestBody Movie movie) { + Movie newMovie = this.repository.save(movie); + + // Create corresponding screenings + List screenings = movie.getScreenings(); + for (Screening s : screenings) { + Screening existingScreening = this.associatedScreeningRepository.findById(s.getId()).orElse(null); + if (existingScreening == null) { + s.setMovie(newMovie); + this.associatedScreeningRepository.save(s); + } else { + existingScreening.setMovie(newMovie); + } + } + // Save to database + this.repository.save(newMovie); + + // Update response + MovieResponse response = new MovieResponse(); + response.set(newMovie); + return new ResponseEntity<>(response, HttpStatus.CREATED); } @GetMapping @@ -35,6 +62,7 @@ public ResponseEntity update( Movie originalMovie = getObjectById(id); movie.setId(id); movie.setCreatedAt(originalMovie.getCreatedAt()); + movie.setScreenings(originalMovie.getScreenings()); return new ResponseEntity<>(this.repository.save(movie), HttpStatus.CREATED); } diff --git a/src/main/java/com/booleanuk/api/cinema/models/Movie.java b/src/main/java/com/booleanuk/api/cinema/models/Movie.java index ae017652..67537949 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Movie.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Movie.java @@ -1,5 +1,6 @@ package com.booleanuk.api.cinema.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,6 +10,7 @@ import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; +import java.util.List; // TODO: Check if all annotations below is needed @Getter @@ -34,6 +36,11 @@ public class Movie { @Column private int runtimeMins; + // TODO: Does this get an own column in the database, or is it just handled with SpringBoot/Hibernate + @OneToMany(mappedBy = "movie", cascade = CascadeType.REMOVE) + @JsonIgnoreProperties("movie") + private List screenings; + @Column(updatable = false) @CreationTimestamp private LocalDateTime createdAt; @@ -42,10 +49,11 @@ public class Movie { @UpdateTimestamp private LocalDateTime updatedAt; - public Movie(String title, String rating, String description, int runtimeMins) { + public Movie(String title, String rating, String description, int runtimeMins, List screenings) { this.title = title; this.rating = rating; this.description = description; this.runtimeMins = runtimeMins; + this.screenings = screenings; } } diff --git a/src/main/java/com/booleanuk/api/cinema/responses/MovieResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/MovieResponse.java new file mode 100644 index 00000000..322c9748 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/MovieResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.responses; + +import com.booleanuk.api.cinema.models.Movie; + +public class MovieResponse extends Response{ +} From 11487c37db3643eb3f03f63b2f58fe2ef21bb1b2 Mon Sep 17 00:00:00 2001 From: shyye Date: Tue, 17 Sep 2024 22:29:59 +0200 Subject: [PATCH 10/11] Update Screening classes --- .../controllers/ScreeningController.java | 28 +++++++++++++++++-- .../api/cinema/models/Screening.java | 7 +++-- .../cinema/responses/ScreeningResponse.java | 6 ++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/ScreeningResponse.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java index dade0e91..5a68fad8 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java @@ -2,11 +2,15 @@ import com.booleanuk.api.cinema.models.Movie; import com.booleanuk.api.cinema.models.Screening; +import com.booleanuk.api.cinema.repositories.MovieRepository; import com.booleanuk.api.cinema.repositories.ScreeningRepository; +import com.booleanuk.api.cinema.responses.Response; +import com.booleanuk.api.cinema.responses.ScreeningResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; import java.util.List; @@ -16,13 +20,31 @@ public class ScreeningController { @Autowired private ScreeningRepository repository; + @Autowired + private MovieRepository associatedMovieRepository; + @PostMapping("{id}/screenings") - public ResponseEntity create( + public ResponseEntity> create( @PathVariable int id, @RequestBody Screening screening) { - screening.setMovieId(id); - return new ResponseEntity<>(this.repository.save(screening), HttpStatus.CREATED); + Screening newScreening = this.repository.save(screening); + + // Get movie + Movie movie = this.associatedMovieRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Movie with this id not found.") + ); + + // Associate the movie with this screening + newScreening.setMovie(movie); + // Update screening list for the movie + movie.getScreenings().add(newScreening); + this.associatedMovieRepository.save(movie); + + // Create response object + ScreeningResponse response = new ScreeningResponse(); + response.set(newScreening); + return new ResponseEntity<>(response, HttpStatus.CREATED); } @GetMapping("{id}/screenings") diff --git a/src/main/java/com/booleanuk/api/cinema/models/Screening.java b/src/main/java/com/booleanuk/api/cinema/models/Screening.java index cd1847e8..038585d8 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Screening.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Screening.java @@ -1,5 +1,6 @@ package com.booleanuk.api.cinema.models; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -24,8 +25,10 @@ public class Screening { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; - @Column - private int movieId; + @JoinColumn //TODO: Should this be here ? + @ManyToOne + @JsonIncludeProperties(value = {"id", "title"}) + private Movie movie; @Column private int screenNumber; diff --git a/src/main/java/com/booleanuk/api/cinema/responses/ScreeningResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/ScreeningResponse.java new file mode 100644 index 00000000..a451d09e --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/ScreeningResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.responses; + +import com.booleanuk.api.cinema.models.Screening; + +public class ScreeningResponse extends Response { +} From 79e1a5df68452b15ec0d2ec4c21deff1a94d9d68 Mon Sep 17 00:00:00 2001 From: shyye Date: Tue, 17 Sep 2024 22:30:21 +0200 Subject: [PATCH 11/11] Add Ticket classes --- .../cinema/controllers/TicketController.java | 74 +++++++++++++++++++ .../booleanuk/api/cinema/models/Ticket.java | 51 +++++++++++++ .../cinema/repositories/TicketRepository.java | 10 +++ .../cinema/responses/TicketListResponse.java | 8 ++ .../api/cinema/responses/TicketResponse.java | 6 ++ 5 files changed, 149 insertions(+) create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Ticket.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repositories/TicketRepository.java create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/TicketListResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/responses/TicketResponse.java diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java new file mode 100644 index 00000000..2e3c1889 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java @@ -0,0 +1,74 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.models.Customer; +import com.booleanuk.api.cinema.models.Screening; +import com.booleanuk.api.cinema.models.Ticket; +import com.booleanuk.api.cinema.repositories.CustomerRepository; +import com.booleanuk.api.cinema.repositories.ScreeningRepository; +import com.booleanuk.api.cinema.repositories.TicketRepository; +import com.booleanuk.api.cinema.responses.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("customers/{customerId}/screenings/{screeningId}") +public class TicketController { + @Autowired + TicketRepository repository; + + @Autowired + CustomerRepository customerRepository; + + @Autowired + ScreeningRepository screeningRepository; + + @PostMapping + public ResponseEntity> create( + @PathVariable int customerId, + @PathVariable int screeningId, + @RequestBody Ticket ticket) { + + Customer customer = this.customerRepository.findById(customerId).orElse(null); + Screening screening = this.screeningRepository.findById(screeningId).orElse(null); + if (customer == null || screening == null) { + // TODO: Could improve ux with better error message / more specific + ErrorResponse error = new ErrorResponse(); + error.set("Invalid customer or screening id"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + ticket.setCustomer(customer); + ticket.setScreening(screening); + + try { + this.repository.save(ticket); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Could not create a new ticket, please check all fields are correct."); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + TicketResponse response = new TicketResponse(); + response.set(ticket); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } + + @GetMapping + public ResponseEntity> getAll( + @PathVariable int customerId, + @PathVariable int screeningId + ) { + TicketListResponse response = new TicketListResponse(); + List tickets = this.repository.findAllByCustomerIdAndScreeningId(customerId, screeningId); + if (tickets.isEmpty()) { + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("No tickets found"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + response.set(tickets); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java new file mode 100644 index 00000000..88af7844 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java @@ -0,0 +1,51 @@ +package com.booleanuk.api.cinema.models; + +import com.fasterxml.jackson.annotation.JsonIncludeProperties; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table +public class Ticket { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @ManyToOne + @JoinColumn(name = "customer_id", nullable = false) + @JsonIncludeProperties(value = {"name"}) + private Customer customer; + + @ManyToOne + @JoinColumn(name = "screening_id", nullable = false) + @JsonIncludeProperties(value = {"screenNumber"}) + private Screening screening; + + @Column + private int numSeats; + + // Resource timestamps: + // - https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#mapping-generated + + @Column(updatable = false) + @CreationTimestamp + private LocalDateTime createdAt; + + @Column + @UpdateTimestamp + private LocalDateTime updatedAt; + + public Ticket(int numSeats) { + this.numSeats = numSeats; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/repositories/TicketRepository.java b/src/main/java/com/booleanuk/api/cinema/repositories/TicketRepository.java new file mode 100644 index 00000000..a99f5c25 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repositories/TicketRepository.java @@ -0,0 +1,10 @@ +package com.booleanuk.api.cinema.repositories; + +import com.booleanuk.api.cinema.models.Ticket; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TicketRepository extends JpaRepository { + List findAllByCustomerIdAndScreeningId(int customerId, int screeningId); +} diff --git a/src/main/java/com/booleanuk/api/cinema/responses/TicketListResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/TicketListResponse.java new file mode 100644 index 00000000..c0b3bec4 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/TicketListResponse.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.cinema.responses; + +import com.booleanuk.api.cinema.models.Ticket; + +import java.util.List; + +public class TicketListResponse extends Response>{ +} diff --git a/src/main/java/com/booleanuk/api/cinema/responses/TicketResponse.java b/src/main/java/com/booleanuk/api/cinema/responses/TicketResponse.java new file mode 100644 index 00000000..f3bd55c9 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/TicketResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.responses; + +import com.booleanuk.api.cinema.models.Ticket; + +public class TicketResponse extends Response{ +}