diff --git a/.gitignore b/.gitignore index 6ac465db..244268f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ /.idea/ +.env diff --git a/README.md b/README.md index 5150e50f..502db251 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/_uV8Mn8f) # 📘 Projektarbete: JPA + Hibernate med GitHub-flöde Projektet genomförs som antingen en Java CLI-applikation eller med hjĂ€lp av JavaFX om ni vill ha ett grafiskt grĂ€nssnitt. @@ -9,11 +10,11 @@ Kommunikation med databasen ska ske med JPA och Hibernate, enligt code first-met ## đŸ—“ïž Veckoplanering med Checklista ### ✅ Vecka 1 – GrundlĂ€ggning och struktur -- [ ] Klona GitHub-repo -- [ ] Konfigurera persistence.xml eller anvĂ€nd PersistenceConfiguration i kod -- [ ] Skapa entiteter och verifiera tabellgenerering -- [ ] LĂ€gg till relationer (One-to-Many, Many-to-Many) -- [ ] Arbeta pĂ„ feature-branches och anvĂ€nd pull requests för kodgranskning +- [ x ] Klona GitHub-repo +- [ x ] Konfigurera persistence.xml eller anvĂ€nd PersistenceConfiguration i kod +- [ x ] Skapa entiteter och verifiera tabellgenerering +- [ x ] LĂ€gg till relationer (One-to-Many, Many-to-Many) +- [ x ] Arbeta pĂ„ feature-branches och anvĂ€nd pull requests för kodgranskning ### ✅ Vecka 2 – Funktionalitet och relationer - [ ] Dela upp funktioner mellan gruppmedlemmar diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..bf94e1be --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: "3.8" + +services: + mysql: + image: mysql:8.3 + container_name: movieapp_mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpass + MYSQL_DATABASE: moviedb + MYSQL_USER: movieuser + MYSQL_PASSWORD: moviepass + ports: + - "127.0.0.1:3306:3306" + volumes: + - mysql_data:/var/lib/mysql + +volumes: + mysql_data: diff --git a/pom.xml b/pom.xml index 909503d0..a360b4a8 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,16 @@ 5.21.0 + + com.google.code.gson + gson + 2.10.1 + + + io.github.cdimascio + dotenv-java + 3.0.0 + org.junit.jupiter junit-jupiter @@ -50,5 +60,42 @@ classgraph 4.8.184 + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + org.slf4j + slf4j-api + 2.0.13 + + + org.openjfx + javafx-media + 25.0.1 + + + org.openjfx + javafx-controls + 25.0.1 + + + org.openjfx + javafx-fxml + 25.0.1 + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + org.example.App + + + + diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 165e5cd5..949ab560 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -1,7 +1,23 @@ package org.example; -public class App { +import javafx.application.Application; +import javafx.stage.Stage; +import org.example.ui.MainApp; +import org.example.util.JPAUtil; + +public class App extends Application { + + @Override + public void start(Stage primaryStage) { + MainApp mainApp = new MainApp(); + mainApp.start(primaryStage); + } + public static void main(String[] args) { - System.out.println("Hello There!"); + launch(args); + + JPAUtil.inTransaction(em -> { + System.out.println("Database schema initialized"); + }); } } diff --git a/src/main/java/org/example/api/TmdbClient.java b/src/main/java/org/example/api/TmdbClient.java new file mode 100644 index 00000000..fbbc4750 --- /dev/null +++ b/src/main/java/org/example/api/TmdbClient.java @@ -0,0 +1,133 @@ +package org.example.api; + +import com.google.gson.Gson; +import io.github.cdimascio.dotenv.Dotenv; +import nonapi.io.github.classgraph.json.JSONUtils; +import org.example.dto.CreditsDTO; +import org.example.dto.MovieDetailsDTO; +import org.example.dto.TopRatedResponseDTO; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +public class TmdbClient { + + private final String apiKey; + private final String baseUrl; + private final HttpClient httpClient; + private final Gson gson; + + + public TmdbClient() { + this(HttpClient.newHttpClient(), new Gson()); + } + + + public TmdbClient(HttpClient httpClient, Gson gson) { + + Dotenv dotenv = Dotenv.load(); + + this.apiKey = dotenv.get("TMDB_API_KEY"); + this.baseUrl = dotenv.get("TMDB_BASE_URL"); + + if (apiKey == null || baseUrl == null) { + throw new IllegalArgumentException("TMDB config missing in .env"); + } + + this.httpClient = httpClient; + this.gson = gson; + } + + public TmdbClient(HttpClient httpClient, Gson gson, String apiKey, String baseUrl) { + if (apiKey == null || baseUrl == null) { + throw new IllegalArgumentException("TMDB config missing"); + } + + this.httpClient = httpClient; + this.gson = gson; + this.apiKey = apiKey; + this.baseUrl = baseUrl; + System.out.println("TMDB BASE URL = " + baseUrl); + } + + public TopRatedResponseDTO getTopRatedMovies() { + try { + String url = baseUrl + "/movie/top_rated?api_key=" + apiKey; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .timeout(Duration.ofSeconds(10)) + .GET() + .build(); + + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException( + "TMDB API error: HTTP " + response.statusCode() + " - " + response.body() + ); + } + + return gson.fromJson(response.body(), TopRatedResponseDTO.class); + + } catch (Exception e) { + throw new RuntimeException("Could not get top rated movies from TMDB", e); + } + } + + public MovieDetailsDTO getMovieDetails(int movieId) { + try { + String url = baseUrl + "/movie/" + movieId + "?api_key=" + apiKey; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .timeout(Duration.ofSeconds(10)) + .GET() + .build(); + + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException( + "TMDB API error: HTTP " + response.statusCode() + " - " + response.body() + ); + } + + return gson.fromJson(response.body(), MovieDetailsDTO.class); + + } catch (Exception e) { + throw new RuntimeException("Could not get movie details from TMDB", e); + } + } + + public CreditsDTO getMovieCredits(int movieId) { + try { + String url = baseUrl + "/movie/" + movieId + "/credits?api_key=" + apiKey; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .timeout(Duration.ofSeconds(10)) + .GET() + .build(); + + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException( + "TMDB API error: HTTP " + response.statusCode() + " - " + response.body() + ); + } + + return gson.fromJson(response.body(), CreditsDTO.class); + + } catch (Exception e) { + throw new RuntimeException("Could not get movie credits from TMDB", e); + } + } +} diff --git a/src/main/java/org/example/dto/CastDTO.java b/src/main/java/org/example/dto/CastDTO.java new file mode 100644 index 00000000..4ca68cba --- /dev/null +++ b/src/main/java/org/example/dto/CastDTO.java @@ -0,0 +1,10 @@ +package org.example.dto; + +import com.google.gson.annotations.SerializedName; + +public record CastDTO ( + int id, + String name, + @SerializedName("profile_path") String profilePath, + int order // 0 = main actor, 2 = second actor, 3 = third actor etc. +) {} diff --git a/src/main/java/org/example/dto/CreditsDTO.java b/src/main/java/org/example/dto/CreditsDTO.java new file mode 100644 index 00000000..1360473f --- /dev/null +++ b/src/main/java/org/example/dto/CreditsDTO.java @@ -0,0 +1,9 @@ +package org.example.dto; + +import java.util.List; + +public record CreditsDTO( + int id, + List cast, + List crew +) {} diff --git a/src/main/java/org/example/dto/CrewDTO.java b/src/main/java/org/example/dto/CrewDTO.java new file mode 100644 index 00000000..c8e54186 --- /dev/null +++ b/src/main/java/org/example/dto/CrewDTO.java @@ -0,0 +1,10 @@ +package org.example.dto; + +import com.google.gson.annotations.SerializedName; + +public record CrewDTO( + int id, + String name, + @SerializedName("profile_path") String profilePath, + String job +) {} diff --git a/src/main/java/org/example/dto/MovieDTO.java b/src/main/java/org/example/dto/MovieDTO.java new file mode 100644 index 00000000..6b2e3ce5 --- /dev/null +++ b/src/main/java/org/example/dto/MovieDTO.java @@ -0,0 +1,12 @@ +package org.example.dto; + +import com.google.gson.annotations.SerializedName; + +public record MovieDTO( + int id, + String title, + String overview, + @SerializedName("release_date") String releaseDate, + @SerializedName("vote_average") double voteAverage, + @SerializedName("poster_path") String posterPath +) {} diff --git a/src/main/java/org/example/dto/MovieDetailsDTO.java b/src/main/java/org/example/dto/MovieDetailsDTO.java new file mode 100644 index 00000000..908860a6 --- /dev/null +++ b/src/main/java/org/example/dto/MovieDetailsDTO.java @@ -0,0 +1,15 @@ +package org.example.dto; + +import com.google.gson.annotations.SerializedName; + +public record MovieDetailsDTO( + int id, + String title, + String overview, + @SerializedName("release_date") String releaseDate, + int runtime, + @SerializedName("vote_average") double voteAverage, + @SerializedName("poster_path") String posterPath + + // Should we add a genre DTO? +) {} diff --git a/src/main/java/org/example/dto/TopRatedResponseDTO.java b/src/main/java/org/example/dto/TopRatedResponseDTO.java new file mode 100644 index 00000000..88b5d414 --- /dev/null +++ b/src/main/java/org/example/dto/TopRatedResponseDTO.java @@ -0,0 +1,12 @@ +package org.example.dto; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public record TopRatedResponseDTO( + int page, + List results, + @SerializedName("total_pages") int totalPages, + @SerializedName("total_results") int totalResults +) {} diff --git a/src/main/java/org/example/movie/entity/Movie.java b/src/main/java/org/example/movie/entity/Movie.java new file mode 100644 index 00000000..78e125c9 --- /dev/null +++ b/src/main/java/org/example/movie/entity/Movie.java @@ -0,0 +1,88 @@ +package org.example.movie.entity; + +import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + + +@Entity +@Table(name = "movies") +public class Movie { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + private String genre; + + @Column(length = 1000) + private String description; + + @Column(nullable = false, unique = true) + private Integer tmdbId; + + @Column(name = "image_url") + private String imageUrl; + + private Integer releaseYear; + private Double imdbRating; + + @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL) + private List roles = new ArrayList<>(); + + protected Movie() {} + + public Movie(String title, Integer tmdbId) { + this.title = title; + this.tmdbId = tmdbId; + } + + public Long getId() { return id; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getGenre() { return genre; } + public void setGenre(String genre) { this.genre = genre; } + + public String getDescription() { return description; } + public void setDescription(String description) { + this.description = description; + } + + public String getImageUrl() { return imageUrl; } + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public Integer getReleaseYear() { return releaseYear; } + public void setReleaseYear(Integer releaseYear) { + this.releaseYear = releaseYear; + } + + public Double getImdbRating() { return imdbRating; } + public void setImdbRating(Double imdbRating) { + this.imdbRating = imdbRating; + } + + public Integer getTmdbId() { + return tmdbId; + } + + public void setTmdbId(Integer tmdbId) { + if (tmdbId == null || tmdbId <= 0) { + throw new IllegalArgumentException("tmdbId must be a positive number"); + } + this.tmdbId = tmdbId; + } + + public List getRoles() { return roles; } + + public void addRole(Role role) { + roles.add(role); + role.setMovie(this); + } +} diff --git a/src/main/java/org/example/movie/entity/Person.java b/src/main/java/org/example/movie/entity/Person.java new file mode 100644 index 00000000..1ebd3d02 --- /dev/null +++ b/src/main/java/org/example/movie/entity/Person.java @@ -0,0 +1,41 @@ +package org.example.movie.entity; + +import jakarta.persistence.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + + +@Entity +@Table(name = "persons") +public class Person { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) + private List roles = new ArrayList<>(); + + protected Person() {} + + public Person(String name) { + this.name = name; + } + + public Long getId() { return id; } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public List getRoles() { return roles; } + + public void addRole(Role role) { + roles.add(role); + role.setPerson(this); + } +} + diff --git a/src/main/java/org/example/movie/entity/Role.java b/src/main/java/org/example/movie/entity/Role.java new file mode 100644 index 00000000..5199e2f5 --- /dev/null +++ b/src/main/java/org/example/movie/entity/Role.java @@ -0,0 +1,63 @@ +package org.example.movie.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "roles") +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private RoleType roleType; + + private Integer creditOrder; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "movie_id", nullable = false) + private Movie movie; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "person_id", nullable = false) + private Person person; + + protected Role() {} + + public Role(RoleType roleType, Movie movie, Person person) { + this.roleType = roleType; + setMovie(movie); + setPerson(person); + } + + public Long getId() { return id; } + + public RoleType getRoleType() { return roleType; } + public void setRoleType(RoleType roleType) { + this.roleType = roleType; + } + + public Integer getCreditOrder() { return creditOrder; } + public void setCreditOrder(Integer creditOrder) { + this.creditOrder = creditOrder; + } + + public Movie getMovie() { return movie; } + public void setMovie(Movie movie) { + this.movie = movie; + if (movie != null && !movie.getRoles().contains(this)) { + movie.getRoles().add(this); + } + } + + public Person getPerson() { return person; } + public void setPerson(Person person) { + this.person = person; + if (person != null && !person.getRoles().contains(this)) { + person.getRoles().add(this); + } + } +} + diff --git a/src/main/java/org/example/movie/entity/RoleType.java b/src/main/java/org/example/movie/entity/RoleType.java new file mode 100644 index 00000000..f16d0769 --- /dev/null +++ b/src/main/java/org/example/movie/entity/RoleType.java @@ -0,0 +1,6 @@ +package org.example.movie.entity; + +public enum RoleType { + ACTOR, + DIRECTOR +} diff --git a/src/main/java/org/example/repository/MovieRepository.java b/src/main/java/org/example/repository/MovieRepository.java new file mode 100644 index 00000000..bb104cbc --- /dev/null +++ b/src/main/java/org/example/repository/MovieRepository.java @@ -0,0 +1,4 @@ +package org.example.repository; + +public class MovieRepository { +} diff --git a/src/main/java/org/example/repository/MovieRepositoryImp.java b/src/main/java/org/example/repository/MovieRepositoryImp.java new file mode 100644 index 00000000..718c8b1e --- /dev/null +++ b/src/main/java/org/example/repository/MovieRepositoryImp.java @@ -0,0 +1,4 @@ +package org.example.repository; + +public class MovieRepositoryImp { +} diff --git a/src/main/java/org/example/service/MovieServices.java b/src/main/java/org/example/service/MovieServices.java new file mode 100644 index 00000000..199295a4 --- /dev/null +++ b/src/main/java/org/example/service/MovieServices.java @@ -0,0 +1,4 @@ +package org.example.service; + +public class MovieServices { +} diff --git a/src/main/java/org/example/ui/ApiMovieDataSource.java b/src/main/java/org/example/ui/ApiMovieDataSource.java new file mode 100644 index 00000000..c38cbdd4 --- /dev/null +++ b/src/main/java/org/example/ui/ApiMovieDataSource.java @@ -0,0 +1,19 @@ +package org.example.ui; + +import java.util.List; +import org.example.api.TmdbClient; +import org.example.dto.MovieDTO; + +public class ApiMovieDataSource implements MovieDataSource { + + private final TmdbClient tmdbClient; + + public ApiMovieDataSource(TmdbClient tmdbClient) { + this.tmdbClient = tmdbClient; + } + + @Override + public List getTopRatedMovies() { + return tmdbClient.getTopRatedMovies().results(); // or searchMovies(), etc. + } +} diff --git a/src/main/java/org/example/ui/MainApp.java b/src/main/java/org/example/ui/MainApp.java new file mode 100644 index 00000000..7fa49193 --- /dev/null +++ b/src/main/java/org/example/ui/MainApp.java @@ -0,0 +1,51 @@ +package org.example.ui; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.example.api.TmdbClient; +import org.example.service.MovieServices; + +public class MainApp { + + private final MovieServices movieServices = new MovieServices(); + + public void start(Stage stage) { + try { + TmdbClient tmdbClient = new TmdbClient(); + MovieDataSource dataSource = new ApiMovieDataSource(tmdbClient); + var fxmlUrl = getClass().getResource("/MainView.fxml"); + if (fxmlUrl == null) { + throw new IllegalStateException("Cannot find FXML resource: /MainView.fxml"); + } + + FXMLLoader loader = new FXMLLoader(fxmlUrl); + + loader.setControllerFactory(type -> { + if (type == MainController.class) { + return new MainController(dataSource); + } + try { + return type.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + Scene scene = new Scene(loader.load()); + + stage.setScene(scene); + stage.setTitle("Movie Database App"); + stage.show(); + + } catch (Exception e) { + e.printStackTrace(); + javafx.scene.control.Alert alert = new javafx.scene.control.Alert( + javafx.scene.control.Alert.AlertType.ERROR, + "Failed to load the application UI: " + e.getMessage() + ); + alert.showAndWait(); + throw new RuntimeException("UI initialization failed", e); + } + } +} diff --git a/src/main/java/org/example/ui/MainController.java b/src/main/java/org/example/ui/MainController.java new file mode 100644 index 00000000..1b3dca9a --- /dev/null +++ b/src/main/java/org/example/ui/MainController.java @@ -0,0 +1,125 @@ +package org.example.ui; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.example.dto.MovieDTO; +import org.example.service.MovieServices; +import org.example.movie.entity.Movie; + +import java.util.List; +import java.util.Objects; + +public class MainController { + + /* @FXML + private ListView movieList; + + private final MovieServices movieServices; + + public MainController(MovieServices movieServices) { + this.movieServices = movieServices; + }*/ + + private final MovieDataSource dataSource; + + public MainController(MovieDataSource dataSource) { + this.dataSource = dataSource; + } + + @FXML + private FlowPane movieContainer; + + @FXML + public void initialize() { + try { + dataSource.getTopRatedMovies().forEach(movie -> + movieContainer.getChildren().add(createMovieCard(movie)) + ); + } catch (Exception e) { + e.printStackTrace(); + javafx.scene.control.Alert alert = new javafx.scene.control.Alert( + javafx.scene.control.Alert.AlertType.ERROR, + "Failed to load the application UI: " + e.getMessage() + ); + alert.showAndWait(); + throw new RuntimeException("UI initialization failed", e); + } + } + + private static final String IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"; + private static final double CARD_WIDTH = 160; + private static final double CARD_HEIGHT = 280; + private static final double POSTER_HEIGHT = 220; + + private VBox createMovieCard(MovieDTO movie) { + VBox card = new VBox(6); + card.setPrefSize(CARD_WIDTH, CARD_HEIGHT); + card.setMinSize(CARD_WIDTH, CARD_HEIGHT); + card.setMaxSize(CARD_WIDTH, CARD_HEIGHT); + + card.setStyle(""" + -fx-background-color: #1e1e1e; + -fx-padding: 6; + -fx-border-radius: 6; + -fx-background-radius: 6; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 8, 0, 0, 2); + """); + + + ImageView poster = new ImageView(); + poster.setFitWidth(CARD_WIDTH - 12); + poster.setFitHeight(POSTER_HEIGHT); + poster.setPreserveRatio(false); + + String posterPath = movie.posterPath(); + + if (posterPath != null && !posterPath.isBlank()) { + poster.setImage(new Image(IMAGE_BASE_URL + posterPath, true)); + } else { + poster.setImage(new Image(Objects.requireNonNull( + getClass().getResourceAsStream("/images/no-poster.jpg")))); + } + + + Label title = new Label(movie.title()); + title.setWrapText(true); + title.setMaxHeight(40); // ≈ 2 lines + title.setStyle(""" + -fx-text-fill: white; + -fx-font-size: 12px; + -fx-font-weight: bold; + """); + + Label overview = new Label(movie.overview()); + overview.setWrapText(true); + overview.setMaxWidth(150); + + VBox.setVgrow(title, Priority.NEVER); + + card.getChildren().addAll(poster, title); + return card; + } + + @FXML + private TextField searchField; + + + /* @FXML + private void loadMovies() { + movieList.getItems().clear(); + + *//* List movies = movieServices.getAllMovies();*//* //Placeholder - hĂ€r lĂ€gger vi in metoder som byggs i MovieServices, skickas till repos och JPA + + *//* for (Movie movie : movies) { + movieList.getItems().add(movie.getTitle()); + }*//* + }*/ +} diff --git a/src/main/java/org/example/ui/MovieDataSource.java b/src/main/java/org/example/ui/MovieDataSource.java new file mode 100644 index 00000000..5f1cec00 --- /dev/null +++ b/src/main/java/org/example/ui/MovieDataSource.java @@ -0,0 +1,10 @@ +package org.example.ui; + +import java.util.List; +import org.example.dto.MovieDTO; + +public interface MovieDataSource { + List getTopRatedMovies(); + + +} diff --git a/src/main/java/org/example/util/JPAUtil.java b/src/main/java/org/example/util/JPAUtil.java new file mode 100644 index 00000000..2cb6d31e --- /dev/null +++ b/src/main/java/org/example/util/JPAUtil.java @@ -0,0 +1,43 @@ +package org.example.util; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.Persistence; + +import java.util.function.Consumer; + +public class JPAUtil { + + private static final EntityManagerFactory emf; + + static { + emf = Persistence.createEntityManagerFactory("myPU"); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (emf.isOpen()) { + emf.close(); + } + })); + } + + public static EntityManager getEntityManager() { + return emf.createEntityManager(); + } + + public static void inTransaction(Consumer work) { + EntityManager em = getEntityManager(); + try { + var tx = em.getTransaction(); + tx.begin(); + work.accept(em); + tx.commit(); + } catch (Exception e) { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + throw e; + } finally { + em.close(); + } + } +} diff --git a/src/main/resources/Images/no-poster.jpg b/src/main/resources/Images/no-poster.jpg new file mode 100644 index 00000000..729aeea6 Binary files /dev/null and b/src/main/resources/Images/no-poster.jpg differ diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml new file mode 100644 index 00000000..f06ae868 --- /dev/null +++ b/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,52 @@ + + + + + + org.hibernate.jpa.HibernatePersistenceProvider + + + org.example.movie.entity.Movie + org.example.movie.entity.Person + org.example.movie.entity.Role + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/MainView.fxml b/src/main/resources/MainView.fxml new file mode 100644 index 00000000..5bedc821 --- /dev/null +++ b/src/main/resources/MainView.fxml @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +