From 7719ea16a6df243555b3c92d698e6a7af0bca2d1 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 21 Aug 2025 13:53:18 +0200 Subject: [PATCH 1/5] Setup complete --- build.gradle | 47 +++++++++++++------ .../controllers/CustomerController.java | 14 ++++++ .../cinema/controllers/MovieController.java | 13 +++++ .../controllers/ScreeningController.java | 14 ++++++ .../cinema/controllers/TicketController.java | 13 +++++ .../booleanuk/api/cinema/models/Customer.java | 33 +++++++++++++ .../booleanuk/api/cinema/models/ERole.java | 7 +++ .../booleanuk/api/cinema/models/Movie.java | 41 ++++++++++++++++ .../api/cinema/models/Screening.java | 35 ++++++++++++++ .../booleanuk/api/cinema/models/Ticket.java | 35 ++++++++++++++ .../cinema/repository/CustomerRepository.java | 7 +++ .../cinema/repository/MovieRepository.java | 7 +++ .../repository/ScreeningRepository.java | 7 +++ .../cinema/repository/TicketRepository.java | 7 +++ src/main/resources/application.yml.example | 23 --------- 15 files changed, 266 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Customer.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/ERole.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Movie.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Screening.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Ticket.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java delete mode 100644 src/main/resources/application.yml.example diff --git a/build.gradle b/build.gradle index 3d7f7607..b33d6708 100644 --- a/build.gradle +++ b/build.gradle @@ -1,31 +1,50 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.1' - id 'io.spring.dependency-management' version '1.1.5' + id 'java' + id 'org.springframework.boot' version '3.5.4' + id 'io.spring.dependency-management' version '1.1.7' } group = 'com.booleanuk' version = '0.0.1-SNAPSHOT' java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'org.postgresql:postgresql' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'io.jsonwebtoken:jjwt-api:0.12.7' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.7' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.7' + + 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' + testImplementation 'org.springframework.security:spring-security-test' + + implementation 'jakarta.validation:jakarta.validation-api:3.1.1' +} + +tasks.named('bootBuildImage') { + builder = 'paketobuildpacks/builder-jammy-base:latest' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } \ No newline at end of file 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..e10eb6e3 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java @@ -0,0 +1,14 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.repository.CustomerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("customers") +public class CustomerController { + @Autowired + CustomerRepository customerRepository; + +} 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..0148e1cf --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java @@ -0,0 +1,13 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.repository.MovieRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("movies") +public class MovieController { + @Autowired + MovieRepository movieRepository; +} 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..5c4e0a90 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java @@ -0,0 +1,14 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.repository.ScreeningRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("screenings") +public class ScreeningController { + @Autowired + ScreeningRepository screeningRepository; + +} 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..93ceb806 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java @@ -0,0 +1,13 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.repository.TicketRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("tickets") +public class TicketController { + @Autowired + TicketRepository ticketRepository; +} 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..d5719562 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Customer.java @@ -0,0 +1,33 @@ +package com.booleanuk.api.cinema.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@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; + + 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/models/ERole.java b/src/main/java/com/booleanuk/api/cinema/models/ERole.java new file mode 100644 index 00000000..42a35a11 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/ERole.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.models; + +public enum ERole { + ROLE_CUSTOMER, + ROLE_ADMIN, + ROLE_TICKETMASTER +} 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..90189e1d --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Movie.java @@ -0,0 +1,41 @@ +package com.booleanuk.api.cinema.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@Entity +@Table(name = "movies") +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; + + @OneToMany(mappedBy = "screening") + @JsonIgnoreProperties({"screening"}) + private List screenings; + + 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/models/Screening.java b/src/main/java/com/booleanuk/api/cinema/models/Screening.java new file mode 100644 index 00000000..e621e6e1 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Screening.java @@ -0,0 +1,35 @@ +package com.booleanuk.api.cinema.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@Data +@NoArgsConstructor +@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 String startsAt; + + public Screening(int screenNumber, int capacity, String startsAt){ + this.screenNumber = screenNumber; + this.capacity = capacity; + this.startsAt = startsAt; + } +} 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..778830cd --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java @@ -0,0 +1,35 @@ +package com.booleanuk.api.cinema.models; + +import com.fasterxml.jackson.annotation.JsonIncludeProperties; +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@Entity +@Table(name = "tickets") +public class Ticket { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column(insertable = false, updatable = false) + private int customer_id; + + @Column(insertable = false, updatable = false) + private int publisher_id; + + @Column + private int numSeats; + + @ManyToOne + @JoinColumn(name = "screening_id", nullable = false) + @JsonIncludeProperties(value = {"screenNumber", "capacity", "startsAt"}) + private Screening screening; + + @ManyToOne + @JoinColumn(name = "customer_id", nullable = false) + @JsonIncludeProperties(value = {"name", "email", "phone"}) + private Customer customer; +} 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..e69f07f0 --- /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.models.Customer; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomerRepository extends JpaRepository { +} 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..3f9be14d --- /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.models.Movie; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MovieRepository extends JpaRepository { +} 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..dea5ac67 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.models.Screening; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScreeningRepository extends JpaRepository { +} 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..dbe8b99d --- /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.models.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 From 10052eb8e2f9b2846499410e019d40e0f2942afc Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 21 Aug 2025 15:59:29 +0200 Subject: [PATCH 2/5] take 1 --- .../java/com/booleanuk/api/cinema/Main.java | 34 ++++++ .../cinema/controllers/AuthController.java | 100 ++++++++++++++++++ .../controllers/CustomerController.java | 82 +++++++++++++- .../cinema/controllers/MovieController.java | 85 ++++++++++++++- .../controllers/ScreeningController.java | 82 +++++++++++++- .../cinema/controllers/TicketController.java | 100 +++++++++++++++++- .../booleanuk/api/cinema/models/Movie.java | 5 +- .../com/booleanuk/api/cinema/models/Role.java | 23 ++++ .../api/cinema/models/Screening.java | 6 ++ .../booleanuk/api/cinema/models/Ticket.java | 2 +- .../com/booleanuk/api/cinema/models/User.java | 48 +++++++++ .../cinema/payload/request/LoginRequest.java | 15 +++ .../cinema/payload/request/SignupRequest.java | 29 +++++ .../response/CustomerListResponse.java | 8 ++ .../payload/response/CustomerResponse.java | 6 ++ .../payload/response/ErrorResponse.java | 19 ++++ .../cinema/payload/response/JwtResponse.java | 25 +++++ .../payload/response/MessageResponse.java | 14 +++ .../payload/response/MovieListResponse.java | 8 ++ .../payload/response/MovieResponse.java | 6 ++ .../api/cinema/payload/response/Response.java | 14 +++ .../response/ScreeningListResponse.java | 8 ++ .../payload/response/ScreeningResponse.java | 6 ++ .../payload/response/TicketListResponse.java | 8 ++ .../payload/response/TicketResponse.java | 6 ++ .../cinema/repository/CustomerRepository.java | 1 + .../cinema/repository/MovieRepository.java | 1 + .../api/cinema/repository/RoleRepository.java | 13 +++ .../repository/ScreeningRepository.java | 1 + .../cinema/repository/TicketRepository.java | 1 + .../api/cinema/repository/UserRepository.java | 15 +++ .../cinema/security/WebSecurityConfig.java | 68 ++++++++++++ .../security/jwt/AuthEntryPointJwt.java | 24 +++++ .../cinema/security/jwt/AuthTokenFilter.java | 54 ++++++++++ .../api/cinema/security/jwt/JwtUtils.java | 63 +++++++++++ .../security/services/UserDetailsImpl.java | 59 +++++++++++ .../services/UserDetailsServiceImpl.java | 26 +++++ 37 files changed, 1050 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/booleanuk/api/cinema/Main.java create mode 100644 src/main/java/com/booleanuk/api/cinema/controllers/AuthController.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/Role.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/User.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/request/LoginRequest.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/request/SignupRequest.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/CustomerListResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/CustomerResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/ErrorResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/JwtResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/MessageResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/MovieListResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/MovieResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/Response.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningListResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/TicketListResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/payload/response/TicketResponse.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java create mode 100644 src/main/java/com/booleanuk/api/cinema/repository/UserRepository.java create mode 100644 src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java create mode 100644 src/main/java/com/booleanuk/api/cinema/security/jwt/AuthEntryPointJwt.java create mode 100644 src/main/java/com/booleanuk/api/cinema/security/jwt/AuthTokenFilter.java create mode 100644 src/main/java/com/booleanuk/api/cinema/security/jwt/JwtUtils.java create mode 100644 src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsImpl.java create mode 100644 src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsServiceImpl.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..1d37c905 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/Main.java @@ -0,0 +1,34 @@ +package com.booleanuk.api.cinema; + +import com.booleanuk.api.cinema.models.ERole; +import com.booleanuk.api.cinema.models.Role; +import com.booleanuk.api.cinema.repository.RoleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main implements CommandLineRunner { + @Autowired + private RoleRepository roleRepository; + + public static void main(String[] args){ + SpringApplication.run(Main.class, args); + } + + @Override + public void run(String... args) { + if (this.roleRepository.existsByName(ERole.ROLE_CUSTOMER)) { + this.roleRepository.save(new Role(ERole.ROLE_CUSTOMER)); + } + + if (this.roleRepository.existsByName(ERole.ROLE_ADMIN)) { + this.roleRepository.save(new Role(ERole.ROLE_ADMIN)); + } + + if (this.roleRepository.existsByName(ERole.ROLE_TICKETMASTER)) { + this.roleRepository.save(new Role(ERole.ROLE_TICKETMASTER)); + } + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/AuthController.java b/src/main/java/com/booleanuk/api/cinema/controllers/AuthController.java new file mode 100644 index 00000000..763083b6 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/controllers/AuthController.java @@ -0,0 +1,100 @@ +package com.booleanuk.api.cinema.controllers; + +import com.booleanuk.api.cinema.models.ERole; +import com.booleanuk.api.cinema.models.Role; +import com.booleanuk.api.cinema.models.User; +import com.booleanuk.api.cinema.payload.request.LoginRequest; +import com.booleanuk.api.cinema.payload.request.SignupRequest; +import com.booleanuk.api.cinema.payload.response.JwtResponse; +import com.booleanuk.api.cinema.payload.response.MessageResponse; +import com.booleanuk.api.cinema.repository.*; +import com.booleanuk.api.cinema.security.jwt.JwtUtils; +import com.booleanuk.api.cinema.security.services.UserDetailsImpl; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("auth") +public class AuthController { + @Autowired + AuthenticationManager authenticationManager; + + @Autowired + UserRepository userRepository; + + @Autowired + RoleRepository roleRepository; + + @Autowired + PasswordEncoder encoder; + + @Autowired + JwtUtils jwtUtils; + + @PostMapping("/signin") + public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { + // If using a salt for password use it here + Authentication authentication = authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); + SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = jwtUtils.generateJwtToken(authentication); + + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + List roles = userDetails.getAuthorities().stream().map((item) -> item.getAuthority()) + .collect(Collectors.toList()); + return ResponseEntity + .ok(new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); + } + + @PostMapping("/signup") + public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signupRequest) { + if (userRepository.existsByUsername(signupRequest.getUsername())) { + return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken")); + } + if (userRepository.existsByEmail(signupRequest.getEmail())) { + return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!")); + } + // Create a new user add salt here if using one + User user = new User(signupRequest.getUsername(), signupRequest.getEmail(), encoder.encode(signupRequest.getPassword())); + Set strRoles = signupRequest.getRole(); + Set roles = new HashSet<>(); + + if (strRoles == null) { + Role userRole = roleRepository.findByName(ERole.ROLE_CUSTOMER).orElseThrow(() -> new RuntimeException("Error: Role is not found")); + roles.add(userRole); + } else { + strRoles.forEach((role) -> { + switch (role) { + case "admin": + Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN).orElseThrow(() -> new RuntimeException("Error: Role is not found")); + roles.add(adminRole); + break; + case "ticketmaster": + Role ticketMasterRole = roleRepository.findByName(ERole.ROLE_TICKETMASTER).orElseThrow(() -> new RuntimeException("Error: Role is not found")); + roles.add(ticketMasterRole); + break; + default: + Role customerRole = roleRepository.findByName(ERole.ROLE_CUSTOMER).orElseThrow(() -> new RuntimeException("Error: Role is not found")); + roles.add(customerRole); + break; + } + }); + } + user.setRoles(roles); + userRepository.save(user); + return ResponseEntity.ok((new MessageResponse("User registered successfully"))); + } +} 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 e10eb6e3..1caefac0 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/CustomerController.java @@ -1,14 +1,90 @@ package com.booleanuk.api.cinema.controllers; +import com.booleanuk.api.cinema.models.Customer; import com.booleanuk.api.cinema.repository.CustomerRepository; +import com.booleanuk.api.cinema.payload.response.CustomerListResponse; +import com.booleanuk.api.cinema.payload.response.CustomerResponse; +import com.booleanuk.api.cinema.payload.response.ErrorResponse; +import com.booleanuk.api.cinema.payload.response.Response; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("customers") public class CustomerController { @Autowired - CustomerRepository customerRepository; + private CustomerRepository customerRepository; + @GetMapping + public ResponseEntity getAllCustomers() { + CustomerListResponse customerListResponse = new CustomerListResponse(); + customerListResponse.set(this.customerRepository.findAll()); + return ResponseEntity.ok(customerListResponse); + } + + @PostMapping + public ResponseEntity> createCustomer(@RequestBody Customer customer) { + CustomerResponse customerResponse = new CustomerResponse(); + try { + customerResponse.set(this.customerRepository.save(customer)); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + return new ResponseEntity<>(customerResponse, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity> getCustomerById(@PathVariable int id) { + Customer customer = this.customerRepository.findById(id).orElse(null); + if (customer == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + CustomerResponse customerResponse = new CustomerResponse(); + customerResponse.set(customer); + return ResponseEntity.ok(customerResponse); + } + + @PutMapping("/{id}") + public ResponseEntity> updateCustomer(@PathVariable int id, @RequestBody Customer customer) { + Customer customerToUpdate = this.customerRepository.findById(id).orElse(null); + if (customerToUpdate == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + customerToUpdate.setEmail(customer.getEmail()); + customerToUpdate.setName(customer.getName()); + customerToUpdate.setPhone(customer.getPhone()); + + try { + customerToUpdate = this.customerRepository.save(customerToUpdate); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + CustomerResponse customerResponse = new CustomerResponse(); + customerResponse.set(customerToUpdate); + return new ResponseEntity<>(customerResponse, HttpStatus.CREATED); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteCustomer(@PathVariable int id) { + Customer customerToDelete = this.customerRepository.findById(id).orElse(null); + if (customerToDelete == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + this.customerRepository.delete(customerToDelete); + CustomerResponse customerResponse = new CustomerResponse(); + customerResponse.set(customerToDelete); + return ResponseEntity.ok(customerResponse); + } } 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 0148e1cf..020ca53d 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/MovieController.java @@ -1,13 +1,92 @@ package com.booleanuk.api.cinema.controllers; +import com.booleanuk.api.cinema.models.Movie; import com.booleanuk.api.cinema.repository.MovieRepository; +import com.booleanuk.api.cinema.payload.response.MovieListResponse; +import com.booleanuk.api.cinema.payload.response.MovieResponse; +import com.booleanuk.api.cinema.payload.response.ErrorResponse; +import com.booleanuk.api.cinema.payload.response.Response; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("movies") public class MovieController { @Autowired - MovieRepository movieRepository; + private MovieRepository movieRepository; + + @GetMapping + public ResponseEntity getAllMovies() { + MovieListResponse movieListResponse = new MovieListResponse(); + movieListResponse.set(this.movieRepository.findAll()); + return ResponseEntity.ok(movieListResponse); + } + + @PostMapping + public ResponseEntity> createMovie(@RequestBody Movie movie) { + MovieResponse movieResponse = new MovieResponse(); + try { + movieResponse.set(this.movieRepository.save(movie)); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + return new ResponseEntity<>(movieResponse, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity> getMovieById(@PathVariable int id) { + Movie movie = this.movieRepository.findById(id).orElse(null); + if (movie == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + MovieResponse movieResponse = new MovieResponse(); + movieResponse.set(movie); + return ResponseEntity.ok(movieResponse); + } + + @PutMapping("/{id}") + public ResponseEntity> updateMovie(@PathVariable int id, @RequestBody Movie movie) { + Movie movieToUpdate = this.movieRepository.findById(id).orElse(null); + if (movieToUpdate == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + movieToUpdate.setDescription(movie.getDescription()); + movieToUpdate.setRating(movie.getRating()); + movieToUpdate.setTitle(movie.getTitle()); + movieToUpdate.setRuntimeMins(movie.getRuntimeMins()); + movieToUpdate.setScreenings(movie.getScreenings()); + + try { + movieToUpdate = this.movieRepository.save(movieToUpdate); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + MovieResponse movieResponse = new MovieResponse(); + movieResponse.set(movieToUpdate); + return new ResponseEntity<>(movieResponse, HttpStatus.CREATED); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteMovie(@PathVariable int id) { + Movie movieToDelete = this.movieRepository.findById(id).orElse(null); + if (movieToDelete == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + this.movieRepository.delete(movieToDelete); + MovieResponse movieResponse = new MovieResponse(); + movieResponse.set(movieToDelete); + return ResponseEntity.ok(movieResponse); + } } 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 5c4e0a90..ebf73746 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java @@ -1,14 +1,90 @@ package com.booleanuk.api.cinema.controllers; +import com.booleanuk.api.cinema.models.Screening; import com.booleanuk.api.cinema.repository.ScreeningRepository; +import com.booleanuk.api.cinema.payload.response.ScreeningListResponse; +import com.booleanuk.api.cinema.payload.response.ScreeningResponse; +import com.booleanuk.api.cinema.payload.response.ErrorResponse; +import com.booleanuk.api.cinema.payload.response.Response; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("screenings") public class ScreeningController { @Autowired - ScreeningRepository screeningRepository; + private ScreeningRepository screeningRepository; + @GetMapping + public ResponseEntity getAllScreenings() { + ScreeningListResponse screeningListResponse = new ScreeningListResponse(); + screeningListResponse.set(this.screeningRepository.findAll()); + return ResponseEntity.ok(screeningListResponse); + } + + @PostMapping + public ResponseEntity> createScreening(@RequestBody Screening screening) { + ScreeningResponse screeningResponse = new ScreeningResponse(); + try { + screeningResponse.set(this.screeningRepository.save(screening)); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + return new ResponseEntity<>(screeningResponse, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity> getScreeningById(@PathVariable int id) { + Screening screening = this.screeningRepository.findById(id).orElse(null); + if (screening == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + ScreeningResponse screeningResponse = new ScreeningResponse(); + screeningResponse.set(screening); + return ResponseEntity.ok(screeningResponse); + } + + @PutMapping("/{id}") + public ResponseEntity> updateScreening(@PathVariable int id, @RequestBody Screening screening) { + Screening screeningToUpdate = this.screeningRepository.findById(id).orElse(null); + if (screeningToUpdate == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + screeningToUpdate.setScreenNumber(screening.getScreenNumber()); + screeningToUpdate.setCapacity(screening.getCapacity()); + screeningToUpdate.setStartsAt(screening.getStartsAt()); + + try { + screeningToUpdate = this.screeningRepository.save(screeningToUpdate); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + ScreeningResponse screeningResponse = new ScreeningResponse(); + screeningResponse.set(screeningToUpdate); + return new ResponseEntity<>(screeningResponse, HttpStatus.CREATED); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteScreening(@PathVariable int id) { + Screening screeningToDelete = this.screeningRepository.findById(id).orElse(null); + if (screeningToDelete == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + this.screeningRepository.delete(screeningToDelete); + ScreeningResponse screeningResponse = new ScreeningResponse(); + screeningResponse.set(screeningToDelete); + return ResponseEntity.ok(screeningResponse); + } } diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java index 93ceb806..f262fa5c 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java @@ -1,13 +1,107 @@ 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.repository.CustomerRepository; +import com.booleanuk.api.cinema.repository.ScreeningRepository; import com.booleanuk.api.cinema.repository.TicketRepository; +import com.booleanuk.api.cinema.payload.response.TicketListResponse; +import com.booleanuk.api.cinema.payload.response.TicketResponse; +import com.booleanuk.api.cinema.payload.response.ErrorResponse; +import com.booleanuk.api.cinema.payload.response.Response; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; @RestController @RequestMapping("tickets") public class TicketController { @Autowired - TicketRepository ticketRepository; + private TicketRepository ticketRepository; + + @Autowired + private CustomerRepository customerRepository; + + @Autowired + private ScreeningRepository screeningRepository; + + @GetMapping + public ResponseEntity getAllTickets() { + TicketListResponse ticketListResponse = new TicketListResponse(); + ticketListResponse.set(this.ticketRepository.findAll()); + return ResponseEntity.ok(ticketListResponse); + } + + @PostMapping + public ResponseEntity> createTicket(@RequestBody Ticket ticket) { + TicketResponse ticketResponse = new TicketResponse(); + try { + ticketResponse.set(this.ticketRepository.save(ticket)); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + return new ResponseEntity<>(ticketResponse, HttpStatus.CREATED); + } + + @GetMapping("/{id}") + public ResponseEntity> getTicketById(@PathVariable int id) { + Ticket ticket = this.ticketRepository.findById(id).orElse(null); + if (ticket == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + TicketResponse ticketResponse = new TicketResponse(); + ticketResponse.set(ticket); + return ResponseEntity.ok(ticketResponse); + } + + @PutMapping("/{id}") + public ResponseEntity> updateTicket(@PathVariable int id, @RequestBody Ticket ticket) { + Screening screening = screeningRepository.findById(ticket.getScreening_id()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + Customer customer = customerRepository.findById(ticket.getCustomer_id()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + + Ticket ticketToUpdate = this.ticketRepository.findById(id).orElse(null); + + if (ticketToUpdate == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + ticketToUpdate.setCustomer(customer); + ticketToUpdate.setScreening(screening); + ticketToUpdate.setNumSeats(ticket.getNumSeats()); + + try { + ticketToUpdate = this.ticketRepository.save(ticketToUpdate); + } catch (Exception e) { + ErrorResponse error = new ErrorResponse(); + error.set("Bad request"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + TicketResponse ticketResponse = new TicketResponse(); + ticketResponse.set(ticketToUpdate); + return new ResponseEntity<>(ticketResponse, HttpStatus.CREATED); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteTicket(@PathVariable int id) { + Ticket ticketToDelete = this.ticketRepository.findById(id).orElse(null); + if (ticketToDelete == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + this.ticketRepository.delete(ticketToDelete); + TicketResponse ticketResponse = new TicketResponse(); + ticketResponse.set(ticketToDelete); + return ResponseEntity.ok(ticketResponse); + } } 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 90189e1d..0e13b8e4 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Movie.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Movie.java @@ -28,10 +28,11 @@ public class Movie { @Column private int runtimeMins; - @OneToMany(mappedBy = "screening") - @JsonIgnoreProperties({"screening"}) + @OneToMany(mappedBy = "movie") + @JsonIgnoreProperties({"movie"}) private List screenings; + public Movie(String title, String rating, String description, int runtimeMins){ this.title = title; this.rating = rating; diff --git a/src/main/java/com/booleanuk/api/cinema/models/Role.java b/src/main/java/com/booleanuk/api/cinema/models/Role.java new file mode 100644 index 00000000..3b825dfb --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/Role.java @@ -0,0 +1,23 @@ +package com.booleanuk.api.cinema.models; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@Entity +@Table(name = "roles") +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Enumerated(EnumType.STRING) + @Column(length = 20) + private ERole name; + + public Role(ERole name) { + this.name = name; + } +} 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 e621e6e1..fa4d35e8 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Screening.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Screening.java @@ -1,6 +1,7 @@ package com.booleanuk.api.cinema.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; @@ -27,6 +28,11 @@ public class Screening { @Column private String startsAt; + @ManyToOne + @JoinColumn(name = "movie_id", nullable = false) + @JsonIncludeProperties(value = {"title", "runtimeMins"}) + private Movie movie; + public Screening(int screenNumber, int capacity, String startsAt){ this.screenNumber = screenNumber; this.capacity = capacity; diff --git a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java index 778830cd..aa50f2c2 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java @@ -18,7 +18,7 @@ public class Ticket { private int customer_id; @Column(insertable = false, updatable = false) - private int publisher_id; + private int screening_id; @Column private int numSeats; diff --git a/src/main/java/com/booleanuk/api/cinema/models/User.java b/src/main/java/com/booleanuk/api/cinema/models/User.java new file mode 100644 index 00000000..4025639d --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/User.java @@ -0,0 +1,48 @@ +package com.booleanuk.api.cinema.models; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashSet; +import java.util.Set; + +@NoArgsConstructor +@Data +@Entity +@Table(name = "users", + uniqueConstraints = { + @UniqueConstraint(columnNames = "username"), + @UniqueConstraint(columnNames = "email") + }) +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @NotBlank + @Size(max = 20) + private String username; + + @NotBlank + @Size(max = 50) + @Email + private String email; + + @NotBlank + @Size(max = 120) + private String password; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles = new HashSet<>(); + + public User(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/request/LoginRequest.java b/src/main/java/com/booleanuk/api/cinema/payload/request/LoginRequest.java new file mode 100644 index 00000000..c16393fe --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/request/LoginRequest.java @@ -0,0 +1,15 @@ +package com.booleanuk.api.cinema.payload.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LoginRequest { + @NotBlank + private String username; + + @NotBlank + private String password; +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/request/SignupRequest.java b/src/main/java/com/booleanuk/api/cinema/payload/request/SignupRequest.java new file mode 100644 index 00000000..f9f4d5d3 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/request/SignupRequest.java @@ -0,0 +1,29 @@ +package com.booleanuk.api.cinema.payload.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; + +import java.util.Set; + +@Getter +@Setter +public class SignupRequest { + @NotBlank + @Size(min = 3, max = 20) + private String username; + + @NotBlank + @Size(max = 50) + @Email + private String email; + + private Set role; + + @NotBlank + @Size(min = 6, max = 40) + private String password; + +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/CustomerListResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/CustomerListResponse.java new file mode 100644 index 00000000..8892a8cf --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/CustomerListResponse.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.cinema.payload.response; + +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/payload/response/CustomerResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/CustomerResponse.java new file mode 100644 index 00000000..bb8bc970 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/CustomerResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.payload.response; + +import com.booleanuk.api.cinema.models.Customer; + +public class CustomerResponse extends Response { +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/ErrorResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/ErrorResponse.java new file mode 100644 index 00000000..90029965 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/ErrorResponse.java @@ -0,0 +1,19 @@ +package com.booleanuk.api.cinema.payload.response; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@NoArgsConstructor +public class ErrorResponse extends Response> { + public void set(String message) { + this.status = "error"; + + Map reply = new HashMap<>(); + reply.put("message", message); + this.data = reply; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/JwtResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/JwtResponse.java new file mode 100644 index 00000000..f29a3819 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/JwtResponse.java @@ -0,0 +1,25 @@ +package com.booleanuk.api.cinema.payload.response; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class JwtResponse { + private String token; + private String type = "Bearer"; + private int id; + private String username; + private String email; + private List roles; + + public JwtResponse(String token, int id, String username, String email, List roles) { + this.token = token; + this.id = id; + this.username = username; + this.email = email; + this.roles = roles; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/MessageResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/MessageResponse.java new file mode 100644 index 00000000..485b570f --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/MessageResponse.java @@ -0,0 +1,14 @@ +package com.booleanuk.api.cinema.payload.response; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class MessageResponse { + private String message; + + public MessageResponse(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/MovieListResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/MovieListResponse.java new file mode 100644 index 00000000..ed5edea5 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/MovieListResponse.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.cinema.payload.response; + +import com.booleanuk.api.cinema.models.Movie; + +import java.util.List; + +public class MovieListResponse extends Response> { +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/MovieResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/MovieResponse.java new file mode 100644 index 00000000..3e0f1bbf --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/MovieResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.payload.response; + +import com.booleanuk.api.cinema.models.Movie; + +public class MovieResponse extends Response { +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/Response.java b/src/main/java/com/booleanuk/api/cinema/payload/response/Response.java new file mode 100644 index 00000000..f2dff66d --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/Response.java @@ -0,0 +1,14 @@ +package com.booleanuk.api.cinema.payload.response; + +import lombok.Getter; + +@Getter +public class Response { + protected String status; + protected T data; + + public void set(T data) { + this.status = "success"; + this.data = data; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningListResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningListResponse.java new file mode 100644 index 00000000..c65f09b5 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningListResponse.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.cinema.payload.response; + +import com.booleanuk.api.cinema.models.Screening; + +import java.util.List; + +public class ScreeningListResponse extends Response> { +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningResponse.java new file mode 100644 index 00000000..23f966d4 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/ScreeningResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.payload.response; + +import com.booleanuk.api.cinema.models.Screening; + +public class ScreeningResponse extends Response { +} diff --git a/src/main/java/com/booleanuk/api/cinema/payload/response/TicketListResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/TicketListResponse.java new file mode 100644 index 00000000..a9fba679 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/TicketListResponse.java @@ -0,0 +1,8 @@ +package com.booleanuk.api.cinema.payload.response; + +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/payload/response/TicketResponse.java b/src/main/java/com/booleanuk/api/cinema/payload/response/TicketResponse.java new file mode 100644 index 00000000..0a986fb4 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/payload/response/TicketResponse.java @@ -0,0 +1,6 @@ +package com.booleanuk.api.cinema.payload.response; + +import com.booleanuk.api.cinema.models.Ticket; + +public class TicketResponse extends Response { +} diff --git a/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java index e69f07f0..6b7bdcfa 100644 --- a/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java +++ b/src/main/java/com/booleanuk/api/cinema/repository/CustomerRepository.java @@ -2,6 +2,7 @@ import com.booleanuk.api.cinema.models.Customer; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; public interface CustomerRepository extends JpaRepository { } diff --git a/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java index 3f9be14d..506be448 100644 --- a/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java +++ b/src/main/java/com/booleanuk/api/cinema/repository/MovieRepository.java @@ -2,6 +2,7 @@ import com.booleanuk.api.cinema.models.Movie; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; public interface MovieRepository extends JpaRepository { } diff --git a/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java new file mode 100644 index 00000000..2f7fc77d --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java @@ -0,0 +1,13 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.models.ERole; +import com.booleanuk.api.cinema.models.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RoleRepository extends JpaRepository { + Optional findByName(ERole name); + + boolean existsByName(ERole name); +} diff --git a/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java index dea5ac67..965b1e6f 100644 --- a/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java +++ b/src/main/java/com/booleanuk/api/cinema/repository/ScreeningRepository.java @@ -2,6 +2,7 @@ import com.booleanuk.api.cinema.models.Screening; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; public interface ScreeningRepository extends JpaRepository { } diff --git a/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java index dbe8b99d..e4c25a4c 100644 --- a/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java +++ b/src/main/java/com/booleanuk/api/cinema/repository/TicketRepository.java @@ -2,6 +2,7 @@ import com.booleanuk.api.cinema.models.Ticket; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; public interface TicketRepository extends JpaRepository { } diff --git a/src/main/java/com/booleanuk/api/cinema/repository/UserRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/UserRepository.java new file mode 100644 index 00000000..6044940a --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/repository/UserRepository.java @@ -0,0 +1,15 @@ +package com.booleanuk.api.cinema.repository; + +import com.booleanuk.api.cinema.models.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); + + boolean existsByUsername(String username); + boolean existsByEmail(String email); +} diff --git a/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java new file mode 100644 index 00000000..edc3ce26 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java @@ -0,0 +1,68 @@ +package com.booleanuk.api.cinema.security; + +import com.booleanuk.api.cinema.security.jwt.AuthEntryPointJwt; +import com.booleanuk.api.cinema.security.jwt.AuthTokenFilter; +import com.booleanuk.api.cinema.security.services.UserDetailsServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig { + @Autowired + private UserDetailsServiceImpl userDetailsService; + + @Autowired + private AuthEntryPointJwt unauthorizedHandler; + + @Bean + public AuthTokenFilter authenticationJwtTokenFilter() { + return new AuthTokenFilter(); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService); + + //authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + + return authProvider; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf((csrf) -> csrf.disable()) + .exceptionHandling((exception) -> exception.authenticationEntryPoint(unauthorizedHandler)) + .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/auth/**").permitAll() + .requestMatchers("/tickets", "/tickets/**", "/movies", "/movies/**", "/screenings", "/screenings/**", "/customers", "/customers/**", "/books/**").authenticated() + ); + http.authenticationProvider(authenticationProvider()); + http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/security/jwt/AuthEntryPointJwt.java b/src/main/java/com/booleanuk/api/cinema/security/jwt/AuthEntryPointJwt.java new file mode 100644 index 00000000..1a5d63c2 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/security/jwt/AuthEntryPointJwt.java @@ -0,0 +1,24 @@ +package com.booleanuk.api.cinema.security.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class AuthEntryPointJwt implements AuthenticationEntryPoint { + private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + logger.error("Unauthorized error: {}", authException.getMessage()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized"); + } + +} diff --git a/src/main/java/com/booleanuk/api/cinema/security/jwt/AuthTokenFilter.java b/src/main/java/com/booleanuk/api/cinema/security/jwt/AuthTokenFilter.java new file mode 100644 index 00000000..192f22c2 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/security/jwt/AuthTokenFilter.java @@ -0,0 +1,54 @@ +package com.booleanuk.api.cinema.security.jwt; + +import com.booleanuk.api.cinema.security.services.UserDetailsServiceImpl; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class AuthTokenFilter extends OncePerRequestFilter { + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private UserDetailsServiceImpl userDetailsService; + + private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + try { + String jwt = parseJwt(request); + if (jwt != null && jwtUtils.validateJwtToken(jwt)) { + String username = jwtUtils.getUserNameFromJwtToken(jwt); + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception e) { + logger.error("Cannot set user authentication: {}", e.getMessage()); + } + filterChain.doFilter(request, response); + } + + private String parseJwt(HttpServletRequest request) { + String headerAuth = request.getHeader("Authorization"); + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/security/jwt/JwtUtils.java b/src/main/java/com/booleanuk/api/cinema/security/jwt/JwtUtils.java new file mode 100644 index 00000000..a780c3d6 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/security/jwt/JwtUtils.java @@ -0,0 +1,63 @@ +package com.booleanuk.api.cinema.security.jwt; + +import com.booleanuk.api.cinema.security.services.UserDetailsImpl; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Date; + +@Component +public class JwtUtils { + private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); + + @Value("${booleanuk.app.jwtSecret}") + private String jwtSecret; + + @Value("${booleanuk.app.jwtExpirationMs}") + private int jwtExpirationMs; + + public String generateJwtToken(Authentication authentication) { + UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); + + return Jwts.builder() + .subject((userPrincipal.getUsername())) + .issuedAt(new Date()) + .expiration(new Date((new Date()).getTime() + this.jwtExpirationMs)) + .signWith(this.key()) + .compact(); + } + + private SecretKey key() { + return Keys.hmacShaKeyFor(Decoders.BASE64.decode(this.jwtSecret)); + } + + public String getUserNameFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().getSubject(); + } + + public boolean validateJwtToken(String authToken) { + try { + Jwts.parser().verifyWith(this.key()).build().parse(authToken); + return true; + } catch (MalformedJwtException e) { + logger.error("Invalid JWT token: {}", e.getMessage()); + } catch (ExpiredJwtException e) { + logger.error("JWT token has expired: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + logger.error("JWT token is unsupported: {}", e.getMessage()); + } catch (IllegalArgumentException e) { + logger.error("JWT claims string is empty: {}", e.getMessage()); + } + return false; + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsImpl.java b/src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsImpl.java new file mode 100644 index 00000000..268543bf --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsImpl.java @@ -0,0 +1,59 @@ +package com.booleanuk.api.cinema.security.services; + +import com.booleanuk.api.cinema.models.User; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Getter +public class UserDetailsImpl implements UserDetails { + private static final long serialVersionUID = 1L; + + private int id; + private String username; + private String email; + + @JsonIgnore + private String password; + + private Collection authorities; + + public UserDetailsImpl(int id, String username, String email, String password, Collection authorities) { + this.id = id; + this.username = username; + this.email = email; + this.password = password; + this.authorities = authorities; + } + + public static UserDetailsImpl build(User user) { + List authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName().name())) + .collect(Collectors.toList()); + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPassword(), + authorities); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + UserDetailsImpl user = (UserDetailsImpl) o; + return Objects.equals(id, user.id); + } +} diff --git a/src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsServiceImpl.java b/src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsServiceImpl.java new file mode 100644 index 00000000..066003c1 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/security/services/UserDetailsServiceImpl.java @@ -0,0 +1,26 @@ +package com.booleanuk.api.cinema.security.services; + +import com.booleanuk.api.cinema.models.User; +import com.booleanuk.api.cinema.repository.UserRepository; +import jakarta.transaction.Transactional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + @Autowired + UserRepository userRepository; + + @Override + @Transactional + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = (User) userRepository.findByUsername(username).orElseThrow( + () -> new UsernameNotFoundException("User not found with username: " + username) + ); + return UserDetailsImpl.build(user); + + } +} From 6b260fb0ba3eaeccaa0d1bb130eced5f2141aa20 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 22 Aug 2025 11:55:25 +0200 Subject: [PATCH 3/5] Everything except Post works --- .../java/com/booleanuk/api/cinema/Main.java | 6 +-- .../controllers/ScreeningController.java | 38 ---------------- .../cinema/controllers/TicketController.java | 44 ------------------- .../booleanuk/api/cinema/models/Customer.java | 8 ++-- .../booleanuk/api/cinema/models/Movie.java | 10 +---- .../api/cinema/models/Screening.java | 13 +++--- .../booleanuk/api/cinema/models/Ticket.java | 2 +- .../api/cinema/repository/RoleRepository.java | 2 - 8 files changed, 15 insertions(+), 108 deletions(-) diff --git a/src/main/java/com/booleanuk/api/cinema/Main.java b/src/main/java/com/booleanuk/api/cinema/Main.java index 1d37c905..f4215fd7 100644 --- a/src/main/java/com/booleanuk/api/cinema/Main.java +++ b/src/main/java/com/booleanuk/api/cinema/Main.java @@ -19,15 +19,15 @@ public static void main(String[] args){ @Override public void run(String... args) { - if (this.roleRepository.existsByName(ERole.ROLE_CUSTOMER)) { + if (this.roleRepository.findByName(ERole.ROLE_CUSTOMER).isEmpty()) { this.roleRepository.save(new Role(ERole.ROLE_CUSTOMER)); } - if (this.roleRepository.existsByName(ERole.ROLE_ADMIN)) { + if (this.roleRepository.findByName(ERole.ROLE_ADMIN).isEmpty()) { this.roleRepository.save(new Role(ERole.ROLE_ADMIN)); } - if (this.roleRepository.existsByName(ERole.ROLE_TICKETMASTER)) { + if (this.roleRepository.findByName(ERole.ROLE_TICKETMASTER).isEmpty()) { this.roleRepository.save(new Role(ERole.ROLE_TICKETMASTER)); } } 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 ebf73746..e4dfffae 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java @@ -49,42 +49,4 @@ public ResponseEntity> getScreeningById(@PathVariable int id) { screeningResponse.set(screening); return ResponseEntity.ok(screeningResponse); } - - @PutMapping("/{id}") - public ResponseEntity> updateScreening(@PathVariable int id, @RequestBody Screening screening) { - Screening screeningToUpdate = this.screeningRepository.findById(id).orElse(null); - if (screeningToUpdate == null) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - screeningToUpdate.setScreenNumber(screening.getScreenNumber()); - screeningToUpdate.setCapacity(screening.getCapacity()); - screeningToUpdate.setStartsAt(screening.getStartsAt()); - - try { - screeningToUpdate = this.screeningRepository.save(screeningToUpdate); - } catch (Exception e) { - ErrorResponse error = new ErrorResponse(); - error.set("Bad request"); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } - ScreeningResponse screeningResponse = new ScreeningResponse(); - screeningResponse.set(screeningToUpdate); - return new ResponseEntity<>(screeningResponse, HttpStatus.CREATED); - } - - @DeleteMapping("/{id}") - public ResponseEntity> deleteScreening(@PathVariable int id) { - Screening screeningToDelete = this.screeningRepository.findById(id).orElse(null); - if (screeningToDelete == null) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - this.screeningRepository.delete(screeningToDelete); - ScreeningResponse screeningResponse = new ScreeningResponse(); - screeningResponse.set(screeningToDelete); - return ResponseEntity.ok(screeningResponse); - } } diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java index f262fa5c..dfb12926 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java @@ -60,48 +60,4 @@ public ResponseEntity> getTicketById(@PathVariable int id) { ticketResponse.set(ticket); return ResponseEntity.ok(ticketResponse); } - - @PutMapping("/{id}") - public ResponseEntity> updateTicket(@PathVariable int id, @RequestBody Ticket ticket) { - Screening screening = screeningRepository.findById(ticket.getScreening_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); - Customer customer = customerRepository.findById(ticket.getCustomer_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); - - Ticket ticketToUpdate = this.ticketRepository.findById(id).orElse(null); - - if (ticketToUpdate == null) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - ticketToUpdate.setCustomer(customer); - ticketToUpdate.setScreening(screening); - ticketToUpdate.setNumSeats(ticket.getNumSeats()); - - try { - ticketToUpdate = this.ticketRepository.save(ticketToUpdate); - } catch (Exception e) { - ErrorResponse error = new ErrorResponse(); - error.set("Bad request"); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } - TicketResponse ticketResponse = new TicketResponse(); - ticketResponse.set(ticketToUpdate); - return new ResponseEntity<>(ticketResponse, HttpStatus.CREATED); - } - - @DeleteMapping("/{id}") - public ResponseEntity> deleteTicket(@PathVariable int id) { - Ticket ticketToDelete = this.ticketRepository.findById(id).orElse(null); - if (ticketToDelete == null) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - this.ticketRepository.delete(ticketToDelete); - TicketResponse ticketResponse = new TicketResponse(); - ticketResponse.set(ticketToDelete); - return ResponseEntity.ok(ticketResponse); - } } 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 d5719562..4a55abf3 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Customer.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Customer.java @@ -25,9 +25,7 @@ public class Customer { @Column private String phone; - public Customer(String name, String email, String phone){ - this.name = name; - this.email = email; - this.phone = phone; - } + @OneToMany(mappedBy = "customer", cascade = CascadeType.REMOVE) + @JsonIgnoreProperties({"customer"}) + private List tickets; } 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 0e13b8e4..2a29c598 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Movie.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Movie.java @@ -28,15 +28,7 @@ public class Movie { @Column private int runtimeMins; - @OneToMany(mappedBy = "movie") + @OneToMany(mappedBy = "movie", cascade = CascadeType.REMOVE) @JsonIgnoreProperties({"movie"}) private List screenings; - - - 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/models/Screening.java b/src/main/java/com/booleanuk/api/cinema/models/Screening.java index fa4d35e8..561e3bf8 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Screening.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Screening.java @@ -28,14 +28,15 @@ public class Screening { @Column private String startsAt; + @Column(insertable = false, updatable = false) + private int movie_id; + + @OneToMany(mappedBy = "screening", cascade = CascadeType.REMOVE) + @JsonIgnoreProperties({"screening"}) + private List tickets; + @ManyToOne @JoinColumn(name = "movie_id", nullable = false) @JsonIncludeProperties(value = {"title", "runtimeMins"}) private Movie movie; - - public Screening(int screenNumber, int capacity, String startsAt){ - this.screenNumber = screenNumber; - this.capacity = capacity; - this.startsAt = startsAt; - } } diff --git a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java index aa50f2c2..a5d61989 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java @@ -21,7 +21,7 @@ public class Ticket { private int screening_id; @Column - private int numSeats; + private int num_seats; @ManyToOne @JoinColumn(name = "screening_id", nullable = false) diff --git a/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java b/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java index 2f7fc77d..7de52a6a 100644 --- a/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java +++ b/src/main/java/com/booleanuk/api/cinema/repository/RoleRepository.java @@ -8,6 +8,4 @@ public interface RoleRepository extends JpaRepository { Optional findByName(ERole name); - - boolean existsByName(ERole name); } From d534b3256fcb49d58bcbf4fc3b27b5f0365d93b1 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 22 Aug 2025 14:41:59 +0200 Subject: [PATCH 4/5] Extension complete --- .../controllers/ScreeningController.java | 40 ++++++++++--------- .../cinema/controllers/TicketController.java | 36 ++++++++--------- .../booleanuk/api/cinema/models/Customer.java | 27 +++++++++++++ .../booleanuk/api/cinema/models/Movie.java | 29 ++++++++++++++ .../api/cinema/models/Screening.java | 30 +++++++++++++- .../cinema/models/ScreeningRequestDTO.java | 10 +++++ .../booleanuk/api/cinema/models/Ticket.java | 33 ++++++++++++--- .../api/cinema/models/TicketRequestDTO.java | 7 ++++ .../cinema/security/WebSecurityConfig.java | 2 +- 9 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/booleanuk/api/cinema/models/ScreeningRequestDTO.java create mode 100644 src/main/java/com/booleanuk/api/cinema/models/TicketRequestDTO.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 e4dfffae..05dee896 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/ScreeningController.java @@ -1,6 +1,9 @@ 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.models.ScreeningRequestDTO; +import com.booleanuk.api.cinema.repository.MovieRepository; import com.booleanuk.api.cinema.repository.ScreeningRepository; import com.booleanuk.api.cinema.payload.response.ScreeningListResponse; import com.booleanuk.api.cinema.payload.response.ScreeningResponse; @@ -11,12 +14,17 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.OffsetDateTime; + @RestController -@RequestMapping("screenings") +@RequestMapping("movies/{id}/screenings") public class ScreeningController { @Autowired private ScreeningRepository screeningRepository; + @Autowired + private MovieRepository movieRepository; + @GetMapping public ResponseEntity getAllScreenings() { ScreeningListResponse screeningListResponse = new ScreeningListResponse(); @@ -25,28 +33,24 @@ public ResponseEntity getAllScreenings() { } @PostMapping - public ResponseEntity> createScreening(@RequestBody Screening screening) { + public ResponseEntity> createScreening(@PathVariable int id, @RequestBody ScreeningRequestDTO dto) { ScreeningResponse screeningResponse = new ScreeningResponse(); try { - screeningResponse.set(this.screeningRepository.save(screening)); + Movie movie = movieRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Movie not found")); + + Screening screening = new Screening(); + screening.setScreenNumber(dto.screenNumber()); + screening.setCapacity(dto.capacity()); + screening.setMovie(movie); + + screeningResponse.set(screeningRepository.save(screening)); + return new ResponseEntity<>(screeningResponse, HttpStatus.CREATED); + } catch (Exception e) { ErrorResponse error = new ErrorResponse(); - error.set("Bad request"); + error.set("Bad request: " + e.getMessage()); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } - return new ResponseEntity<>(screeningResponse, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity> getScreeningById(@PathVariable int id) { - Screening screening = this.screeningRepository.findById(id).orElse(null); - if (screening == null) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - ScreeningResponse screeningResponse = new ScreeningResponse(); - screeningResponse.set(screening); - return ResponseEntity.ok(screeningResponse); } } diff --git a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java index dfb12926..7c09baa1 100644 --- a/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java +++ b/src/main/java/com/booleanuk/api/cinema/controllers/TicketController.java @@ -3,6 +3,7 @@ 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.models.TicketRequestDTO; import com.booleanuk.api.cinema.repository.CustomerRepository; import com.booleanuk.api.cinema.repository.ScreeningRepository; import com.booleanuk.api.cinema.repository.TicketRepository; @@ -14,10 +15,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ResponseStatusException; @RestController -@RequestMapping("tickets") +@RequestMapping("customers/{customerId}/screenings/{screeningId}") public class TicketController { @Autowired private TicketRepository ticketRepository; @@ -36,28 +36,26 @@ public ResponseEntity getAllTickets() { } @PostMapping - public ResponseEntity> createTicket(@RequestBody Ticket ticket) { + public ResponseEntity> createTicket(@RequestBody TicketRequestDTO dto, @PathVariable int customerId, @PathVariable int screeningId) { TicketResponse ticketResponse = new TicketResponse(); try { - ticketResponse.set(this.ticketRepository.save(ticket)); + Customer customer = customerRepository.findById(customerId) + .orElseThrow(() -> new RuntimeException("Customer not found")); + Screening screening = screeningRepository.findById(screeningId) + .orElseThrow(() -> new RuntimeException("Screening not found")); + + Ticket ticket = new Ticket(); + ticket.setNumSeats(dto.numSeats()); + ticket.setCustomer(customer); + ticket.setScreening(screening); + + ticketResponse.set(ticketRepository.save(ticket)); + return new ResponseEntity<>(ticketResponse, HttpStatus.CREATED); + } catch (Exception e) { ErrorResponse error = new ErrorResponse(); - error.set("Bad request"); + error.set("Bad request: " + e.getMessage()); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } - return new ResponseEntity<>(ticketResponse, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity> getTicketById(@PathVariable int id) { - Ticket ticket = this.ticketRepository.findById(id).orElse(null); - if (ticket == null) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - TicketResponse ticketResponse = new TicketResponse(); - ticketResponse.set(ticket); - return ResponseEntity.ok(ticketResponse); } } 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 4a55abf3..aa190b47 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Customer.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Customer.java @@ -1,10 +1,12 @@ package com.booleanuk.api.cinema.models; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.OffsetDateTime; import java.util.List; @Data @@ -25,7 +27,32 @@ public class Customer { @Column private String phone; + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime createdAt; + + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime updatedAt; + @OneToMany(mappedBy = "customer", cascade = CascadeType.REMOVE) @JsonIgnoreProperties({"customer"}) private List tickets; + + public Customer(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } + + @PrePersist + protected void onCreate() { + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + this.updatedAt = OffsetDateTime.now(); + } } 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 2a29c598..f955da19 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Movie.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Movie.java @@ -1,10 +1,12 @@ package com.booleanuk.api.cinema.models; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.OffsetDateTime; import java.util.List; @Data @@ -28,7 +30,34 @@ public class Movie { @Column private int runtimeMins; + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime createdAt; + + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime updatedAt; + @OneToMany(mappedBy = "movie", cascade = CascadeType.REMOVE) @JsonIgnoreProperties({"movie"}) private List screenings; + + 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; + } + + @PrePersist + protected void onCreate() { + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + this.updatedAt = OffsetDateTime.now(); + } } 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 561e3bf8..62543f6f 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.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; @@ -8,6 +9,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; import java.util.List; @Data @@ -26,10 +28,16 @@ public class Screening { private int capacity; @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") private String startsAt; - @Column(insertable = false, updatable = false) - private int movie_id; + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime createdAt; + + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime updatedAt; @OneToMany(mappedBy = "screening", cascade = CascadeType.REMOVE) @JsonIgnoreProperties({"screening"}) @@ -39,4 +47,22 @@ public class Screening { @JoinColumn(name = "movie_id", nullable = false) @JsonIncludeProperties(value = {"title", "runtimeMins"}) private Movie movie; + + public Screening(int screenNumber, int capacity, String startsAt) { + this.screenNumber = screenNumber; + this.capacity = capacity; + this.startsAt = OffsetDateTime.now().toString(); + } + + + @PrePersist + protected void onCreate() { + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + this.updatedAt = OffsetDateTime.now(); + } } diff --git a/src/main/java/com/booleanuk/api/cinema/models/ScreeningRequestDTO.java b/src/main/java/com/booleanuk/api/cinema/models/ScreeningRequestDTO.java new file mode 100644 index 00000000..c665d6a1 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/ScreeningRequestDTO.java @@ -0,0 +1,10 @@ +package com.booleanuk.api.cinema.models; + +import jakarta.persistence.Column; + +public record ScreeningRequestDTO( + int screenNumber, + int capacity, + String startsAt, + int movie_id +) {} diff --git a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java index a5d61989..794966e7 100644 --- a/src/main/java/com/booleanuk/api/cinema/models/Ticket.java +++ b/src/main/java/com/booleanuk/api/cinema/models/Ticket.java @@ -1,10 +1,14 @@ package com.booleanuk.api.cinema.models; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.OffsetDateTime; + @Data @NoArgsConstructor @Entity @@ -14,22 +18,41 @@ public class Ticket { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; - @Column(insertable = false, updatable = false) - private int customer_id; + @Column + private int numSeats; - @Column(insertable = false, updatable = false) - private int screening_id; + @Column + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime createdAt; @Column - private int num_seats; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX") + private OffsetDateTime updatedAt; @ManyToOne @JoinColumn(name = "screening_id", nullable = false) @JsonIncludeProperties(value = {"screenNumber", "capacity", "startsAt"}) + @JsonIgnore private Screening screening; @ManyToOne @JoinColumn(name = "customer_id", nullable = false) @JsonIncludeProperties(value = {"name", "email", "phone"}) + @JsonIgnore private Customer customer; + + public Ticket(int numSeats) { + this.numSeats = numSeats; + } + + @PrePersist + protected void onCreate() { + this.createdAt = OffsetDateTime.now(); + this.updatedAt = OffsetDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + this.updatedAt = OffsetDateTime.now(); + } } diff --git a/src/main/java/com/booleanuk/api/cinema/models/TicketRequestDTO.java b/src/main/java/com/booleanuk/api/cinema/models/TicketRequestDTO.java new file mode 100644 index 00000000..7820d584 --- /dev/null +++ b/src/main/java/com/booleanuk/api/cinema/models/TicketRequestDTO.java @@ -0,0 +1,7 @@ +package com.booleanuk.api.cinema.models; + +public record TicketRequestDTO( + int numSeats, + int customer_id, + int screening_id +) {} diff --git a/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java index edc3ce26..9eea8000 100644 --- a/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java @@ -54,7 +54,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests((requests) -> requests .requestMatchers("/auth/**").permitAll() - .requestMatchers("/tickets", "/tickets/**", "/movies", "/movies/**", "/screenings", "/screenings/**", "/customers", "/customers/**", "/books/**").authenticated() + .requestMatchers("/movies", "/movies/**", "/customers", "/customers/**").authenticated() ); http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); From 4601cfd3eede58d82102f1b83abc6535e30652b1 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 22 Aug 2025 14:59:07 +0200 Subject: [PATCH 5/5] minor fixes to role accesses --- .../booleanuk/api/cinema/security/WebSecurityConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java index 9eea8000..a9b7c2e3 100644 --- a/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/api/cinema/security/WebSecurityConfig.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; @@ -54,7 +55,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests((requests) -> requests .requestMatchers("/auth/**").permitAll() - .requestMatchers("/movies", "/movies/**", "/customers", "/customers/**").authenticated() + .requestMatchers(HttpMethod.GET,"/movies", "/movies/**", "/customers", "/customers/**").authenticated() + .requestMatchers(HttpMethod.DELETE,"/movies", "/movies/**", "/customers", "/customers/**").hasAnyRole("ADMIN") + .requestMatchers(HttpMethod.PUT,"/movies", "/movies/**", "/customers", "/customers/**").hasAnyRole("ADMIN") + .requestMatchers(HttpMethod.POST,"/movies", "/movies/**", "/customers", "/customers/**").hasAnyRole("ADMIN") + .requestMatchers(HttpMethod.POST,"customers/{customerId}/screenings/{screeningId}").hasAnyRole("TICKETMASTER") ); http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);