diff --git a/.gitignore b/.gitignore index dc01f204..efdafb17 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ out/ ### VS Code ### .vscode/ + diff --git a/build.gradle b/build.gradle index 3d7f7607..ae1807dc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.3.1' - id 'io.spring.dependency-management' version '1.1.5' + id 'org.springframework.boot' version '3.4.2' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.booleanuk' @@ -13,6 +13,12 @@ java { } } +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + repositories { mavenCentral() } @@ -20,10 +26,15 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.postgresql:postgresql' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3' } tasks.named('test') { 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..57047dbf --- /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); + } +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/cinema/customers/Customer.java b/src/main/java/com/booleanuk/api/cinema/customers/Customer.java new file mode 100644 index 00000000..d0aba323 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/customers/Customer.java @@ -0,0 +1,51 @@ +package com.booleanuk.api.cinema.customers; + + +import com.booleanuk.api.cinema.screens.Screen; +import com.booleanuk.api.cinema.tickets.Ticket; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.List; + + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "customers") +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column(name = "name") + private String name; + + @Column(name = "email") + private String email; + + @Column(name = "phone") + private String phone; + + @Column(name = "date_created") + private String date_created; + + @Column(name = "date_updated") + private String date_updated; + + @OneToMany(mappedBy = "customer") + @JsonIgnoreProperties({"id", "customer"}) + private List tickets; + + public Customer(int id) { + this.id = id; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/customers/CustomerController.java b/src/main/java/com/booleanuk/api/cinema/customers/CustomerController.java new file mode 100644 index 00000000..be7eb484 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/customers/CustomerController.java @@ -0,0 +1,57 @@ +package com.booleanuk.api.cinema.customers; + + +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; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("customers") +public class CustomerController { + + @Autowired + CustomerRepository customerRepository; + + @GetMapping + public ResponseEntity> getAllCustomers() { + return new ResponseEntity<>(new Response<>(customerRepository.findAll()), HttpStatus.OK); + } + + @PostMapping + public ResponseEntity> createCustomer(@RequestBody Customer customer) { + if (customer == null) { + ErrorResponse errorResponse = new ErrorResponse(Map.of("message","Failed to create customer")); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_GATEWAY); + } + customer.setDate_created(String.valueOf(LocalDateTime.now())); + return new ResponseEntity<>(new Response<>(this.customerRepository.save(customer)), HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updateCustomer(@PathVariable(name = "id") int id, @RequestBody Customer customer) { + Customer customerToUpdate = this.customerRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such customer.")); + customerToUpdate.setName(customer.getName()); + customerToUpdate.setEmail(customer.getEmail()); + customerToUpdate.setPhone(customer.getPhone()); + customerToUpdate.setDate_updated(String.valueOf(LocalDateTime.now())); + return new ResponseEntity<>(this.customerRepository.save(customerToUpdate), HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCustomer(@PathVariable(name = "id") int id) { + Customer toDelete = this.customerRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such customer found")); + this.customerRepository.delete(toDelete); + return ResponseEntity.ok(toDelete); + + } + +} diff --git a/src/main/java/com/booleanuk/api/cinema/customers/CustomerRepository.java b/src/main/java/com/booleanuk/api/cinema/customers/CustomerRepository.java new file mode 100644 index 00000000..f644212f --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/customers/CustomerRepository.java @@ -0,0 +1,5 @@ +package com.booleanuk.api.cinema.customers; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomerRepository extends JpaRepository {} diff --git a/src/main/java/com/booleanuk/api/cinema/movies/Movie.java b/src/main/java/com/booleanuk/api/cinema/movies/Movie.java new file mode 100644 index 00000000..167b1a1e --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/movies/Movie.java @@ -0,0 +1,51 @@ +package com.booleanuk.api.cinema.movies; + +import com.booleanuk.api.cinema.screens.Screen; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import java.util.List; + + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "movies") +public class Movie { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column(name = "title") + private String title; + + @Column(name = "rating") + private String rating; + + @Column(name = "description") + private String description; + + @Column(name = "runtime_mins") + private int runtime_mins; + + @Column(name = "created_at") + private String created_at; + + @Column(name = "updated_at") + private String updated_at; + + @OneToMany(mappedBy = "movie") + @JsonIgnoreProperties({"id", "movie"}) + private List screens; + + public Movie(int id) { + this.id = id; + } + +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/cinema/movies/MovieController.java b/src/main/java/com/booleanuk/api/cinema/movies/MovieController.java new file mode 100644 index 00000000..c99be3f9 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/movies/MovieController.java @@ -0,0 +1,59 @@ +package com.booleanuk.api.cinema.movies; + + +import com.booleanuk.api.cinema.screens.Screen; +import com.booleanuk.api.cinema.screens.ScreenRepository; +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.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@RestController +@RequestMapping("movies") +public class MovieController { + + @Autowired + MovieRepository movieRepository; + + @Autowired + ScreenRepository screenRepository; + + @GetMapping + public ResponseEntity> getAllMovies() { + return ResponseEntity.ok(movieRepository.findAll()); + } + + @PostMapping + public ResponseEntity createMovie(@RequestBody Movie movie) { + List screenList = new ArrayList<>(); + movie.setScreens(screenList); + movie.setCreated_at(String.valueOf(LocalDateTime.now())); + return new ResponseEntity<>(this.movieRepository.save(movie), HttpStatus.CREATED); + } + + @PutMapping("/{id}") + public ResponseEntity updateMovie(@PathVariable(name = "id") int id, @RequestBody Movie movie) { + Movie movieToUpdate = this.movieRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such movie.")); + movieToUpdate.setTitle(movie.getTitle()); + movieToUpdate.setDescription(movie.getDescription()); + movieToUpdate.setRating(movie.getRating()); + movieToUpdate.setRuntime_mins(movie.getRuntime_mins()); + movieToUpdate.setUpdated_at(String.valueOf(LocalDateTime.now())); + return new ResponseEntity<>(this.movieRepository.save(movieToUpdate), HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteMovie(@PathVariable(name = "id") int id) { + Movie toDelete = this.movieRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such movie found")); + this.screenRepository.deleteAll(toDelete.getScreens()); + this.movieRepository.delete(toDelete); + return ResponseEntity.ok(toDelete); + + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/movies/MovieRepository.java b/src/main/java/com/booleanuk/api/cinema/movies/MovieRepository.java new file mode 100644 index 00000000..e13298f6 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/movies/MovieRepository.java @@ -0,0 +1,5 @@ +package com.booleanuk.api.cinema.movies; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MovieRepository extends JpaRepository {} 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..35375dcb --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/ErrorResponse.java @@ -0,0 +1,18 @@ +package com.booleanuk.api.cinema.responses; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Getter + +public class ErrorResponse extends Response>{ + + public ErrorResponse(Map data) { + super(data); + this.status = "Error"; + + } +} \ No newline at end of file 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..473d50ae --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/responses/Response.java @@ -0,0 +1,15 @@ +package com.booleanuk.api.cinema.responses; + +import lombok.Getter; + +@Getter +public class Response { + protected String status; + protected T data; + + public Response(T data) { + this.status = "Success"; + this.data = data; + } + +} diff --git a/src/main/java/com/booleanuk/api/cinema/screens/Screen.java b/src/main/java/com/booleanuk/api/cinema/screens/Screen.java new file mode 100644 index 00000000..d8a71b63 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/screens/Screen.java @@ -0,0 +1,56 @@ +package com.booleanuk.api.cinema.screens; + +import com.booleanuk.api.cinema.movies.Movie; +import com.booleanuk.api.cinema.tickets.Ticket; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "screens") +public class Screen { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column(name = "screen_number") + private int screen_number; + + @Column(name = "starts_at") + private String starts_at; + + @Column(name = "capacity") + private int capacity; + + @Column(name = "created_at") + private String created_at; + + @Column(name = "updated_at") + private String updated_at; + + @ManyToOne + @JoinColumn(name = "movie_id") + @JsonIgnore + private Movie movie; + + @OneToMany(mappedBy = "screen") + @JsonIgnoreProperties({"id", "screen"}) + private List tickets; + + public Screen(int id) { + this.id = id; + } + +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/cinema/screens/ScreenController.java b/src/main/java/com/booleanuk/api/cinema/screens/ScreenController.java new file mode 100644 index 00000000..6cf90a9d --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/screens/ScreenController.java @@ -0,0 +1,72 @@ +package com.booleanuk.api.cinema.screens; + + + +import com.booleanuk.api.cinema.customers.Customer; +import com.booleanuk.api.cinema.movies.MovieRepository; +import com.booleanuk.api.cinema.movies.Movie; +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.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("movies") +public class ScreenController { + + @Autowired + ScreenRepository screenRepository; + + @Autowired + MovieRepository movieRepository; + + @GetMapping("{id}/screenings") + public ResponseEntity> getAllScreens(@PathVariable(name = "id") int id) { + Movie movie = this.movieRepository.findById(id) + .orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such movie!")); + + return ResponseEntity.ok(movie.getScreens()); + } + + @PostMapping("{id}/screenings") + public ResponseEntity createScreen(@PathVariable(name = "id") int id, @RequestBody Screen screen) { + Movie movie = this.movieRepository.findById(id) + .orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such movie!")); + screen.setMovie(movie); + screen.setCreated_at(String.valueOf(LocalDateTime.now())); + return new ResponseEntity<>(this.screenRepository.save(screen), HttpStatus.CREATED); + } + +/* NOT DONE!! CONTAINS ERRORS! + @PutMapping("/{id}") + public ResponseEntity updateScreen(@PathVariable(name = "id") int id, @RequestBody Screen screen) { + Screen screenToUpdate = this.screenRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such screen.")); + + Movie movie = this.movieRepository.findById(screen.getMovie().getId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such movie")); + + screenToUpdate.setMovie(movie); + screenToUpdate.setScreen_number(screen.getScreen_number()); + screenToUpdate.setStarts_at(screen.getStarts_at()); + screenToUpdate.setCapacity(screen.getCapacity()); + screenToUpdate.setUpdated_at(LocalDateTime.now()); + return new ResponseEntity<>(this.screenRepository.save(screenToUpdate), HttpStatus.OK); + } + + + @DeleteMapping("/{id}") + public ResponseEntity deleteScreen(@PathVariable(name = "id") int id) { + Screen toDelete = this.screenRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No such customer found")); + this.screenRepository.delete(toDelete); + return ResponseEntity.ok(toDelete); + + } + + */ +} diff --git a/src/main/java/com/booleanuk/api/cinema/screens/ScreenRepository.java b/src/main/java/com/booleanuk/api/cinema/screens/ScreenRepository.java new file mode 100644 index 00000000..df32c974 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/screens/ScreenRepository.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.screens; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScreenRepository extends JpaRepository { +} diff --git a/src/main/java/com/booleanuk/api/cinema/tickets/Ticket.java b/src/main/java/com/booleanuk/api/cinema/tickets/Ticket.java new file mode 100644 index 00000000..62cf1c7b --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/tickets/Ticket.java @@ -0,0 +1,44 @@ +package com.booleanuk.api.cinema.tickets; + +import com.booleanuk.api.cinema.customers.Customer; +import com.booleanuk.api.cinema.screens.Screen; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "tickets") +public class Ticket { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private int num_seats; + + @ManyToOne + @JoinColumn(name = "customer_id") + @JsonIgnore + private Customer customer; + + @ManyToOne + @JoinColumn(name = "screening_id") + @JsonIgnore + private Screen screen; + + @Column(name = "created_at") + private String created_at; + + @Column(name = "updated_at") + private String updated_at; +} diff --git a/src/main/java/com/booleanuk/api/cinema/tickets/TicketController.java b/src/main/java/com/booleanuk/api/cinema/tickets/TicketController.java new file mode 100644 index 00000000..440a702a --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/tickets/TicketController.java @@ -0,0 +1,80 @@ +package com.booleanuk.api.cinema.tickets; + + +import com.booleanuk.api.cinema.customers.Customer; +import com.booleanuk.api.cinema.customers.CustomerRepository; +import com.booleanuk.api.cinema.responses.ErrorResponse; +import com.booleanuk.api.cinema.responses.Response; +import com.booleanuk.api.cinema.screens.Screen; +import com.booleanuk.api.cinema.screens.ScreenRepository; +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.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("customers") +public class TicketController { + + @Autowired + TicketRepository ticketRepository; + + @Autowired + CustomerRepository customerRepository; + + @Autowired + ScreenRepository screenRepository; + + @GetMapping("/{customerId}/screenings/{screeningId}") + public ResponseEntity> getAllTickets(@PathVariable(name = "customerId") int customerId, @PathVariable(name = "screeningId") int screeningId) { + + Customer customer = this.customerRepository.findById(customerId).orElse(null); + if (customer == null) { + ErrorResponse error = new ErrorResponse(Map.of("message","No such customer found")); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + // Check if the tickets belonging to the customer ID match the screenID + List tickets = new ArrayList<>(); + for(Ticket customerTicket : customer.getTickets()) { + if (customerTicket.getScreen().getId() == screeningId) { + tickets.add(customerTicket); + } + } + + if (tickets.isEmpty()) { + ErrorResponse noMatchingTicketsError = new ErrorResponse(Map.of("message","The customer exists but does not have any tickets with that screening ID")); + return new ResponseEntity<>(noMatchingTicketsError, HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(new Response<>(tickets), HttpStatus.OK); + + } + + @PostMapping("/{customerId}/screenings/{screeningId}") + public ResponseEntity> bookNewTicket(@PathVariable(name = "customerId") int customerId, + @PathVariable(name = "screeningId") int screeningId, + @RequestBody Ticket ticket) + { + + Customer customer = this.customerRepository.findById(customerId).orElse(null); + if (customer == null) { + ErrorResponse error = new ErrorResponse(Map.of("message","No such customer found")); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + Screen screen = this.screenRepository.findById(screeningId).orElse(null); + if (screen == null) { + ErrorResponse error = new ErrorResponse(Map.of("message","Customer exists but no such screen found.")); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + ticket.setCustomer(customer); + ticket.setScreen(screen); + ticket.setCreated_at(String.valueOf(LocalDateTime.now())); + return new ResponseEntity<>(new Response<>(this.ticketRepository.save(ticket)), HttpStatus.CREATED); + + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/tickets/TicketRepository.java b/src/main/java/com/booleanuk/api/cinema/tickets/TicketRepository.java new file mode 100644 index 00000000..6448b7eb --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/tickets/TicketRepository.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.tickets; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TicketRepository extends JpaRepository { +} diff --git a/src/main/resources/application.yml.example b/src/main/resources/application.yml.example deleted file mode 100644 index 275ec30f..00000000 --- a/src/main/resources/application.yml.example +++ /dev/null @@ -1,23 +0,0 @@ -server: - port: 4000 - error: - include-message: always - include-binding-errors: always - include-stacktrace: never - include-exception: false - -spring: - datasource: - url: jdbc:postgresql://DATABASE_URL:5432/DATABASE_NAME - username: DATABASE_USERNAME - password: DATABASE_PASSWORD - max-active: 3 - max-idle: 3 - jpa: - hibernate: - ddl-auto: update - properties: - hibernate: - dialect: org.hibernate.dialect.PostgreSQLDialect - format_sql: true - show-sql: true