Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ae9f784
Created skeleton of MVC files and main. Updated build.gradle to inclu…
Sep 11, 2024
5d96c18
Implemented basic models based on diagram.
Sep 11, 2024
17edd6b
Implemented basic customer controller with CRUD functionality.
Sep 11, 2024
092733a
Implemented basic movie controller with CRUD functionality. Set all f…
Sep 11, 2024
91d3864
Added actual requestmapping.
Sep 11, 2024
18886e6
Added @autowired.
Sep 11, 2024
2c5ad17
Updated customer and movie to use OffsetDateTime instead of LocalDate…
Sep 11, 2024
2a9c8c3
Implemented screening functionality satisfying core requirements. Add…
Sep 11, 2024
d14dc3e
Implemented screening functionality satisfying core requirements. Add…
Sep 11, 2024
73eecbc
Implemented foundation for database relationships.
Sep 12, 2024
2438b5b
Implemented custom responses.
Sep 12, 2024
275ad47
Refactored customer, and added new response type.
Sep 12, 2024
5307199
Implemented responsefactory to abstract the return types in the contr…
Sep 12, 2024
fd07e94
Updated customercontroller with new return.
Sep 12, 2024
6fa51c8
Updated moviecontroller to new response format.
Sep 12, 2024
6c28bfe
Updated screeningcontroller to new response format.
Sep 12, 2024
a46c65c
Updated ticketcontroller to new response format.
Sep 12, 2024
176ee28
Refactoring.
Sep 12, 2024
7a47b5e
Refactoring.
Sep 12, 2024
846b2fa
Implemented remaining functionality to satisfy extension criteria. Re…
Sep 12, 2024
6c198cc
Severe refactoring of movie and screenings. Implemented movie DTOs.
Sep 13, 2024
4976c32
Refactored customer and ticket.
Sep 13, 2024
9baa322
Implemented DTO for movierequest to decouple request object from data…
Sep 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ out/

### VS Code ###
.vscode/

src/main/resources/application.yml
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ dependencies {
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// Lombok
compileOnly 'org.projectlombok:lombok:1.18.34'
annotationProcessor 'org.projectlombok:lombok'
// To use @Valid
implementation 'org.springframework.boot:spring-boot-starter-validation'
// MapStruct for mapping DTOs
implementation("org.mapstruct:mapstruct:1.5.3.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")

}

tasks.named('test') {
Expand Down
Empty file.
11 changes: 11 additions & 0 deletions src/main/java/com/booleanuk/api/cinema/Main.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.booleanuk.api.cinema.customer.controller;

import com.booleanuk.api.cinema.customer.model.Customer;
import com.booleanuk.api.cinema.customer.model.CustomerResponseDTO;
import com.booleanuk.api.cinema.customer.repository.CustomerRepository;
import com.booleanuk.api.cinema.response.Response;
import com.booleanuk.api.cinema.response.ResponseFactory;
import com.booleanuk.api.cinema.screening.model.Screening;
import com.booleanuk.api.cinema.screening.repository.ScreeningRepository;
import com.booleanuk.api.cinema.ticket.model.Ticket;
import com.booleanuk.api.cinema.ticket.repository.TicketRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.booleanuk.api.cinema.response.ResponseFactory.*;

@RestController
@RequestMapping("/customers")
public class CustomerController {

@Autowired
CustomerRepository customerRepository;

@Autowired
TicketRepository ticketRepository;

@Autowired
ScreeningRepository screeningRepository;

// Workaround for exception 415
@PostMapping(consumes = {"application/json", "application/json;charset=UTF-8"})
public ResponseEntity<Response> addCustomer(@Valid @RequestBody Customer customer, BindingResult result) {

if (result.hasErrors()) {
return badRequestErrorResponse();
}

Customer savedCustomer = this.customerRepository.save(customer);
CustomerResponseDTO response = convertToCustomerResponseDTO(savedCustomer);
return createdSuccessResponse(response);
}

@GetMapping
public ResponseEntity<Response> getAllCustomers() {
List<CustomerResponseDTO> response = new ArrayList<>();
this.customerRepository.findAll().forEach(customer ->
response.add(convertToCustomerResponseDTO(customer))
);
return okSuccessResponse(response);
}

@GetMapping("/{id}")
public ResponseEntity<Response> getCustomerById(@PathVariable (name = "id") int id) {
return this.customerRepository.findById(id).
map(customer -> {
CustomerResponseDTO response = convertToCustomerResponseDTO(customer);
return okSuccessResponse(response);
})
.orElseGet(ResponseFactory::notFoundErrorResponse);
}

@PutMapping("/{id}")
public ResponseEntity<Response> updateCustomer(@PathVariable (name = "id") int id, @Valid @RequestBody Customer updatedCustomer, BindingResult result) {

if (result.hasErrors()) {
return badRequestErrorResponse();
}

return this.customerRepository.findById(id).map(customerToUpdate -> {
updateCustomerDetails(customerToUpdate, updatedCustomer);
Customer savedCustomer = this.customerRepository.save(customerToUpdate);
CustomerResponseDTO response = convertToCustomerResponseDTO(savedCustomer);
return createdSuccessResponse(response);
}).orElseGet(ResponseFactory::notFoundErrorResponse);
}

@DeleteMapping("/{id}")
public ResponseEntity<Response> deleteCustomer(@PathVariable (name = "id") int id){
return this.customerRepository.findById(id).map(customerToDelete -> {
this.customerRepository.delete(customerToDelete);
return okSuccessResponse(customerToDelete);
}).orElseGet(ResponseFactory::notFoundErrorResponse);
}

/* Tickets */
@PostMapping("/{customerId}/screenings/{screeningId}")
public ResponseEntity<Response> bookTicket(@PathVariable (name = "customerId") int customerId,
@PathVariable (name = "screeningId") int screeningId,
@Valid @RequestBody Ticket ticket, BindingResult result) {

if (result.hasErrors()) {
return badRequestErrorResponse();
}

Optional<Customer> optionalCustomer = this.customerRepository.findById(customerId);
if (optionalCustomer.isEmpty()){
return notFoundErrorResponse();
}

Optional<Screening> optionalScreening = this.screeningRepository.findById(screeningId);
if (optionalScreening.isEmpty()){
return notFoundErrorResponse();
}

Customer customer = optionalCustomer.get();
Screening screening = optionalScreening.get();

ticket.setCustomer(customer);
ticket.setScreening(screening);

Ticket savedTicket = this.ticketRepository.save(ticket);
return createdSuccessResponse(savedTicket);

}

@GetMapping("/{customerId}/screenings/{screeningId}")
public ResponseEntity<Response> getAllTickets(@PathVariable (name = "customerId") int customerId,
@PathVariable (name = "screeningId") int screeningId) {

if (this.customerRepository.findById(customerId).isEmpty()) {
return notFoundErrorResponse();
}

if (this.screeningRepository.findById(screeningId).isEmpty()) {
return notFoundErrorResponse();
}

Customer customer = this.customerRepository.findById(customerId).get();
Screening screening = this.screeningRepository.findById(screeningId).get();

List<Ticket> ticketList = this.ticketRepository.findAllByCustomerAndScreening(customer, screening);

return okSuccessResponse(ticketList);
}


private void updateCustomerDetails(Customer oldCustomer, Customer newCustomer) {
oldCustomer.setName(newCustomer.getName());
oldCustomer.setPhone(newCustomer.getPhone());
oldCustomer.setEmail(newCustomer.getEmail());
oldCustomer.setUpdatedAt(OffsetDateTime.now());
}

private CustomerResponseDTO convertToCustomerResponseDTO(Customer customer){
return new CustomerResponseDTO(customer.getId(), customer.getName(), customer.getEmail(), customer.getPhone(), customer.getCreatedAt(), customer.getUpdatedAt());
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.booleanuk.api.cinema.customer.model;

import com.booleanuk.api.cinema.ticket.model.Ticket;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@NoArgsConstructor

@Entity
@Table(name = "customers")
public class Customer {

public Customer (String name, String email, String phone) {
this.name = name;
this.email = email;
this.phone = phone;
this.tickets = new ArrayList<>();
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

@NotBlank(message = "name is required")
@Column(name = "name", nullable = false)
private String name;

@NotBlank(message = "email is required")
@Column(name = "email", nullable = false)
private String email;

@NotBlank(message = "phone is required")
@Column(name = "phone", nullable = false)
private String phone;

@Column(name = "createdAt", nullable = false)
private OffsetDateTime createdAt;

@Column(name = "updatedAt", nullable = false)
private OffsetDateTime updatedAt;

@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference(value = "customer-tickets")
List<Ticket> tickets;

@PrePersist
private void onCreate() {
/*
This method is called before the entity manager saves the entity to the database.
*/
this.createdAt = OffsetDateTime.now();
this.updatedAt = OffsetDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.booleanuk.api.cinema.customer.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.OffsetDateTime;

@Getter
@Setter
@NoArgsConstructor

public class CustomerResponseDTO {

public CustomerResponseDTO (int id, String name, String email, String phone, OffsetDateTime createdAt, OffsetDateTime updatedAt) {
this.id = id;
this.name = name;
this.email = email;
this.phone = phone;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

private int id;

private String name;

private String email;

private String phone;

private OffsetDateTime createdAt;

private OffsetDateTime updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.booleanuk.api.cinema.customer.repository;

import com.booleanuk.api.cinema.customer.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CustomerRepository extends JpaRepository<Customer, Integer> {
}
19 changes: 19 additions & 0 deletions src/main/java/com/booleanuk/api/cinema/mapper/MovieMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.booleanuk.api.cinema.mapper;

import com.booleanuk.api.cinema.movie.model.Movie;
import com.booleanuk.api.cinema.movie.model.MovieRequestDTO;
import com.booleanuk.api.cinema.movie.model.MovieResponseDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface MovieMapper {

// Ignored the targets when mapping.
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
Movie toEntity(MovieRequestDTO requestDTO);

MovieResponseDTO toResponseDTO(Movie movie);
}
Loading