diff --git a/build.gradle b/build.gradle index 3d7f7607..3d03b1c7 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,8 @@ dependencies { runtimeOnly 'org.postgresql:postgresql' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + compileOnly 'org.projectlombok:lombok:1.18.34' + annotationProcessor 'org.projectlombok:lombok' } 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..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); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/controller/CustomerController.java b/src/main/java/com/booleanuk/api/cinema/controller/CustomerController.java new file mode 100644 index 00000000..a236b379 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controller/CustomerController.java @@ -0,0 +1,70 @@ +package com.booleanuk.api.cinema.controller; + +import com.booleanuk.api.cinema.model.Customer; +import com.booleanuk.api.cinema.repository.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.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("customers") +public class CustomerController { + + @Autowired + private CustomerRepository customerRepository; + + @PostMapping + public ResponseEntity createCustomer(@RequestBody Customer customer) { + try { + customer.setCreatedAt(LocalDateTime.now()); + customer.setUpdatedAt(LocalDateTime.now()); + return new ResponseEntity<>(this.customerRepository.save(customer), HttpStatus.CREATED); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not save the customer: " + e.getMessage()); + } + } + + + @GetMapping + public ResponseEntity> getAllCustomers() { + return ResponseEntity.ok(this.customerRepository.findAll()); + } + + + @GetMapping("/{id}") + public ResponseEntity getOneCustomer(@PathVariable int id) { + Customer customer = this.customerRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Customer with ID " + id + " not found.") + ); + return ResponseEntity.ok(customer); + } + + + @PutMapping("/{id}") + public ResponseEntity updateCustomer(@PathVariable int id, @RequestBody Customer customer) { + Customer customerToUpdate = this.customerRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Customer with ID " + id + " not found.") + ); + customerToUpdate.setName(customer.getName()); + customerToUpdate.setEmail(customer.getEmail()); + customerToUpdate.setPhone(customer.getPhone()); + customerToUpdate.setUpdatedAt(LocalDateTime.now()); + return new ResponseEntity<>(this.customerRepository.save(customerToUpdate), HttpStatus.OK); + } + + + @DeleteMapping("/{id}") + public ResponseEntity deleteCustomer(@PathVariable int id) { + Customer customerToDelete = this.customerRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Customer with ID " + id + " not found.") + ); + this.customerRepository.delete(customerToDelete); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} + diff --git a/src/main/java/com/booleanuk/api/cinema/controller/MovieController.java b/src/main/java/com/booleanuk/api/cinema/controller/MovieController.java new file mode 100644 index 00000000..dd49b132 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controller/MovieController.java @@ -0,0 +1,69 @@ +package com.booleanuk.api.cinema.controller; + +import com.booleanuk.api.cinema.model.Movie; +import com.booleanuk.api.cinema.repository.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.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("movies") +public class MovieController { + + @Autowired + private MovieRepository movieRepository; + + @PostMapping + public ResponseEntity createMovie(@RequestBody Movie movie) { + try { + movie.setCreatedAt(LocalDateTime.now()); + movie.setUpdatedAt(LocalDateTime.now()); + return new ResponseEntity<>(this.movieRepository.save(movie), HttpStatus.CREATED); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not save the movie: " + e.getMessage()); + } + } + + @GetMapping + public ResponseEntity> getAllMovies() { + return ResponseEntity.ok(this.movieRepository.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity getOneMovie(@PathVariable int id) { + Movie movie = this.movieRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Movie with ID " + id + " not found.") + ); + return ResponseEntity.ok(movie); + } + + @PutMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public Movie updateMovie(@PathVariable int id, @RequestBody Movie updatedMovie) { + return this.movieRepository.findById(id) + .map(movie -> { + movie.setTitle(updatedMovie.getTitle()); + movie.setRating(updatedMovie.getRating()); + movie.setDescription(updatedMovie.getDescription()); + movie.setRuntimeMins(updatedMovie.getRuntimeMins()); + movie.setUpdatedAt(LocalDateTime.now()); + return this.movieRepository.save(movie); + }) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Movie with ID " + id + " not found.")); + } + + + @DeleteMapping("/{id}") + public ResponseEntity deleteMovie(@PathVariable int id) { + Movie movieToDelete = this.movieRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Movie with ID " + id + " not found.") + ); + this.movieRepository.delete(movieToDelete); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/controller/ScreeningController.java b/src/main/java/com/booleanuk/api/cinema/controller/ScreeningController.java new file mode 100644 index 00000000..9a12495e --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controller/ScreeningController.java @@ -0,0 +1,54 @@ +package com.booleanuk.api.cinema.controller; + +import com.booleanuk.api.cinema.model.Movie; +import com.booleanuk.api.cinema.model.Screening; +import com.booleanuk.api.cinema.repository.MovieRepository; +import com.booleanuk.api.cinema.repository.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 org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/movies") +public class ScreeningController { + + @Autowired + private MovieRepository movieRepository; + + @Autowired + private ScreeningRepository screeningRepository; + + @PostMapping("/{id}/screenings") + public ResponseEntity createScreening(@PathVariable int id, @RequestBody Screening screeningDetails) { + Movie movie = this.movieRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Movie with ID " + id + " not found.") + ); + + Screening screening = new Screening( + screeningDetails.getScreenNumber(), + screeningDetails.getCapacity(), + screeningDetails.getStartsAt(), + movie + ); + screening.setCreatedAt(LocalDateTime.now()); + screening.setUpdatedAt(LocalDateTime.now()); + + return new ResponseEntity<>(this.screeningRepository.save(screening), HttpStatus.CREATED); + } + + @GetMapping("/{id}/screenings") + public ResponseEntity> getAllScreeningsForMovie(@PathVariable int id) { + List screenings = this.screeningRepository.findByMovieId(id); + + if (screenings.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No screenings found for Movie with ID " + id); + } + + return ResponseEntity.ok(screenings); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/controller/TicketController.java b/src/main/java/com/booleanuk/api/cinema/controller/TicketController.java new file mode 100644 index 00000000..ba4483c7 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controller/TicketController.java @@ -0,0 +1,65 @@ +package com.booleanuk.api.cinema.controller; + +import com.booleanuk.api.cinema.model.Ticket; +import com.booleanuk.api.cinema.repository.TicketRepository; +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("/tickets") +public class TicketController { + + @Autowired + private TicketRepository ticketRepository; + + @PostMapping + public ResponseEntity createTicket(@RequestBody Ticket ticket) { + try { + ticket.setCreatedAt(LocalDateTime.now()); + ticket.setUpdatedAt(LocalDateTime.now()); + return new ResponseEntity<>(this.ticketRepository.save(ticket), HttpStatus.CREATED); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not create the ticket: " + e.getMessage()); + } + } + + @GetMapping + public ResponseEntity> getAllTickets() { + return ResponseEntity.ok(this.ticketRepository.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity getTicketById(@PathVariable int id) { + Ticket ticket = this.ticketRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found with ID: " + id) + ); + return ResponseEntity.ok(ticket); + } + + @PutMapping("/{id}") + public ResponseEntity updateTicket(@PathVariable int id, @RequestBody Ticket updatedTicket) { + Ticket ticket = this.ticketRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found with ID: " + id) + ); + ticket.setNumSeats(updatedTicket.getNumSeats()); + ticket.setCustomer(updatedTicket.getCustomer()); + ticket.setScreening(updatedTicket.getScreening()); + ticket.setUpdatedAt(LocalDateTime.now()); + return new ResponseEntity<>(this.ticketRepository.save(ticket), HttpStatus.OK); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteTicket(@PathVariable int id) { + Ticket ticket = this.ticketRepository.findById(id).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found with ID: " + id) + ); + this.ticketRepository.delete(ticket); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/dto/MovieDTO.java b/src/main/java/com/booleanuk/api/cinema/dto/MovieDTO.java new file mode 100644 index 00000000..b366126c --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/dto/MovieDTO.java @@ -0,0 +1,55 @@ +package com.booleanuk.api.cinema.dto; + +import java.util.List; + + +public class MovieDTO { + private String title; + private String rating; + private String description; + private Integer runtimeMins; + private List screenings; + + public MovieDTO() { + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getRating() { + return rating; + } + + public void setRating(String rating) { + this.rating = rating; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getRuntimeMins() { + return runtimeMins; + } + + public void setRuntimeMins(Integer runtimeMins) { + this.runtimeMins = runtimeMins; + } + + public List getScreenings() { + return screenings; + } + + public void setScreenings(List screenings) { + this.screenings = screenings; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/dto/ScreeningDTO.java b/src/main/java/com/booleanuk/api/cinema/dto/ScreeningDTO.java new file mode 100644 index 00000000..6c6a6029 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/dto/ScreeningDTO.java @@ -0,0 +1,36 @@ +package com.booleanuk.api.cinema.dto; + +import java.time.LocalDateTime; + +public class ScreeningDTO { + private Integer screenNumber; + private Integer capacity; + private LocalDateTime startsAt; + + public ScreeningDTO() { + } + + public Integer getScreenNumber() { + return screenNumber; + } + + public void setScreenNumber(Integer screenNumber) { + this.screenNumber = screenNumber; + } + + public Integer getCapacity() { + return capacity; + } + + public void setCapacity(Integer capacity) { + this.capacity = capacity; + } + + public LocalDateTime getStartsAt() { + return startsAt; + } + + public void setStartsAt(LocalDateTime startsAt) { + this.startsAt = startsAt; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/model/Customer.java b/src/main/java/com/booleanuk/api/cinema/model/Customer.java new file mode 100644 index 00000000..a03260ce --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/model/Customer.java @@ -0,0 +1,57 @@ +package com.booleanuk.api.cinema.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +@Entity +@Table(name = "Customers") +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private String name; + + @Column + private String email; + + @Column + private String phone; + + @Column + private LocalDateTime createdAt; + + @Column + private LocalDateTime updatedAt; + + @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) + private List tickets; + + public Customer(int id){ + this.id = id; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + public Customer(String name, String email, String phone){ + this.name = name; + this.email = email; + this.phone = phone; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + public Customer() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/cinema/model/Movie.java b/src/main/java/com/booleanuk/api/cinema/model/Movie.java new file mode 100644 index 00000000..acb209a9 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/model/Movie.java @@ -0,0 +1,57 @@ +package com.booleanuk.api.cinema.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "Movies") +@Getter +@Setter +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 + private LocalDateTime createdAt; + + @Column + private LocalDateTime updatedAt; + + @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List screenings = new ArrayList<>(); + + public Movie() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + public Movie(String title, String rating, String description, int runtimeMins) { + this.title = title; + this.rating = rating; + this.description = description; + this.runtimeMins = runtimeMins; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } +} + + diff --git a/src/main/java/com/booleanuk/api/cinema/model/Screening.java b/src/main/java/com/booleanuk/api/cinema/model/Screening.java new file mode 100644 index 00000000..e951f194 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/model/Screening.java @@ -0,0 +1,58 @@ +package com.booleanuk.api.cinema.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Setter +@Entity +@Table(name = "Screenings") +public class Screening { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private int screenNumber; + + @Column + private int capacity; + + @Column + private LocalDateTime startsAt; + + @Column + private LocalDateTime createdAt; + + @Column + private LocalDateTime updatedAt; + + @ManyToOne + @JoinColumn(name = "movie_id", nullable = false) + @JsonIgnore + private Movie movie; + + @OneToMany(mappedBy = "screening", cascade = CascadeType.ALL) + private List tickets; + + public Screening() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + public Screening(int screenNumber, int capacity, LocalDateTime startsAt, Movie movie) { + this.screenNumber = screenNumber; + this.capacity = capacity; + this.startsAt = startsAt; + this.movie = movie; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } +} + diff --git a/src/main/java/com/booleanuk/api/cinema/model/Ticket.java b/src/main/java/com/booleanuk/api/cinema/model/Ticket.java new file mode 100644 index 00000000..e1f79c57 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/model/Ticket.java @@ -0,0 +1,48 @@ +package com.booleanuk.api.cinema.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Entity +@Table(name = "Tickets") +public class Ticket { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private int numSeats; + + @Column + private LocalDateTime createdAt; + + @Column + private LocalDateTime updatedAt; + + @ManyToOne + @JoinColumn(name = "customer_id", nullable = false) + private Customer customer; + + @ManyToOne + @JoinColumn(name = "screening_id", nullable = false) + private Screening screening; + + public Ticket() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + public Ticket(int numSeats, Customer customer, Screening screening) { + this.numSeats = numSeats; + this.customer = customer; + this.screening = screening; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java new file mode 100644 index 00000000..9b0560b0 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.model.Customer; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomerRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java new file mode 100644 index 00000000..f6c7843b --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.model.Movie; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MovieRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java new file mode 100644 index 00000000..a1573703 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java @@ -0,0 +1,11 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.model.Screening; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ScreeningRepository extends JpaRepository { + List findByMovieId(int movieId); +} + diff --git a/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java new file mode 100644 index 00000000..ee891c02 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.model.Ticket; +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