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 @@
+[](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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+