diff --git a/.gitignore b/.gitignore
index bbaa093..13bbb79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
+node_modules
HELP.md
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
### STS ###
.apt_generated
@@ -12,12 +14,18 @@ target/
.settings
.springBeans
.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
@@ -25,16 +33,15 @@ target/
/dist/
/nbdist/
/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
### VS Code ###
.vscode/
-/.mvn/
+
+### Kotlin ###
+.kotlin
### Custom ###
/data/
/backend/src/main/resources/static/
/docker/docker-compose.yml
-/.gameyfin/
+/.gameyfin/
\ No newline at end of file
diff --git a/.run/Angular Application.run.xml b/.run/Angular Application.run.xml
deleted file mode 100644
index 4346cac..0000000
--- a/.run/Angular Application.run.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.run/Angular CLI Server.run.xml b/.run/Angular CLI Server.run.xml
deleted file mode 100644
index 60749b4..0000000
--- a/.run/Angular CLI Server.run.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 2555f0c..640629d 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,43 @@
on bare metal
+🌈 Themes (including light and dark mode)
+🔌 Easily expandable with plugins
+🔒 Integrates into your SSO solution via OAuth2 / OpenID Connect
-## Preview
+## Screenshots
-https://user-images.githubusercontent.com/9295182/197277953-d69464a4-d280-407b-9274-ae62e6917981.mp4
+`Work in progress`
## Installation
-### General
+`Work in progress`
-Since Gameyfin loads information from IGDB, you need to register yourself there. Follow [this guide](https://api-docs.igdb.com/#account-creation).
+## Contribute to Gameyfin
-### Docker
+`Work in progress`
-1. Download the `docker-compose.example.yml` file from this repository and rename it to just `docker-compose.yml`
-2. Edit the configuration values to your liking
-3. Run `docker-compose up -d`
-
-### Bare metal
-
-1. Make sure you have a JRE or JDK with version 18 or greater installed
-2. Download the latest `gameyfin.jar` and `gameyfin.properties` file from the releases page
-3. Edit the config options in the `gameyfin.properties` file
-4. Use the following command to start Gameyfin: `java -jar gameyfin.jar`
-5. Open the address of your Gameyfin host in your browser, Gameyfin runs under port 8080 by default
+Gameyfin v2 is written in Kotlin and uses the following libraries/frameworks:
+* Spring Boot 3 for the backend
+* Vaadin for the frontend
+* PF4J for the plugin system
+* H2 database for persistence
+* Micrometer and Prometheus for monitoring
\ No newline at end of file
diff --git a/assets/fix_game_mapping.png b/assets/fix_game_mapping.png
deleted file mode 100644
index 58eda1a..0000000
Binary files a/assets/fix_game_mapping.png and /dev/null differ
diff --git a/assets/game_detail_view.png b/assets/game_detail_view.png
deleted file mode 100644
index d829c4f..0000000
Binary files a/assets/game_detail_view.png and /dev/null differ
diff --git a/assets/game_mappings.png b/assets/game_mappings.png
deleted file mode 100644
index 30761b8..0000000
Binary files a/assets/game_mappings.png and /dev/null differ
diff --git a/assets/library_overview.png b/assets/library_overview.png
deleted file mode 100644
index 2feab80..0000000
Binary files a/assets/library_overview.png and /dev/null differ
diff --git a/assets/scan_library.png b/assets/scan_library.png
deleted file mode 100644
index e3c8c9e..0000000
Binary files a/assets/scan_library.png and /dev/null differ
diff --git a/backend/pom.xml b/backend/pom.xml
deleted file mode 100644
index 4b72099..0000000
--- a/backend/pom.xml
+++ /dev/null
@@ -1,288 +0,0 @@
-
-
- 4.0.0
-
-
- gameyfin
- de.grimsi
- 1.4.6-SNAPSHOT
-
-
- gameyfin-backend
-
- jar
-
-
- 21
-
- 1.7.0
- 2.2.0
- 2.15.1
- 1.21
- 3.11.4
- 3.25.2
- 5.0.0
- 0.4.0
- 0.8.11
-
-
- 3.3.1
-
-
-
-
-
- de.grimsi
- gameyfin-frontend
- ${project.parent.version}
- runtime
-
-
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
-
-
- org.springdoc
- springdoc-openapi-ui
- ${springdoc-openapi-ui.version}
-
-
-
-
- io.github.resilience4j
- resilience4j-reactor
- ${resilience4j.version}
-
-
- io.github.resilience4j
- resilience4j-ratelimiter
- ${resilience4j.version}
-
-
- io.github.resilience4j
- resilience4j-bulkhead
- ${resilience4j.version}
-
-
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
-
-
- com.h2database
- h2
- runtime
-
-
- org.flywaydb
- flyway-core
-
-
- org.hibernate.validator
- hibernate-validator
-
-
-
-
-
- commons-io
- commons-io
- ${commons-io.version}
-
-
-
-
- com.google.protobuf
- protobuf-java
- ${protobuf-java.version}
-
-
- com.google.protobuf
- protobuf-java-util
- ${protobuf-java.version}
-
-
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.jeasy
- easy-random-core
- ${easy-random.version}
- test
-
-
-
- com.squareup.okhttp3
- okhttp
- test
-
-
- com.squareup.okhttp3
- mockwebserver
- test
-
-
- com.google.jimfs
- jimfs
- 1.3.0
- test
-
-
-
-
-
- org.springframework.boot
- spring-boot-devtools
- runtime
- true
-
-
- org.springframework.boot
- spring-boot-configuration-processor
- true
-
-
- org.projectlombok
- lombok
- true
-
-
-
-
- gameyfin-${project.parent.version}
-
-
- ${basedir}/src/main/resources
- true
-
- **/*.properties
- **/*.yml
- **/*.yaml
- **/*.sql
- **/*.txt
- **/*.json
- **/*.js
- **/*.css
- **/*.html
- **/*.ico
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
-
- maven-resources-plugin
- ${maven-resources-plugin.version}
-
-
- copy-resources
- validate
-
- copy-resources
-
-
- ${basedir}/src/main/resources/static
-
-
- ${project.parent.basedir}/frontend/dist/frontend/
-
-
-
-
-
-
-
-
-
- com.github.os72
- protoc-jar-maven-plugin
- ${protoc.plugin.version}
-
-
- generate-sources
-
- run
-
-
- true
- false
-
- ${project.basedir}/src/main/resources/proto
-
-
-
- java
- ${project.build.directory}/generated-sources/protobuf
-
-
-
-
-
-
-
-
-
-
- org.jacoco
- jacoco-maven-plugin
- ${jacoco-maven-plugin.version}
-
-
- jacoco-initialize
-
- prepare-agent
-
-
-
- jacoco-site
- package
-
- report
-
-
-
-
-
-
-
-
diff --git a/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java b/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java
deleted file mode 100644
index b04a980..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package de.grimsi.gameyfin;
-
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.builder.SpringApplicationBuilder;
-import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
-
-@SpringBootApplication
-@ConfigurationPropertiesScan
-public class GameyfinApplication {
-
- public static void main(String[] args) {
- new SpringApplicationBuilder(GameyfinApplication.class)
- .properties( "file.encoding=UTF-8", "spring.config.name=application,gameyfin,database,secure")
- .build()
- .run(args);
- }
-
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/FileSystemProviderConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/FileSystemProviderConfig.java
deleted file mode 100644
index 61c4f59..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/FileSystemProviderConfig.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-
-/**
- * This class holds configuration for the default {@link java.nio.file.spi.FileSystemProvider} used by Gameyfin.
- */
-@Configuration
-public class FileSystemProviderConfig {
- /**
- * Configures the default {@link FileSystem} to be used.
- * This makes it easier to mock certain classes in unit tests.
- * @return the default FileSystem
- */
- @Bean
- public FileSystem defaultFileSystem() {
- return FileSystems.getDefault();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java
deleted file mode 100644
index cdcba6d..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import de.grimsi.gameyfin.service.FilesystemService;
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.event.ApplicationReadyEvent;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.event.EventListener;
-import org.springframework.core.annotation.Order;
-import org.springframework.core.env.ConfigurableEnvironment;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.util.Locale;
-
-/**
- * This class contains logic to the configuration of the filesystem which Gameyfin works on.
- * It handles creating required folders, setting up paths for Gameyfin and setting the correct properties of those folders.
- */
-@Configuration
-@RequiredArgsConstructor
-public class FilesystemConfig {
-
- @Value("${gameyfin.folders.data}")
- private String dataFolderPath;
-
- private final FilesystemService filesystemService;
-
- /**
- * This will create the cache folder for Gameyfin.
- * The path of this folder is specified in the "gameyfin.cache" parameter which is either:
- * 1. Derived from the first configured library folder path or
- * 2. Explicitly set by the user
- *
- * For more details see {@link GameyfinFolderConfig#setConfigurableEnvironment(ConfigurableEnvironment)}
- */
- @EventListener(ApplicationReadyEvent.class)
- @Order(1)
- public void createCacheFolder() {
- filesystemService.createCacheFolder();
- }
-
- /**
- * This will make sure that the internal folder (".gameyfin") is marked as hidden on DOS/Windows-based systems.
- * On UNIX-based systems files and folders starting with a dot are hidden
- */
- @EventListener(ApplicationReadyEvent.class)
- @Order(2)
- public void hideInternalFolderOnDOS() throws IOException {
- if (!isRunningOnWindows()) return;
-
- Path internalFolder = filesystemService.getPath(dataFolderPath);
-
- if (!Files.exists(internalFolder) || !Files.isDirectory(internalFolder)) return;
-
- Files.setAttribute(internalFolder, "dos:hidden", Boolean.TRUE, LinkOption.NOFOLLOW_LINKS);
- }
-
- private boolean isRunningOnWindows() {
- return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows");
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/FrontendConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/FrontendConfig.java
deleted file mode 100644
index 4fc7142..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/FrontendConfig.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-import org.springframework.lang.NonNull;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import org.springframework.web.servlet.resource.PathResourceResolver;
-
-import java.io.IOException;
-
-@Configuration
-public class FrontendConfig implements WebMvcConfigurer {
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("/**")
- .addResourceLocations("classpath:/static/")
- .resourceChain(true)
- .addResolver(new PathResourceResolver() {
- @Override
- protected Resource getResource(@NonNull String resourcePath, @NonNull Resource location) throws IOException {
- Resource requestedResource = location.createRelative(resourcePath);
- return requestedResource.exists() && requestedResource.isReadable() ? requestedResource : new ClassPathResource("/static/index.html");
- }
- }
- );
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/GameyfinFolderConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/GameyfinFolderConfig.java
deleted file mode 100644
index 5a2a2c1..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/GameyfinFolderConfig.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.jdbc.DataSourceBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.core.env.*;
-import org.springframework.util.StringUtils;
-
-import javax.sql.DataSource;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.stream.StreamSupport;
-
-/**
- * This class handles the creation of all folders used by Gameyfin.
- * It also stores their paths in Spring environment variables.
- */
-@Configuration
-@RequiredArgsConstructor
-public class GameyfinFolderConfig {
-
-
- @Value("${gameyfin.internal-folder}")
- private String INTERNAL_FOLDER_NAME;
-
- /**
- * The following SpEL expression will:
- * 1. Split the comma-seperated string contained in "gameyfin.sources" into elements
- * 2. Take the first element
- * 3. Assign its value to the variable
- */
- @Value("#{'${gameyfin.sources}'.split(',')[0]}")
- private String firstLibraryPath;
-
- @Value("${gameyfin.folders.data}")
- private String dataFolderPath;
-
- private final Environment env;
-
- /**
- * Dynamically sets the "gameyfin.db" and "gameyfin.cache" properties
- * if the "gameyfin.folders.data" property is *not* set by the user (default).
- *
- * @param env - The application environment, provided by the Spring container
- */
- @Autowired
- public void setConfigurableEnvironment(ConfigurableEnvironment env) {
- Properties props = new Properties();
-
- if (!StringUtils.hasText(dataFolderPath)) {
-
- //set the data folder property, so it can be referenced at runtime
- props.setProperty("gameyfin.folders.data", "%s/%s".formatted(firstLibraryPath, INTERNAL_FOLDER_NAME));
-
- props.setProperty("gameyfin.db", "%s/%s/db".formatted(firstLibraryPath, INTERNAL_FOLDER_NAME));
- props.setProperty("gameyfin.cache", "%s/%s/cache".formatted(firstLibraryPath, INTERNAL_FOLDER_NAME));
- } else {
-
- props.setProperty("gameyfin.db", "%s/%s/db".formatted(dataFolderPath, INTERNAL_FOLDER_NAME));
- props.setProperty("gameyfin.cache", "%s/%s/cache".formatted(dataFolderPath, INTERNAL_FOLDER_NAME));
- }
-
- env.getPropertySources().addFirst(new PropertiesPropertySource("gameyfinFilesystemProperties", props));
- }
-
- /**
- * This bean is needed so Spring initializes the data source after we are done messing with the configuration environment
- *
- * @return DataSource
- */
- @ConfigurationProperties(prefix = "spring.datasource")
- @Bean
- @Primary
- public DataSource getDataSource() {
- Properties properties = loadAllProperties();
-
- return DataSourceBuilder
- .create()
- .url(properties.getProperty("spring.datasource.url"))
- .build();
- }
-
- private Properties loadAllProperties() {
- Properties props = new Properties();
-
- MutablePropertySources propSrcs = ((AbstractEnvironment) env).getPropertySources();
-
- StreamSupport.stream(propSrcs.spliterator(), false)
- .filter(EnumerablePropertySource.class::isInstance)
- .map(ps -> ((EnumerablePropertySource>) ps).getPropertyNames())
- .flatMap(Arrays::stream)
- .forEach(propName -> props.setProperty(propName, env.getProperty(propName)));
-
- return props;
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java b/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java
deleted file mode 100644
index 3140b37..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.io.support.PropertiesLoaderUtils;
-
-import java.util.Objects;
-
-@Configuration
-public class SecureProperties {
-
- @Autowired
- public void setConfigurableEnvironment(ConfigurableEnvironment env) {
- try {
- Resource resource = new ClassPathResource("/config/secure.yml");
- env.getPropertySources().addFirst(new PropertiesPropertySource(Objects.requireNonNull(resource.getFilename()), PropertiesLoaderUtils.loadProperties(resource)));
- } catch (Exception ex) {
- throw new RuntimeException(ex.getMessage(), ex);
- }
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/SecurityConfiguration.java b/backend/src/main/java/de/grimsi/gameyfin/config/SecurityConfiguration.java
deleted file mode 100644
index 2e7ff76..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/SecurityConfiguration.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
-import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-
-@Configuration
-@EnableWebSecurity
-@EnableMethodSecurity
-@RequiredArgsConstructor
-public class SecurityConfiguration {
-
- @Value("${gameyfin.user}")
- private String username;
-
- @Value("${gameyfin.password}")
- private String password;
-
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http.csrf(AbstractHttpConfigurer::disable);
- http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
- http.httpBasic(Customizer.withDefaults());
- return http.build();
- }
-
- @Bean
- public InMemoryUserDetailsManager userDetailsService() {
- UserDetails user = User
- .builder()
- .username(username)
- .password("{noop}" + password) // FIXME: not very secure
- .authorities("ADMIN_API_ACCESS")
- .build();
- return new InMemoryUserDetailsManager(user);
- }
-
- @Bean
- CorsConfigurationSource corsConfigurationSource() {
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
- return source;
- }
-
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java
deleted file mode 100644
index a91fe01..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/WebClientConfig.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package de.grimsi.gameyfin.config;
-
-import io.github.resilience4j.bulkhead.Bulkhead;
-import io.github.resilience4j.bulkhead.BulkheadConfig;
-import io.github.resilience4j.ratelimiter.RateLimiter;
-import io.github.resilience4j.ratelimiter.RateLimiterConfig;
-import io.netty.handler.logging.LogLevel;
-import lombok.Getter;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.client.reactive.ReactorClientHttpConnector;
-import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
-import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
-import org.springframework.web.reactive.function.client.WebClient;
-import reactor.core.publisher.Mono;
-import reactor.netty.http.client.HttpClient;
-import reactor.netty.transport.logging.AdvancedByteBufFormat;
-
-import java.time.Duration;
-
-@Slf4j
-@Getter
-@Configuration
-public class WebClientConfig implements WebClientCustomizer {
-
- private final RateLimiter igdbRateLimiter;
- private final Bulkhead igdbConcurrencyLimiter;
-
-
- public WebClientConfig(@Value("${gameyfin.igdb.api.max-concurrent-requests}") int maxConcurrentRequestsToIgdb,
- @Value("${gameyfin.igdb.api.max-requests-per-second}") int maxRequestsPerSecondToIgdb) {
-
- log.info("IGDB API connection properties: max. {} req/s, max. {} concurrent requests", maxRequestsPerSecondToIgdb, maxConcurrentRequestsToIgdb);
-
- igdbRateLimiter = RateLimiter.of("igdb-rate-limiter",
- RateLimiterConfig.custom()
- .limitForPeriod(maxRequestsPerSecondToIgdb)
- .limitRefreshPeriod(Duration.ofSeconds(1))
- .timeoutDuration(Duration.ofMinutes(1))
- .build());
-
- igdbConcurrencyLimiter = Bulkhead.of("igdb-concurrency-limiter",
- BulkheadConfig.custom()
- .maxConcurrentCalls(maxConcurrentRequestsToIgdb)
- .maxWaitDuration(Duration.ofMinutes(1))
- .build());
- }
-
- @Override
- public void customize(WebClient.Builder webClientBuilder) {
- HttpClient httpClient = HttpClient.create()
- .wiretap(this.getClass().getCanonicalName(), LogLevel.TRACE, AdvancedByteBufFormat.TEXTUAL) // Enable full request / response logging in TRACE
- .proxyWithSystemProperties(); // Enable use of system proxy
-
- webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient));
- }
-
- /**
- * This fixes the wrong Content-Type in responses of the IGDB API by overwriting it so the WebClient is able to parse it automatically
- * They return "application/protobuf", correct would be "application/x-protobuf"
- *
- * @return the filter function
- */
- public static ExchangeFilterFunction fixProtobufContentTypeInterceptor() {
- return ExchangeFilterFunction.ofResponseProcessor(clientResponse ->
- Mono.just(clientResponse.mutate()
- .headers(headers -> headers.remove(HttpHeaders.CONTENT_TYPE))
- .header(HttpHeaders.CONTENT_TYPE, String.valueOf(ProtobufHttpMessageConverter.PROTOBUF))
- .build())
- );
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/properties/GameyfinProperties.java b/backend/src/main/java/de/grimsi/gameyfin/config/properties/GameyfinProperties.java
deleted file mode 100644
index ce83f00..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/config/properties/GameyfinProperties.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package de.grimsi.gameyfin.config.properties;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-import java.util.List;
-
-// see https://stackoverflow.com/questions/26699385/spring-boot-yaml-configuration-for-a-list-of-strings
-@ConfigurationProperties("gameyfin")
-// Technically SonarQube is correct, but I like to keep the lowercase method names since it correlates better with the config keys
-@SuppressWarnings("java:S3010")
-public record GameyfinProperties(
- folders folders,
- List fileExtensions,
- List fileSuffixes,
- igdb igdb) {
-
- public record folders(String data) {}
-
-
- public record igdb(config config) {
- public record config(List preferredPlatforms) {}
- }
-}
-
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/AutocompleteSuggestionDto.java b/backend/src/main/java/de/grimsi/gameyfin/dto/AutocompleteSuggestionDto.java
deleted file mode 100644
index 3f0f816..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/AutocompleteSuggestionDto.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.Instant;
-import java.util.List;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class AutocompleteSuggestionDto {
- private String slug;
- private String title;
- private Instant releaseDate;
- private List platforms;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/GameOverviewDto.java b/backend/src/main/java/de/grimsi/gameyfin/dto/GameOverviewDto.java
deleted file mode 100644
index f0fa069..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/GameOverviewDto.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.Builder;
-import lombok.Data;
-
-@Data
-@Builder
-public class GameOverviewDto {
- private String slug;
- private String title;
- private String coverId;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/ImageDownloadResultDto.java b/backend/src/main/java/de/grimsi/gameyfin/dto/ImageDownloadResultDto.java
deleted file mode 100644
index 7e0d103..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/ImageDownloadResultDto.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.Data;
-
-@Data
-public class ImageDownloadResultDto {
- private int coverDownloads;
- private int screenshotDownloads;
- private int companyLogoDownloads;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanRequestDto.java b/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanRequestDto.java
deleted file mode 100644
index f2a8cbb..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanRequestDto.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class LibraryScanRequestDto {
- private String path;
- private boolean downloadImages;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanResult.java b/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanResult.java
deleted file mode 100644
index dd0fbe8..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanResult.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.Builder;
-import lombok.Data;
-
-@Data
-@Builder
-public class LibraryScanResult {
- private int newGames;
- private int deletedGames;
- private int newUnmappableFiles;
- private int totalGames;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanResultDto.java b/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanResultDto.java
deleted file mode 100644
index 48d7307..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/LibraryScanResultDto.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class LibraryScanResultDto {
- private int newGames;
- private int deletedGames;
- private int newUnmappableFiles;
- private int totalGames;
- private int coverDownloads;
- private int screenshotDownloads;
- private int companyLogoDownloads;
- private int scanDuration;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/dto/PathToSlugDto.java b/backend/src/main/java/de/grimsi/gameyfin/dto/PathToSlugDto.java
deleted file mode 100644
index 3612724..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/dto/PathToSlugDto.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package de.grimsi.gameyfin.dto;
-
-import lombok.Data;
-
-@Data
-public class PathToSlugDto {
- private String path;
- private String slug;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/Company.java b/backend/src/main/java/de/grimsi/gameyfin/entities/Company.java
deleted file mode 100644
index 0369b39..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/Company.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class Company {
- @Id
- private String slug;
-
- @Column(nullable = false)
- private String name;
-
- private String logoId;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- Company company = (Company) o;
- return slug != null && Objects.equals(slug, company.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/DetectedGame.java b/backend/src/main/java/de/grimsi/gameyfin/entities/DetectedGame.java
deleted file mode 100644
index da790b7..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/DetectedGame.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-
-import lombok.*;
-import org.hibernate.Hibernate;
-import org.hibernate.annotations.CreationTimestamp;
-
-import jakarta.persistence.*;
-import java.time.Instant;
-import java.util.List;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class DetectedGame {
-
- // Game properties
- @Id
- private String slug;
-
- @Column(nullable = false)
- private String title;
-
- @Lob
- @Column(columnDefinition = "CLOB")
- private String summary;
-
- private Instant releaseDate;
-
- private Integer userRating;
-
- private Integer criticsRating;
-
- private Integer totalRating;
-
- private String category;
-
- private boolean offlineCoop;
-
- private boolean onlineCoop;
-
- private boolean lanSupport;
-
- private int maxPlayers;
-
- @Column(nullable = false)
- private String coverId;
-
- @ElementCollection
- private List screenshotIds;
-
- @ElementCollection
- private List videoIds;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List companies;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List genres;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List keywords;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List themes;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List playerPerspectives;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List platforms;
-
- @ManyToOne(cascade = CascadeType.MERGE)
- @JoinColumn(name = "library")
- @ToString.Exclude
- private Library library;
-
- // Technical properties
- @Column(nullable = false)
- private String path;
-
- @Column(nullable = false)
- private long diskSize;
-
- @Column(columnDefinition = "boolean default false")
- private boolean confirmedMatch;
-
- @CreationTimestamp
- private Instant addedToLibrary;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- DetectedGame that = (DetectedGame) o;
- return slug != null && Objects.equals(slug, that.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/Genre.java b/backend/src/main/java/de/grimsi/gameyfin/entities/Genre.java
deleted file mode 100644
index 021caa3..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/Genre.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class Genre {
- @Id
- private String slug;
-
- private String name;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- Genre genre = (Genre) o;
- return slug != null && Objects.equals(slug, genre.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/Keyword.java b/backend/src/main/java/de/grimsi/gameyfin/entities/Keyword.java
deleted file mode 100644
index 87c96fe..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/Keyword.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class Keyword {
- @Id
- private String slug;
-
- private String name;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- Keyword keyword = (Keyword) o;
- return slug != null && Objects.equals(slug, keyword.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/Library.java b/backend/src/main/java/de/grimsi/gameyfin/entities/Library.java
deleted file mode 100644
index 3db1fb5..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/Library.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.*;
-import java.util.List;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class Library {
-
- @Id
- private String path;
-
- @ManyToMany(cascade = CascadeType.MERGE)
- @ToString.Exclude
- private List platforms;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- Library library = (Library) o;
- return path != null && Objects.equals(path, library.path);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/Platform.java b/backend/src/main/java/de/grimsi/gameyfin/entities/Platform.java
deleted file mode 100644
index d4decf6..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/Platform.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class Platform {
- @Id
- private String slug;
-
- @Column(nullable = false)
- private String name;
-
- private String logoId;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- Platform platform = (Platform) o;
- return slug != null && Objects.equals(slug, platform.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/PlayerPerspective.java b/backend/src/main/java/de/grimsi/gameyfin/entities/PlayerPerspective.java
deleted file mode 100644
index 692c2fd..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/PlayerPerspective.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class PlayerPerspective {
- @Id
- private String slug;
-
- private String name;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- PlayerPerspective that = (PlayerPerspective) o;
- return slug != null && Objects.equals(slug, that.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/Theme.java b/backend/src/main/java/de/grimsi/gameyfin/entities/Theme.java
deleted file mode 100644
index 606933b..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/Theme.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import java.util.Objects;
-
-@Entity
-@Builder
-@Getter
-@Setter
-@ToString
-@AllArgsConstructor
-@RequiredArgsConstructor
-public class Theme {
- @Id
- private String slug;
-
- private String name;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- Theme theme = (Theme) o;
- return slug != null && Objects.equals(slug, theme.slug);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/entities/UnmappableFile.java b/backend/src/main/java/de/grimsi/gameyfin/entities/UnmappableFile.java
deleted file mode 100644
index 7157706..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/entities/UnmappableFile.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package de.grimsi.gameyfin.entities;
-
-import lombok.*;
-import org.hibernate.Hibernate;
-
-import jakarta.persistence.*;
-
-import java.util.Objects;
-
-@Entity
-@Getter
-@Setter
-@ToString
-@NoArgsConstructor
-public class UnmappableFile {
-
- public UnmappableFile(String path) {
- this.path = path;
- }
-
- @Id
- @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HIBERNATE_SEQUENCE")
- @SequenceGenerator(name = "HIBERNATE_SEQUENCE", allocationSize = 1)
- private Long id;
-
- private String path;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
- UnmappableFile that = (UnmappableFile) o;
- return path != null && Objects.equals(path, that.path);
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/exceptions/DownloadAbortedException.java b/backend/src/main/java/de/grimsi/gameyfin/exceptions/DownloadAbortedException.java
deleted file mode 100644
index 6e79758..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/exceptions/DownloadAbortedException.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.grimsi.gameyfin.exceptions;
-
-public class DownloadAbortedException extends RuntimeException {
- public DownloadAbortedException() {
- super();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbApiProperties.java b/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbApiProperties.java
deleted file mode 100644
index 35a6370..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbApiProperties.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.grimsi.gameyfin.igdb;
-
-import java.util.List;
-
-public class IgdbApiProperties {
- public static final String ENDPOINT_GAMES_PROTOBUF = "games.pb";
- public static final String ENDPOINT_PLATFORMS_PROTOBUF = "platforms.pb";
-
- private static final List GAME_QUERY_FIELDS = List.of(
- "slug", "name", "summary", "first_release_date", "rating", "aggregated_rating", "total_rating", "category",
- "multiplayer_modes.lancoop", "multiplayer_modes.onlinecoop", "multiplayer_modes.offlinecoop", "multiplayer_modes.onlinemax",
- "cover.image_id", "screenshots.image_id", "videos.video_id",
- "involved_companies.company.slug", "involved_companies.company.name", "involved_companies.company.logo.image_id",
- "genres.slug", "genres.name",
- "keywords.slug", "keywords.name",
- "themes.slug", "themes.name",
- "player_perspectives.slug", "player_perspectives.name",
- "platforms.slug", "platforms.name", "platforms.platform_logo.image_id"
- );
-
- public static final String GAME_QUERY_FIELDS_STRING = String.join(",", GAME_QUERY_FIELDS);
-
- public static final String IMAGES_BASE_URL = "https://images.igdb.com/igdb/image/upload/";
-
- public static final String COVER_IMAGE_SIZE = "cover_big";
- public static final String SCREENSHOT_IMAGE_SIZE = "screenshot_med";
- public static final String LOGO_IMAGE_SIZE = "logo_med";
-
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbApiQueryBuilder.java b/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbApiQueryBuilder.java
deleted file mode 100644
index aa9f2f2..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbApiQueryBuilder.java
+++ /dev/null
@@ -1,448 +0,0 @@
-package de.grimsi.gameyfin.igdb;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Builder for fluent building of igdb api queries.
- */
-public class IgdbApiQueryBuilder {
- private final StringBuilder stringBuilder;
- private String search;
- private String fields;
- private String limit;
- private String where;
- private String sort;
-
- public IgdbApiQueryBuilder() {
- this.stringBuilder = new StringBuilder();
- this.fields = "fields *;";
- this.search = "";
- this.limit = "";
- this.where = "";
- this.sort = "";
- }
-
- /**
- * Creates a {@link Condition} that are concatenated through the `&` operator.
- * This condition produces `(condition & condition & condition)`.
- *
- * @param conditions multiple conditions
- * @return an {@link AndCondition}
- */
- public static Condition and(Condition... conditions) {
- return new AndCondition(conditions);
- }
-
- /**
- * Creates a {@link Condition} that are concatenated through the `|` operator.
- * This condition produces `(condition | condition | condition)`.
- *
- * @param conditions multiple conditions
- * @return an {@link OrCondition}
- */
- public static Condition or(Condition... conditions) {
- return new OrCondition(conditions);
- }
-
- /**
- * Creates a {@link Condition} to look for string values in a list.
- * This condition produces `field = ("val1","val2")`.
- *
- * @param field a field to search through
- * @param values the string values
- * @return an {@link InCondition}
- */
- public static Condition in(String field, String... values) {
- return new InCondition(field, values);
- }
-
- /**
- * Creates a {@link Condition} to look for number values in a list.
- * This condition produces `field = (1,2,3)`.
- *
- * @param field a field to search through
- * @param values the number values
- * @return an {@link InCondition}
- */
- public static Condition in(String field, Number... values) {
- return new InCondition(field, values);
- }
-
- /**
- * Creates a {@link Condition} to look for values in a collection.
- * This condition produces `field = ("val1","val2")` if a collection of strings is passed.
- * This condition produces `field = (1,2,3)` if a collection of numbers is passed.
- *
- * @param field a field to search through.
- * @param values a collection of values.
- * @return an {@link InCondition}.
- */
- public static Condition in(String field, Collection> values) {
- return new InCondition(field, values.toArray(new Object[0]));
- }
-
- /**
- * Creates a {@link Condition} to filter for matching string values.
- * This condition produces `field = "value"`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition equal(String field, String value) {
- return new EqualsCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to filter for matching number values.
- * This condition produces `field = 123`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition equal(String field, Number value) {
- return new EqualsCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to filter for non-matching string values.
- * This condition produces `field != "value"`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition not(String field, String value) {
- return new NotCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to filter for non-matching number values.
- * This condition produces `field != 123`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition not(String field, Number value) {
- return new NotCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to check if a value is bigger than the given value.
- * This condition produces `field > 123`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition greater(String field, Number value) {
- return new GreaterThanCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to check if a value is bigger or equal to the given value.
- * This condition produces `field >= 123`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition greaterEquals(String field, Number value) {
- return new GreaterEqualsCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to check if a value is smaller than the given value.
- * This condition produces `field < 123`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition lesser(String field, Number value) {
- return new LessThanCondition(field, value);
- }
-
- /**
- * Creates a {@link Condition} to check if a value is smaller or equal to the given value.
- * This condition produces `field <= 123`.
- *
- * @param field a field to search through.
- * @param value a value for comparison.
- * @return an {@link InCondition}.
- */
- public static Condition lesserEquals(String field, Number value) {
- return new LesserEqualsCondition(field, value);
- }
-
- /**
- * Builds the query string.
- *
- * @return an igdb compatible query
- */
- public String build() {
- stringBuilder.append(search);
- stringBuilder.append(fields);
- stringBuilder.append(limit);
- stringBuilder.append(where);
- stringBuilder.append(sort);
- return stringBuilder.toString();
- }
-
- /**
- * Adds the `search "xyz";` query param.
- *
- * @param searchTerm a term to search for.
- * @return the builder
- */
- public IgdbApiQueryBuilder search(String searchTerm) {
- this.search = "search \"%s\";".formatted(searchTerm);
- return this;
- }
-
- /**
- * Adds the `fields abc,xyz;` query param.
- *
- * @param fields fields that should be returned (defaults to *).
- * @return the builder
- */
- public IgdbApiQueryBuilder fields(String fields) {
- this.fields = "fields %s;".formatted(fields);
- return this;
- }
-
- /**
- * Adds the `limit 1234;` query param.
- *
- * @param limit how many results should be returned.
- * @return the builder
- */
- public IgdbApiQueryBuilder limit(int limit) {
- this.limit = "limit %d;".formatted(limit);
- return this;
- }
-
- /**
- * Adds the `where xyz;` query param.
- *
- * @param condition a {@link Condition} object containing all conditions to filter the igdb db.
- * @return the builder
- */
- public IgdbApiQueryBuilder where(Condition condition) {
- this.where = "where %s;".formatted(condition.build());
- return this;
- }
-
- /**
- * Adds the `sort xyz asc;` query param.
- *
- * @param field a term to search for.
- * @param order the {@link SortOrder} (either ASC or DESC).
- * @return the builder
- */
- public IgdbApiQueryBuilder sort(String field, SortOrder order) {
- this.sort = "sort %s %s;".formatted(field, order.value);
- return this;
- }
-
- /**
- * Sort order enum for sorting query result.
- */
- public enum SortOrder {
- ASC("asc"), DESC("desc");
-
- public final String value;
-
- SortOrder(String value) {
- this.value = value;
- }
- }
-
- /**
- * Abstract condition object.
- */
- public abstract static class Condition {
- protected static String wrap(String conditions) {
- return "(%s)".formatted(conditions);
- }
-
- public abstract String build();
- }
-
- /**
- * InCondition
- */
- public static class InCondition extends Condition {
- private static final String PATTERN = "%s = (%s)";
- private final String field;
- private final String in;
-
- public InCondition(String field, Object[] values) {
- this.field = field;
- if (Arrays.stream(values).anyMatch(String.class::isInstance))
- this.in = Arrays.stream(values).map("\"%s\""::formatted).collect(Collectors.joining(","));
- else if (Arrays.stream(values).anyMatch(Number.class::isInstance))
- this.in = Arrays.stream(values).map("%d"::formatted).collect(Collectors.joining(","));
- else this.in = null;
- }
-
- @Override
- public String build() {
- return PATTERN.formatted(field, in);
- }
- }
-
- /**
- * NotCondition
- */
- public static class NotCondition extends OperatorCondition {
- private static final String OPERATOR = "!=";
-
- public NotCondition(String field, String value) {
- super(field, OPERATOR, value);
- }
-
- public NotCondition(String field, Number value) {
- super(field, OPERATOR, value);
- }
- }
-
- /**
- * EqualsCondition
- */
- public static class EqualsCondition extends OperatorCondition {
-
- private static final String OPERATOR = "=";
-
- public EqualsCondition(String field, String value) {
- super(field, OPERATOR, value);
- }
-
- public EqualsCondition(String field, Number value) {
- super(field, OPERATOR, value);
- }
- }
-
- /**
- * GreaterThanCondition
- */
- public static class GreaterThanCondition extends OperatorCondition {
-
- private static final String OPERATOR = ">";
-
- public GreaterThanCondition(String field, Number value) {
- super(field, OPERATOR, value);
- }
- }
-
- /**
- * GreaterEqualsCondition
- */
- public static class GreaterEqualsCondition extends OperatorCondition {
-
- private static final String OPERATOR = ">=";
-
- public GreaterEqualsCondition(String field, Number value) {
- super(field, OPERATOR, value);
- }
- }
-
- /**
- * LessThanCondition
- */
- public static class LessThanCondition extends OperatorCondition {
-
- private static final String OPERATOR = "<";
-
- public LessThanCondition(String field, Number value) {
- super(field, OPERATOR, value);
- }
- }
-
- /**
- * LesserEqualsCondition
- */
- public static class LesserEqualsCondition extends OperatorCondition {
-
- private static final String OPERATOR = "<=";
-
- public LesserEqualsCondition(String field, Number value) {
- super(field, OPERATOR, value);
- }
- }
-
- /**
- * OperatorCondition for inheritance
- */
- public static class OperatorCondition extends Condition {
- private static final String PATTERN = "%s %s %s";
- private static final String ESCAPED_STRING = "\"%s\"";
- private static final String DIGITS = "%s";
-
- private final String field;
- private final String value;
- private final String operator;
-
- public OperatorCondition(String field, String operator, String value) {
- this.field = field;
- this.operator = operator;
- this.value = value != null ? ESCAPED_STRING.formatted(value) : null;
- }
-
- public OperatorCondition(String field, String operator, Number value) {
- this.field = field;
- this.operator = operator;
- this.value = value != null ? DIGITS.formatted(value) : null;
- }
-
- public OperatorCondition() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String build() {
- return PATTERN.formatted(field, operator, value);
- }
- }
-
- /**
- * AndCondition
- */
- public static class AndCondition extends Condition {
- private static final String AND = " & ";
-
- private final Condition[] conditions;
-
- public AndCondition(Condition[] conditions) {
- this.conditions = conditions;
- }
-
- @Override
- public String build() {
- return wrap(Arrays.stream(conditions).map(Condition::build).collect(Collectors.joining(AND)));
- }
- }
-
- /**
- * OrCondition
- */
- public static class OrCondition extends Condition {
- private static final String OR = " | ";
-
- private final Condition[] conditions;
-
- public OrCondition(Condition[] conditions) {
- this.conditions = conditions;
- }
-
- @Override
- public String build() {
- return wrap(Arrays.stream(conditions).map(Condition::build).collect(Collectors.joining(OR)));
- }
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java b/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java
deleted file mode 100644
index ef3990c..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/igdb/IgdbWrapper.java
+++ /dev/null
@@ -1,259 +0,0 @@
-package de.grimsi.gameyfin.igdb;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.config.WebClientConfig;
-import de.grimsi.gameyfin.config.properties.GameyfinProperties;
-import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
-import de.grimsi.gameyfin.entities.Platform;
-import de.grimsi.gameyfin.igdb.IgdbApiQueryBuilder.*;
-import de.grimsi.gameyfin.igdb.dto.TwitchOAuthTokenDto;
-import de.grimsi.gameyfin.mapper.GameMapper;
-import de.grimsi.gameyfin.mapper.PlatformMapper;
-import io.github.resilience4j.reactor.bulkhead.operator.BulkheadOperator;
-import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.MediaType;
-import org.springframework.stereotype.Service;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.util.UriComponentsBuilder;
-
-import jakarta.annotation.PostConstruct;
-import java.net.URI;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static de.grimsi.gameyfin.igdb.IgdbApiProperties.GAME_QUERY_FIELDS_STRING;
-import static de.grimsi.gameyfin.igdb.IgdbApiQueryBuilder.*;
-import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
-
-@Slf4j
-@RequiredArgsConstructor
-@Service
-public class IgdbWrapper {
- private final WebClient.Builder webclientBuilder;
- private final WebClientConfig webClientConfig;
- private final GameMapper gameMapper;
- private final GameyfinProperties gameyfinProperties;
-
- @Value("${gameyfin.igdb.api.client-id}")
- private String clientId;
- @Value("${gameyfin.igdb.api.client-secret}")
- private String clientSecret;
- @Value("${gameyfin.igdb.api.endpoints.base}")
- private String igdbApiBaseUrl;
- @Value("${gameyfin.igdb.api.endpoints.auth}")
- private String twitchAuthUrl;
- private WebClient twitchApiClient;
-
- private WebClient igdbApiClient;
-
- private TwitchOAuthTokenDto accessToken;
-
- @PostConstruct
- public void init() {
- twitchApiClient = webclientBuilder.build();
- authenticate();
- initIgdbClient();
- }
-
- private void authenticate() {
- log.info("Authenticating on Twitch API...");
-
- URI url = UriComponentsBuilder
- .fromHttpUrl(twitchAuthUrl)
- .query("client_id={client_id}").query("client_secret={client_secret}").query("grant_type=client_credentials")
- .buildAndExpand(clientId, clientSecret)
- .toUri();
-
- this.accessToken = twitchApiClient
- .post()
- .uri(url)
- .accept(MediaType.APPLICATION_JSON)
- .retrieve()
- .bodyToMono(TwitchOAuthTokenDto.class)
- .block();
-
- log.info("Successfully authenticated.");
- }
-
- public Optional getGameById(Long id) {
- IgdbApiQueryBuilder queryBuilder = new IgdbApiQueryBuilder();
- Igdb.GameResult gameResult = queryIgdbApi(
- IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF,
- queryBuilder.fields(GAME_QUERY_FIELDS_STRING)
- .where(equal("id", id))
- .limit(1)
- .build(),
- Igdb.GameResult.class
- );
-
- if (gameResult == null) return Optional.empty();
-
- return Optional.of(gameResult.getGames(0));
- }
-
- public Optional getGameBySlug(String slug) {
- IgdbApiQueryBuilder queryBuilder = new IgdbApiQueryBuilder();
- Igdb.GameResult gameResult = queryIgdbApi(
- IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF,
- queryBuilder.fields(GAME_QUERY_FIELDS_STRING)
- .where(equal("slug", slug))
- .limit(1)
- .build(),
- Igdb.GameResult.class
- );
-
- if (gameResult == null) return Optional.empty();
-
- return Optional.of(gameResult.getGames(0));
- }
-
- public List findPossibleMatchingTitles(String searchTerm, int limit) {
- IgdbApiQueryBuilder queryBuilder = new IgdbApiQueryBuilder();
- Igdb.GameResult gameResult = queryIgdbApi(
- IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF,
- queryBuilder.search(searchTerm)
- .fields("slug,name,first_release_date,platforms.name")
- .where(in("platforms", gameyfinProperties.igdb().config().preferredPlatforms()))
- .limit(limit)
- .build(),
- Igdb.GameResult.class
- );
-
- if (gameResult == null) return Collections.emptyList();
-
- return gameResult.getGamesList().stream().map(gameMapper::toAutocompleteSuggestionDto).toList();
- }
-
- public Optional searchForGameByTitle(String searchTerm) {
- return searchForGameByTitle(searchTerm, List.of());
- }
-
- public Optional searchForGameByTitle(String searchTerm, Collection platformSlugs) {
- IgdbApiQueryBuilder queryBuilder = new IgdbApiQueryBuilder();
- Condition platforms = isNotEmpty(platformSlugs) ?
- and(in("platforms", gameyfinProperties.igdb().config().preferredPlatforms()), in("platforms.slug", platformSlugs)) :
- in("platforms", gameyfinProperties.igdb().config().preferredPlatforms());
-
- Igdb.GameResult gameResult = queryIgdbApi(
- IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF,
- queryBuilder.search(searchTerm)
- .fields(GAME_QUERY_FIELDS_STRING)
- .where(platforms)
- .build(),
- Igdb.GameResult.class
- );
-
- if (gameResult == null) {
- log.warn("Could not find game for title '{}'", searchTerm);
-
- // Try to remove brackets (and their content) at the end of the search term and search again
- // Although this process is recursive, we will only end up with a maximum recursion depth of two
- Pattern brackets = Pattern.compile("[()<>{}\\[\\]]");
- Matcher hasBrackets = brackets.matcher(searchTerm);
-
- if (hasBrackets.find()) {
- String searchTermWithoutBrackets = searchTerm.split(brackets.pattern())[0].trim();
- log.warn("Removed brackets, trying again with search term '{}'", searchTermWithoutBrackets);
- return searchForGameByTitle(searchTermWithoutBrackets, platformSlugs);
- } else if (searchTerm.contains("-") || searchTerm.contains(".") || searchTerm.contains("_") || searchTerm.contains("INTERNAL") || searchTerm.contains("internal") || searchTerm.contains("REPACK") || searchTerm.contains("PROPER") || searchTerm.contains("repack") || searchTerm.contains("proper")) {
- String searchTermWithoutDash = searchTerm;
- if (searchTermWithoutDash.contains("-")) {
- searchTermWithoutDash = searchTermWithoutDash.substring(0, searchTermWithoutDash.lastIndexOf('-')).trim();
- }
- searchTermWithoutDash = searchTermWithoutDash.replace(".", " ");
- searchTermWithoutDash = searchTermWithoutDash.replace("_", " ");
- searchTermWithoutDash = searchTermWithoutDash.replaceAll("(?i)repack", " ");
- searchTermWithoutDash = searchTermWithoutDash.replaceAll("(?i)internal", " ");
- searchTermWithoutDash = searchTermWithoutDash.replaceAll("(?i)proper", " ");
- log.warn("Removed release stuff, trying again with search term '{}'", searchTermWithoutDash);
- return searchForGameByTitle(searchTermWithoutDash, platformSlugs);
- }
- log.warn("Using slug as last resort for " + searchTerm + ": " + searchTerm.replace(" ", "-").toLowerCase());
- return getGameBySlug(searchTerm.replace(" ", "-").toLowerCase());
- }
-
- List games = gameResult.getGamesList();
-
- // If we only get one game, we don't have to check for exact matches, so return it directly
- if (games.size() == 1) return Optional.ofNullable(games.get(0));
-
- // First check if there are any matches with the exact search term
- // If no exact match has been found, check if there are matches where the name ends with the search term
- // This will filter out most DLCs and similiar stuff, but will detect a game even when your search term is not exactly the title
- // If that also returns nothing, just return the first search result
- //
- // Example: Searching for "Rainbow Six Siege" will result in returning "Tom Clancy's Rainbow Six Siege" (the game we want)
- // If we just used the first result from IGDB we would get something like "Tom Clancy's Rainbow Six Siege Demon Veil" as a result
-
- Optional srExactTitleMatch = games.stream().filter(s -> s.getName().equals(searchTerm)).findFirst();
- if (srExactTitleMatch.isPresent()) return srExactTitleMatch;
-
- Optional srTitleEndsWithMatch = games.stream().filter(s -> s.getName().endsWith(searchTerm)).findFirst();
- if (srTitleEndsWithMatch.isPresent()) return srTitleEndsWithMatch;
-
- // Just return the first result and hope that IGDBs search algorithm is somewhat helpful this time
- return Optional.of(games.get(0));
- }
-
- public List findPlatforms(String searchTerm, int limit) {
- IgdbApiQueryBuilder queryBuilder = new IgdbApiQueryBuilder();
- Igdb.PlatformResult platformResult = queryIgdbApi(
- IgdbApiProperties.ENDPOINT_PLATFORMS_PROTOBUF,
- queryBuilder.search(searchTerm)
- .fields("slug,name")
- .limit(limit)
- .build(),
- Igdb.PlatformResult.class
- );
-
- if (platformResult == null) return Collections.emptyList();
-
- return platformResult.getPlatformsList().stream().map(PlatformMapper::toPlatform).toList();
- }
-
- public Optional getPlatformBySlug(String slug) {
- IgdbApiQueryBuilder queryBuilder = new IgdbApiQueryBuilder();
- Igdb.PlatformResult platformResult = queryIgdbApi(
- IgdbApiProperties.ENDPOINT_PLATFORMS_PROTOBUF,
- queryBuilder.fields("slug,name,platform_logo")
- .where(equal("slug", slug))
- .build(),
- Igdb.PlatformResult.class
- );
-
- if (platformResult == null) return Optional.empty();
-
- return platformResult.getPlatformsList().stream().map(PlatformMapper::toPlatform).findFirst();
- }
-
- private void initIgdbClient() {
- if (accessToken == null) {
- authenticate();
- }
-
- igdbApiClient = webclientBuilder
- .baseUrl(igdbApiBaseUrl)
- .defaultHeader("Client-ID", clientId)
- .defaultHeader("Authorization", "Bearer %s".formatted(accessToken.getAccessToken()))
- .filter(WebClientConfig.fixProtobufContentTypeInterceptor())
- .build();
- }
-
- private T queryIgdbApi(String endpoint, String query, Class responseClass) {
- return igdbApiClient.post()
- .uri(endpoint)
- .bodyValue(query)
- .retrieve()
- .bodyToMono(responseClass)
- .transformDeferred(BulkheadOperator.of(webClientConfig.getIgdbConcurrencyLimiter()))
- .transformDeferred(RateLimiterOperator.of(webClientConfig.getIgdbRateLimiter()))
- .block();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/igdb/dto/TwitchOAuthTokenDto.java b/backend/src/main/java/de/grimsi/gameyfin/igdb/dto/TwitchOAuthTokenDto.java
deleted file mode 100644
index 64fb1fa..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/igdb/dto/TwitchOAuthTokenDto.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package de.grimsi.gameyfin.igdb.dto;
-
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@NoArgsConstructor
-@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
-public class TwitchOAuthTokenDto {
- private String accessToken;
- private Long expiresIn;
- private String tokenType;
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/CompanyMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/CompanyMapper.java
deleted file mode 100644
index 8f0eb00..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/CompanyMapper.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Company;
-
-import java.util.List;
-
-public class CompanyMapper {
-
- public static Company toCompany(Igdb.InvolvedCompany c) {
- return Company.builder()
- .slug(c.getCompany().getSlug())
- .name(c.getCompany().getName())
- .logoId(c.getCompany().getLogo().getImageId())
- .build();
- }
-
- public static List toCompanies(List c) {
- return c.stream().map(CompanyMapper::toCompany).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java
deleted file mode 100644
index 389a688..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
-import de.grimsi.gameyfin.dto.GameOverviewDto;
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.entities.Library;
-import de.grimsi.gameyfin.service.FilesystemService;
-import de.grimsi.gameyfin.service.LibraryService;
-import de.grimsi.gameyfin.util.ProtobufUtil;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StopWatch;
-import org.springframework.util.StringUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.time.Instant;
-import java.util.List;
-
-import static java.util.stream.Collectors.toList;
-
-@Slf4j
-@RequiredArgsConstructor
-@Component
-public class GameMapper {
-
- private final FilesystemService filesystemService;
-
- public DetectedGame toDetectedGame(Igdb.Game g, Path path, Library library) {
- List multiplayerModes = g.getMultiplayerModesList();
- List screenshotIds = g.getScreenshotsList().stream().map(Igdb.Screenshot::getImageId).toList();
- List videoIds = g.getVideosList().stream().map(Igdb.GameVideo::getVideoId).toList();
-
- return DetectedGame.builder()
- .slug(g.getSlug())
- .title(g.getName())
- .summary(g.getSummary())
- .releaseDate(ProtobufUtil.toInstant(g.getFirstReleaseDate()))
- .userRating((int) g.getRating())
- .criticsRating((int) g.getAggregatedRating())
- .totalRating((int) g.getTotalRating())
- .category(g.getCategory().name())
- .offlineCoop(hasOfflineCoop(multiplayerModes))
- .onlineCoop(hasOnlineCoop(multiplayerModes))
- .lanSupport(hasLanSupport(multiplayerModes))
- .maxPlayers(getMaxPlayers(multiplayerModes))
- .coverId(getCoverId(g))
- .screenshotIds(screenshotIds)
- .videoIds(videoIds)
- .companies(CompanyMapper.toCompanies(g.getInvolvedCompaniesList()))
- .genres(GenreMapper.toGenres(g.getGenresList()))
- .keywords(KeywordMapper.toKeywords(g.getKeywordsList()))
- .themes(ThemeMapper.toThemes(g.getThemesList()))
- .playerPerspectives(PlayerPerspectiveMapper.toPlayerPerspectives(g.getPlayerPerspectivesList()))
- .platforms(PlatformMapper.toPlatforms(g.getPlatformsList()))
- .path(path.toString())
- .library(library)
- .diskSize(calculateDiskSize(g, path))
- .addedToLibrary(Instant.now())
- .build();
- }
-
- public GameOverviewDto toGameOverviewDto(DetectedGame game) {
- return GameOverviewDto.builder()
- .slug(game.getSlug())
- .title(game.getTitle())
- .coverId(game.getCoverId())
- .build();
- }
-
- public AutocompleteSuggestionDto toAutocompleteSuggestionDto(Igdb.Game game) {
- return AutocompleteSuggestionDto.builder()
- .slug(game.getSlug())
- .title(game.getName())
- .releaseDate(ProtobufUtil.toInstant(game.getFirstReleaseDate()))
- .platforms(game.getPlatformsList().stream().map(Igdb.Platform::getName).toList())
- .build();
- }
-
- private String getCoverId(Igdb.Game g) {
- String coverId = g.getCover().getImageId();
-
- if (StringUtils.hasText(coverId)) return coverId;
-
- return "nocover";
- }
-
- private boolean hasOfflineCoop(List modes) {
- return modes.stream().anyMatch(Igdb.MultiplayerMode::getOfflinecoop);
- }
-
- private boolean hasLanSupport(List modes) {
- return modes.stream().anyMatch(Igdb.MultiplayerMode::getLancoop);
- }
-
- private boolean hasOnlineCoop(List modes) {
- return modes.stream().anyMatch(Igdb.MultiplayerMode::getOnlinecoop);
- }
-
- private int getMaxPlayers(List modes) {
- return modes.stream().mapToInt(Igdb.MultiplayerMode::getOnlinecoopmax).max().orElse(0);
- }
-
- private long calculateDiskSize(Igdb.Game g, Path path) {
- StopWatch stopWatch = new StopWatch();
- log.info("Calculating disk size for game '{}'...", g.getName());
-
- stopWatch.start();
-
- long fileSize;
-
- try {
- fileSize = filesystemService.getSizeOnDisk(path);
- } catch (IOException e) {
- log.error("Error while calculating disk size for game '{}'", g.getName());
- fileSize = -1L;
- }
-
- stopWatch.stop();
-
- log.info("Calculated disk size for game '{}' in {} seconds", g.getName(), (int) stopWatch.getTotalTimeSeconds());
- return fileSize;
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/GenreMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/GenreMapper.java
deleted file mode 100644
index eb9208f..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/GenreMapper.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Genre;
-
-import java.util.List;
-
-public class GenreMapper {
-
- public static Genre toGenre(Igdb.Genre g) {
- return Genre.builder()
- .slug(g.getSlug())
- .name(g.getName())
- .build();
- }
-
- public static List toGenres(List g) {
- return g.stream().map(GenreMapper::toGenre).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/KeywordMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/KeywordMapper.java
deleted file mode 100644
index fbaca16..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/KeywordMapper.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Keyword;
-
-import java.util.List;
-
-public class KeywordMapper {
- public static Keyword toKeyword(Igdb.Keyword g) {
- return Keyword.builder()
- .slug(g.getSlug())
- .name(g.getName())
- .build();
- }
-
- public static List toKeywords(List g) {
- return g.stream().map(KeywordMapper::toKeyword).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/PlatformMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/PlatformMapper.java
deleted file mode 100644
index 8cfca2d..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/PlatformMapper.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Platform;
-
-import java.util.List;
-
-public class PlatformMapper {
-
- public static Platform toPlatform(Igdb.Platform c) {
- return Platform.builder()
- .slug(c.getSlug())
- .name(c.getName())
- .logoId(c.getPlatformLogo().getImageId())
- .build();
- }
-
- public static List toPlatforms(List c) {
- return c.stream().map(PlatformMapper::toPlatform).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/PlayerPerspectiveMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/PlayerPerspectiveMapper.java
deleted file mode 100644
index 67a5529..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/PlayerPerspectiveMapper.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.PlayerPerspective;
-
-import java.util.List;
-
-public class PlayerPerspectiveMapper {
-
- public static PlayerPerspective toPlayerPerspective(Igdb.PlayerPerspective g) {
- return PlayerPerspective.builder()
- .slug(g.getSlug())
- .name(g.getName())
- .build();
-}
-
- public static List toPlayerPerspectives(List g) {
- return g.stream().map(PlayerPerspectiveMapper::toPlayerPerspective).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/ThemeMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/ThemeMapper.java
deleted file mode 100644
index 5102b5c..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/mapper/ThemeMapper.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Theme;
-
-import java.util.List;
-
-public class ThemeMapper {
- public static Theme toTheme(Igdb.Theme g) {
- return Theme.builder()
- .slug(g.getSlug())
- .name(g.getName())
- .build();
- }
-
- public static List toThemes(List g) {
- return g.stream().map(ThemeMapper::toTheme).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/CompanyRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/CompanyRepository.java
deleted file mode 100644
index 9e2f7cc..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/CompanyRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.Company;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface CompanyRepository extends JpaRepository {
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/DetectedGameRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/DetectedGameRepository.java
deleted file mode 100644
index 40ba374..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/DetectedGameRepository.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.DetectedGame;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-
-import static org.apache.commons.lang3.StringUtils.isBlank;
-
-public interface DetectedGameRepository extends JpaRepository {
-
- boolean existsByPath(String path);
-
- boolean existsBySlug(String slug);
-
- Optional findByPath(String path);
-
- List findByPathStartsWithAndLibraryIsNull(String path);
-
- List getAllByPathNotIn(Collection paths);
-
- List getAllByPathNotInAndPathStartsWith(Collection paths, String libraryPath);
-
- default List getAllByPathNotInAndPathStartsWith(List paths, String libraryPath) {
- List pathStrings = paths.stream().map(Path::toString).toList();
- // get games that are not in the paths list but are starting with libraryPath if libraryPath is not empty
- return isBlank(libraryPath) ? getAllByPathNotIn(pathStrings) : getAllByPathNotInAndPathStartsWith(pathStrings, libraryPath);
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/GenreRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/GenreRepository.java
deleted file mode 100644
index 08b49aa..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/GenreRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.Genre;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface GenreRepository extends JpaRepository {
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/KeywordRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/KeywordRepository.java
deleted file mode 100644
index 7d8ecaf..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/KeywordRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.Keyword;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface KeywordRepository extends JpaRepository {
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/LibraryRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/LibraryRepository.java
deleted file mode 100644
index 1837fbb..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/LibraryRepository.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.Library;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.Optional;
-
-public interface LibraryRepository extends JpaRepository {
-
- boolean existsByPathIgnoreCase(String path);
-
- Optional findByPath(String path);
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/PlatformRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/PlatformRepository.java
deleted file mode 100644
index 869a53d..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/PlatformRepository.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.Platform;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.Optional;
-
-public interface PlatformRepository extends JpaRepository {
-
- boolean existsBySlug(String slug);
-
- Optional findBySlug(String slug);
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/PlayerPerspectiveRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/PlayerPerspectiveRepository.java
deleted file mode 100644
index 8762c53..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/PlayerPerspectiveRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.PlayerPerspective;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface PlayerPerspectiveRepository extends JpaRepository {
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/ThemeRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/ThemeRepository.java
deleted file mode 100644
index d29c7ff..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/ThemeRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.Keyword;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface ThemeRepository extends JpaRepository {
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/repositories/UnmappableFileRepository.java b/backend/src/main/java/de/grimsi/gameyfin/repositories/UnmappableFileRepository.java
deleted file mode 100644
index c1f6a6b..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/repositories/UnmappableFileRepository.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.grimsi.gameyfin.repositories;
-
-import de.grimsi.gameyfin.entities.UnmappableFile;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Predicate;
-
-import static org.apache.commons.lang3.StringUtils.isBlank;
-
-public interface UnmappableFileRepository extends JpaRepository {
-
- boolean existsByPath(String path);
-
- List getAllByPathNotIn(Collection paths);
-
- List getAllByPathNotInAndPathStartsWith(Collection paths, String libraryPath);
-
-
- Optional findByPath(String path);
-
- default List getAllByPathNotInAndPathStartsWith(List paths, String libraryPath) {
- List pathStrings = paths.stream().map(Path::toString).toList();
- // get unmapped files that are not in the paths list but are starting with libraryPath if libraryPath is not empty
- return isBlank(libraryPath) ? getAllByPathNotIn(pathStrings) : getAllByPathNotInAndPathStartsWith(pathStrings, libraryPath);
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/rest/GamesController.java b/backend/src/main/java/de/grimsi/gameyfin/rest/GamesController.java
deleted file mode 100644
index 36afcbb..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/rest/GamesController.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package de.grimsi.gameyfin.rest;
-
-import de.grimsi.gameyfin.dto.GameOverviewDto;
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.service.DownloadService;
-import de.grimsi.gameyfin.service.GameService;
-import lombok.RequiredArgsConstructor;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * This controller handles logic related to detected games.
- */
-@RestController
-@RequestMapping("/v1/games")
-@RequiredArgsConstructor
-public class GamesController {
-
- private final GameService gameService;
- private final DownloadService downloadService;
-
- @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
- public List getAllGames() {
- return gameService.getAllDetectedGames();
- }
-
- @GetMapping(value = "/game/{slug}", produces = MediaType.APPLICATION_JSON_VALUE)
- public DetectedGame getGame(@PathVariable String slug) {
- return gameService.getDetectedGame(slug);
- }
-
- @GetMapping(value = "/game-overviews", produces = MediaType.APPLICATION_JSON_VALUE)
- public List getGameOverviews() {
- return gameService.getGameOverviews();
- }
-
- @GetMapping(value = "/game-mappings", produces = MediaType.APPLICATION_JSON_VALUE)
- public Map getGameMappings() {
- return gameService.getAllMappings();
- }
-
- @GetMapping(value = "/game/{slug}/download")
- public ResponseEntity downloadGameFiles(@PathVariable String slug) {
-
- DetectedGame game = gameService.getDetectedGame(slug);
-
- String downloadFileName = downloadService.getDownloadFileName(game);
- long downloadFileSize = downloadService.getDownloadFileSize(game);
-
- HttpHeaders headers = new HttpHeaders();
- headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"%s\"".formatted(downloadFileName));
- headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
- headers.add(HttpHeaders.PRAGMA, "no-cache");
- headers.add(HttpHeaders.EXPIRES, "0");
- headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
- if (downloadFileSize > 0) {
- headers.setContentLength(downloadFileSize);
- }
-
- return ResponseEntity
- .ok()
- .headers(headers)
- .body(out -> downloadService.sendGamefilesToClient(game, out));
- }
-
- @GetMapping(value = "/game/{slug}/refresh", produces = MediaType.APPLICATION_JSON_VALUE)
- public DetectedGame refreshGame(@PathVariable String slug) {
- return gameService.refreshGame(slug);
- }
-
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/rest/ImageController.java b/backend/src/main/java/de/grimsi/gameyfin/rest/ImageController.java
deleted file mode 100644
index b979389..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/rest/ImageController.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package de.grimsi.gameyfin.rest;
-
-import de.grimsi.gameyfin.service.DownloadService;
-import lombok.RequiredArgsConstructor;
-import org.springframework.core.io.Resource;
-import org.springframework.http.CacheControl;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * This controller handles functionality for images.
- */
-@RestController
-@RequestMapping("/v1/images")
-@RequiredArgsConstructor
-public class ImageController {
-
- private final DownloadService downloadService;
-
- @GetMapping(value = "/{imageId}", produces = MediaType.IMAGE_PNG_VALUE)
- public ResponseEntity getImage(@PathVariable String imageId) {
- return ResponseEntity.ok()
- .cacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic())
- .body(downloadService.sendImageToClient(imageId));
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/rest/LibraryController.java b/backend/src/main/java/de/grimsi/gameyfin/rest/LibraryController.java
deleted file mode 100644
index e5bcd79..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/rest/LibraryController.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package de.grimsi.gameyfin.rest;
-
-import de.grimsi.gameyfin.dto.*;
-import de.grimsi.gameyfin.entities.Library;
-import de.grimsi.gameyfin.service.ImageService;
-import de.grimsi.gameyfin.service.LibraryService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.MediaType;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.util.StopWatch;
-import org.springframework.web.bind.annotation.*;
-
-import java.nio.file.Path;
-import java.util.List;
-
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
-
-/**
- * This controller handles functionality of the library.
- */
-@RestController
-@RequestMapping("/v1/library")
-@PreAuthorize("hasAuthority('ADMIN_API_ACCESS')")
-@RequiredArgsConstructor
-@Slf4j
-public class LibraryController {
-
- private final LibraryService libraryService;
- private final ImageService imageService;
-
- @PostMapping(value = "/scan", produces = MediaType.APPLICATION_JSON_VALUE)
- public LibraryScanResultDto scanLibraries(@RequestBody LibraryScanRequestDto libraryScanRequest) {
- StopWatch stopWatch = new StopWatch();
- stopWatch.start();
-
- LibraryScanResultDto lscDto = new LibraryScanResultDto();
-
- String path = libraryScanRequest.getPath();
- List libraries = isNotBlank(path) ? List.of(libraryService.getLibrary(path)) : libraryService.getLibraries();
- List libraryScanResults = libraries.stream().map(libraryService::scanGameLibrary).toList();
-
- lscDto.setNewGames(libraryScanResults.stream().map(LibraryScanResult::getNewGames).reduce(0, Integer::sum));
- lscDto.setDeletedGames(libraryScanResults.stream().map(LibraryScanResult::getDeletedGames).reduce(0, Integer::sum));
- lscDto.setNewUnmappableFiles(libraryScanResults.stream().map(LibraryScanResult::getNewUnmappableFiles).reduce(0, Integer::sum));
- lscDto.setTotalGames(libraryScanResults.stream().map(LibraryScanResult::getTotalGames).reduce(0, Integer::sum));
-
- if (libraryScanRequest.isDownloadImages()) {
- ImageDownloadResultDto idrDto = downloadImages();
-
- lscDto.setCoverDownloads(idrDto.getCoverDownloads());
- lscDto.setScreenshotDownloads(idrDto.getScreenshotDownloads());
- lscDto.setCompanyLogoDownloads(idrDto.getCompanyLogoDownloads());
- }
-
- stopWatch.stop();
- lscDto.setScanDuration((int) stopWatch.getTotalTimeSeconds());
-
- log.info("Library scan completed in {} seconds.", (int) stopWatch.getTotalTimeSeconds());
-
- return lscDto;
- }
-
- @GetMapping(value = "/download-images")
- public ImageDownloadResultDto downloadImages() {
- ImageDownloadResultDto idrDto = new ImageDownloadResultDto();
-
- idrDto.setCoverDownloads(imageService.downloadGameCoversFromIgdb());
- idrDto.setScreenshotDownloads(imageService.downloadGameScreenshotsFromIgdb());
- idrDto.setCompanyLogoDownloads(imageService.downloadCompanyLogosFromIgdb());
-
- log.info("Downloading images completed.");
-
- return idrDto;
- }
-
- @GetMapping(value = "/files", produces = MediaType.APPLICATION_JSON_VALUE)
- public List getAllFiles() {
- return libraryService.getGameFiles().stream().map(Path::toString).toList();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/rest/LibraryManagementController.java b/backend/src/main/java/de/grimsi/gameyfin/rest/LibraryManagementController.java
deleted file mode 100644
index 42bbab6..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/rest/LibraryManagementController.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package de.grimsi.gameyfin.rest;
-
-
-import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
-import de.grimsi.gameyfin.dto.PathToSlugDto;
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.entities.Library;
-import de.grimsi.gameyfin.entities.Platform;
-import de.grimsi.gameyfin.entities.UnmappableFile;
-import de.grimsi.gameyfin.service.GameService;
-import de.grimsi.gameyfin.service.ImageService;
-import de.grimsi.gameyfin.service.LibraryService;
-import lombok.RequiredArgsConstructor;
-import org.springframework.http.MediaType;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/v1/library-management")
-@PreAuthorize("hasAuthority('ADMIN_API_ACCESS')")
-@RequiredArgsConstructor
-public class LibraryManagementController {
-
- private final GameService gameService;
- private final ImageService imageService;
- private final LibraryService libraryService;
-
- @DeleteMapping(value = "/delete-game/{slug}", produces = MediaType.APPLICATION_JSON_VALUE)
- public void deleteGame(@PathVariable String slug) {
- gameService.deleteGame(slug);
- }
-
- @DeleteMapping(value = "/delete-unmapped-file/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
- public void deleteUnmappedFile(@PathVariable Long id) {
- gameService.deleteUnmappedFile(id);
- }
-
- @GetMapping(value = "/confirm-game/{slug}", produces = MediaType.APPLICATION_JSON_VALUE)
- public DetectedGame confirmMatch(@PathVariable String slug, @RequestParam(required = false, defaultValue = "true") boolean confirm) {
- return gameService.confirmGame(slug, confirm);
- }
-
- @PostMapping(value = "/map-path", produces = MediaType.APPLICATION_JSON_VALUE)
- public DetectedGame manuallyMapPathToSlug(@RequestBody PathToSlugDto pathToSlugDto) {
- DetectedGame game = gameService.mapPathToGame(pathToSlugDto.getPath(), pathToSlugDto.getSlug());
-
- imageService.downloadGameCoversFromIgdb();
- imageService.downloadGameScreenshotsFromIgdb();
- imageService.downloadCompanyLogosFromIgdb();
-
- return game;
- }
-
- @GetMapping(value = "/unmapped-files", produces = MediaType.APPLICATION_JSON_VALUE)
- public List getUnmappedFiles() {
- return gameService.getAllUnmappedFiles();
- }
-
- @GetMapping(value = "/autocomplete-suggestions", produces = MediaType.APPLICATION_JSON_VALUE)
- public List getAutocompleteSuggestions(@RequestParam String searchTerm, @RequestParam(required = false, defaultValue = "10") int limit) {
- return libraryService.getAutocompleteSuggestions(searchTerm, limit);
- }
-
- @GetMapping(value = "/platforms", produces = MediaType.APPLICATION_JSON_VALUE)
- public List getPlatforms(@RequestParam String searchTerm, @RequestParam(required = false, defaultValue = "10") int limit) {
- return libraryService.getPlatforms(searchTerm, limit);
- }
-
- @GetMapping(value = "/libraries", produces = MediaType.APPLICATION_JSON_VALUE)
- public List getLibraries() {
- return libraryService.getOrCreateLibraries();
- }
-
- @PostMapping(value = "/map-library", produces = MediaType.APPLICATION_JSON_VALUE)
- public Library mapPathToPlatform(@RequestBody PathToSlugDto pathToSlugDto) {
- return libraryService.mapPlatformsToLibrary(pathToSlugDto.getPath(), pathToSlugDto.getSlug());
- }
-
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java b/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java
deleted file mode 100644
index c01f902..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package de.grimsi.gameyfin.service;
-
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.exceptions.DownloadAbortedException;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.catalina.connector.ClientAbortException;
-import org.apache.commons.io.FileUtils;
-import org.springframework.core.io.Resource;
-import org.springframework.stereotype.Service;
-import org.springframework.util.StopWatch;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.*;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.zip.Deflater;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-import static de.grimsi.gameyfin.util.FilenameUtil.getFilenameWithExtension;
-
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class DownloadService {
-
- private final FilesystemService filesystemService;
-
- public String getDownloadFileName(DetectedGame g) {
- Path path = filesystemService.getPath(g.getPath());
-
- if (!Files.isDirectory(path)) return getFilenameWithExtension(path);
- return getFilenameWithExtension(path) + ".zip";
- }
-
- public long getDownloadFileSize(DetectedGame game) {
- Path path = filesystemService.getPath(game.getPath());
-
- try {
- if (!Files.isDirectory(path)) {
- long fileSize = filesystemService.getSizeOnDisk(path);
- log.info("Calculated file size for {} ({} MB).", path, Math.divideExact(fileSize, 1000000L));
- return fileSize;
- } else {
- // return zero since we cannot set content length for ZipOutputStreams that are used to archive directories
- return 0;
- }
- } catch (IOException e) {
- throw new DownloadAbortedException();
- }
- }
-
- public Resource sendImageToClient(String imageId) {
- String filename = "%s.png".formatted(imageId);
- return filesystemService.getFileFromCache(filename);
- }
-
- public void sendGamefilesToClient(DetectedGame game, OutputStream outputStream) {
-
- StopWatch stopWatch = new StopWatch();
-
- log.info("Starting game file download for {}...", game.getTitle());
-
- stopWatch.start();
-
- Path path = filesystemService.getPath(game.getPath());
-
- try {
- if (path.toFile().isDirectory()) {
- sendGamefilesAsZipToClient(path, outputStream);
- } else {
- sendGamefileToClient(path, outputStream);
- }
- } catch (DownloadAbortedException e) {
- stopWatch.stop();
- log.info("Download of game {} was aborted by client after {} seconds", game.getTitle(), (int) stopWatch.getTotalTimeSeconds());
- return;
- }
-
- stopWatch.stop();
-
- log.info("Downloaded game files of {} in {} seconds.", game.getTitle(), (int) stopWatch.getTotalTimeSeconds());
- }
-
- private void sendGamefileToClient(Path path, OutputStream outputStream) {
- try {
- Files.copy(path, outputStream);
- } catch (ClientAbortException e) {
- // Aborted downloads will be handled gracefully
- throw new DownloadAbortedException();
- } catch (IOException e) {
- log.error("Error while downloading file:", e);
- }
- }
-
- private void sendGamefilesAsZipToClient(Path path, OutputStream outputStream) {
- log.info("Archiving game path {} for download...", path);
- ZipOutputStream zos = new ZipOutputStream(outputStream) {{
- def.setLevel(Deflater.NO_COMPRESSION);
- }};
-
- try {
- Files.walkFileTree(path, new SimpleFileVisitor<>() {
- @SneakyThrows
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
- zos.putNextEntry(new ZipEntry(path.relativize(file).toString()));
- log.debug("Adding file {} to archive...", file);
- Files.copy(file, zos);
- zos.closeEntry();
-
- return FileVisitResult.CONTINUE;
- }
- });
-
- zos.close();
- } catch (ClientAbortException e) {
- // Aborted downloads will be handled gracefully
- throw new DownloadAbortedException();
- } catch (IOException e) {
- log.error("Error while zipping files:", e);
- }
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/FilesystemService.java b/backend/src/main/java/de/grimsi/gameyfin/service/FilesystemService.java
deleted file mode 100644
index 5c8632a..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/service/FilesystemService.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package de.grimsi.gameyfin.service;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FileUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.io.ByteArrayResource;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.http.HttpStatus;
-import org.springframework.stereotype.Service;
-import org.springframework.web.server.ResponseStatusException;
-import reactor.core.publisher.Flux;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.file.*;
-
-/**
- * This class handles all filesystem operations for Gameyfin.
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class FilesystemService {
-
- @Value("${gameyfin.cache}")
- private String cacheFolderPath;
-
- private final FileSystem fileSystem;
-
- /**
- * Returns the given path on the configured filesystem.
- * Basically just another way of doing {@link Path#of(String, String...)}, but easier to mock.
- * @return The path
- */
- public Path getPath(String first, String... more) {
- return fileSystem.getPath(first, more);
- }
-
- /**
- * This method will create the folder specified in the "gameyfin.cache" property.
- * If the folder already exists, nothing will happen.
- */
- public void createCacheFolder() {
- log.debug("Creating cache folder...");
-
- try {
- Files.createDirectories(getPath(cacheFolderPath));
- log.debug("Cache folder created.");
- } catch (IOException e) {
- log.error("Error while creating the cache folder.", e);
- }
- }
-
- public void saveFileToCache(Flux dataBuffer, String filename) {
- DataBufferUtils.write(dataBuffer, getPath(cacheFolderPath).resolve(filename), StandardOpenOption.CREATE)
- .share().block();
- }
-
- public ByteArrayResource getFileFromCache(String filename) {
- try {
- return new ByteArrayResource(Files.readAllBytes(getPath(cacheFolderPath, filename)));
- } catch (IOException e) {
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find image file %s".formatted(filename));
- }
- }
-
- public void deleteFileFromCache(String filename) {
- try {
- Files.delete(getPathFromFilename(filename));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public boolean isCachedFileCorrupt(String filename) {
- try {
- return Files.size(getPathFromFilename(filename)) == 0L;
- } catch (IOException e) {
- log.error("Could not determine file size of '{}'", filename);
- return true;
- }
- }
-
- public boolean doesCachedFileExist(String filename) {
- return Files.exists(getPathFromFilename(filename));
- }
-
- public long getSizeOnDisk(Path path) throws IOException {
- if (Files.isDirectory(path)) {
- // Some benchmarks I did have shown that trying to parallelize this process makes it slower instead of faster
- return FileUtils.sizeOfDirectory(path.toFile());
- } else {
- try {
- return Files.size(path);
- } catch (IOException e) {
- log.error("Error while calculating size of file '{}'.", path);
- throw e;
- }
- }
- }
-
- private Path getPathFromFilename(String filename) {
- return getPath(cacheFolderPath, filename);
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/GameService.java b/backend/src/main/java/de/grimsi/gameyfin/service/GameService.java
deleted file mode 100644
index 62427ce..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/service/GameService.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package de.grimsi.gameyfin.service;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.dto.GameOverviewDto;
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.entities.Library;
-import de.grimsi.gameyfin.entities.UnmappableFile;
-import de.grimsi.gameyfin.igdb.IgdbWrapper;
-import de.grimsi.gameyfin.mapper.GameMapper;
-import de.grimsi.gameyfin.repositories.DetectedGameRepository;
-import de.grimsi.gameyfin.repositories.LibraryRepository;
-import de.grimsi.gameyfin.repositories.UnmappableFileRepository;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.http.HttpStatus;
-import org.springframework.stereotype.Service;
-import org.springframework.web.server.ResponseStatusException;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-@RequiredArgsConstructor
-@Slf4j
-@Service
-public class GameService {
-
- private final IgdbWrapper igdbWrapper;
- private final GameMapper gameMapper;
- private final DetectedGameRepository detectedGameRepository;
- private final UnmappableFileRepository unmappableFileRepository;
- private final LibraryRepository libraryRepository;
- private final FilesystemService filesystemService;
-
- public List getAllDetectedGames() {
- return detectedGameRepository.findAll();
- }
-
- public DetectedGame getDetectedGame(String slug) {
- return detectedGameRepository.findById(slug)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Game with slug '%s' not found in library.".formatted(slug)));
- }
-
- public List getAllUnmappedFiles() {
- return unmappableFileRepository.findAll();
- }
-
- public Map getAllMappings() {
- return detectedGameRepository.findAll().stream().collect(Collectors.toMap(DetectedGame::getPath, DetectedGame::getTitle));
- }
-
- public List getGameOverviews() {
- return detectedGameRepository.findAll().stream().map(gameMapper::toGameOverviewDto).toList();
- }
-
- public void deleteGame(String slug) {
- DetectedGame gameToBeDeleted = getDetectedGame(slug);
-
- // Add the path of the game to be deleted to the unmappable files,
- // so it doesn't get re-indexed on the next library scan
- unmappableFileRepository.save(new UnmappableFile(gameToBeDeleted.getPath()));
-
- detectedGameRepository.delete(gameToBeDeleted);
- }
-
- public void deleteUnmappedFile(Long id) {
- unmappableFileRepository.deleteById(id);
- }
-
- public DetectedGame confirmGame(String slug, boolean confirm) {
- DetectedGame g = getDetectedGame(slug);
- g.setConfirmedMatch(confirm);
- return detectedGameRepository.save(g);
- }
-
- public DetectedGame mapPathToGame(String path, String slug) {
-
- if (detectedGameRepository.existsBySlug(slug))
- throw new ResponseStatusException(HttpStatus.CONFLICT, "Game with slug '%s' already exists in database.".formatted(slug));
-
- Optional optionalUnmappableFile = unmappableFileRepository.findByPath(path);
-
- if (optionalUnmappableFile.isPresent()) {
- return mapUnmappableFile(optionalUnmappableFile.get(), slug);
- }
-
- Optional optionalDetectedGame = detectedGameRepository.findByPath(path);
-
- if (optionalDetectedGame.isPresent()) {
- return mapDetectedGame(optionalDetectedGame.get(), slug);
- }
-
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Path '%s' not in database".formatted(path));
- }
-
- public DetectedGame refreshGame(String slug) {
- Optional optionalDetectedGame = detectedGameRepository.findById(slug);
-
- if (optionalDetectedGame.isPresent()) {
- log.info("Refreshing game with slug '{}'", slug);
- return mapDetectedGame(optionalDetectedGame.get(), slug);
- }
-
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Game with slug '%s' not found in database".formatted(slug));
- }
-
- private DetectedGame mapUnmappableFile(UnmappableFile unmappableFile, String slug) {
- DetectedGame game = mapPathToGame(filesystemService.getPath(unmappableFile.getPath()), slug);
- unmappableFileRepository.delete(unmappableFile);
- return game;
- }
-
- private DetectedGame mapDetectedGame(DetectedGame existingGame, String slug) {
- DetectedGame game = mapPathToGame(filesystemService.getPath(existingGame.getPath()), slug);
- detectedGameRepository.delete(existingGame);
- return game;
- }
-
- private DetectedGame mapPathToGame(Path path, String slug) {
- Igdb.Game igdbGame = igdbWrapper.getGameBySlug(slug)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Game with slug '%s' does not exist on IGDB.".formatted(slug)));
-
- // Parent folder should be the library
- Path libraryPath = path.getParent();
- Library library = libraryRepository.findByPath(libraryPath.toString()).orElse(null);
- DetectedGame game = gameMapper.toDetectedGame(igdbGame, path, library);
- game.setConfirmedMatch(true);
-
- return detectedGameRepository.save(game);
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/ImageService.java b/backend/src/main/java/de/grimsi/gameyfin/service/ImageService.java
deleted file mode 100644
index 86696c6..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/service/ImageService.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package de.grimsi.gameyfin.service;
-
-import de.grimsi.gameyfin.entities.Company;
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.igdb.IgdbApiProperties;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.util.StopWatch;
-import org.springframework.util.StringUtils;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.reactive.function.client.WebClientResponseException;
-import reactor.core.publisher.Flux;
-
-import jakarta.annotation.PostConstruct;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-
-@Slf4j
-@RequiredArgsConstructor
-@Service
-public class ImageService {
-
- private final FilesystemService filesystemService;
- private final GameService gameService;
- private final WebClient.Builder webclientBuilder;
- private WebClient igdbImageClient;
-
- @PostConstruct
- public void init() {
- igdbImageClient = webclientBuilder.baseUrl(IgdbApiProperties.IMAGES_BASE_URL).build();
- }
-
- public int downloadGameCoversFromIgdb() {
- StopWatch stopWatch = new StopWatch();
-
- log.info("Starting game cover download...");
- stopWatch.start();
-
- MultiValueMap gameToImageIds = new LinkedMultiValueMap<>(
- gameService.getAllDetectedGames().stream()
- .collect(Collectors.toMap(DetectedGame::getSlug, g -> Collections.singletonList(g.getCoverId()))));
-
- int downloadCount = saveImagesIntoCache(gameToImageIds, IgdbApiProperties.COVER_IMAGE_SIZE, "cover", "game");
-
- stopWatch.stop();
-
- log.info("Downloaded {} covers in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
- return downloadCount;
- }
-
- public int downloadGameScreenshotsFromIgdb() {
- StopWatch stopWatch = new StopWatch();
-
- log.info("Starting game screenshot download...");
- stopWatch.start();
-
- MultiValueMap gamesToImageIds = new LinkedMultiValueMap<>(
- gameService.getAllDetectedGames().stream()
- .collect(Collectors.toMap(DetectedGame::getSlug, DetectedGame::getScreenshotIds)));
-
- int downloadCount = saveImagesIntoCache(gamesToImageIds, IgdbApiProperties.SCREENSHOT_IMAGE_SIZE, "screenshot", "game");
-
- stopWatch.stop();
-
- log.info("Downloaded {} screenshots in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
- return downloadCount;
- }
-
- public int downloadCompanyLogosFromIgdb() {
- StopWatch stopWatch = new StopWatch();
-
- log.info("Starting company logo download...");
- stopWatch.start();
-
- Map> companyToLogoIdMap = gameService.getAllDetectedGames().stream()
- .flatMap(g -> g.getCompanies().stream())
- .collect(Collectors.toMap(Company::getSlug, c -> Collections.singletonList(c.getLogoId()), (c1, c2) -> c1));
-
- MultiValueMap companiesToLogoIds = new LinkedMultiValueMap<>(companyToLogoIdMap);
-
- int downloadCount = saveImagesIntoCache(companiesToLogoIds, IgdbApiProperties.LOGO_IMAGE_SIZE, "logo", "company");
-
- stopWatch.stop();
-
- log.info("Downloaded {} company logos in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
- return downloadCount;
- }
-
- private int saveImagesIntoCache(MultiValueMap entityToImageIds, String imageSize, String imageType, String entityType) {
- AtomicInteger downloadCounter = new AtomicInteger();
-
- entityToImageIds.forEach((key, value) -> value.forEach(imageId -> {
-
- if (!StringUtils.hasText(imageId)) return;
-
- String imgFileName = "%s.png".formatted(imageId);
- String imgUrl = "t_%s/%s".formatted(imageSize, imgFileName);
-
- if (filesystemService.doesCachedFileExist(imgFileName)) {
- if (filesystemService.isCachedFileCorrupt(imgFileName)) {
- log.info("File '{}' is corrupt, retrying download...", imgFileName);
- filesystemService.deleteFileFromCache(imgFileName);
- } else {
- log.debug("{} for {} '{}' already downloaded ({}), skipping.",
- imageType.substring(0, 1).toUpperCase() + imageType.substring(1).toLowerCase(),
- entityType,
- key,
- imgFileName);
- return;
- }
- }
-
- Flux dataBuffer = igdbImageClient.get()
- .uri(imgUrl)
- .retrieve()
- .bodyToFlux(DataBuffer.class);
-
- try {
- filesystemService.saveFileToCache(dataBuffer, imgFileName);
- } catch (WebClientResponseException e) {
- if (e.getStatusCode().is4xxClientError()) {
- log.error("Could not download {} for {} '{}' from {}: {}", imageType, entityType, key, IgdbApiProperties.IMAGES_BASE_URL + imgUrl, e.getStatusCode());
- }
- }
-
- downloadCounter.getAndIncrement();
- log.info("Downloaded {} for {} '{}' from {}", imageType, entityType, key, IgdbApiProperties.IMAGES_BASE_URL + imgUrl);
- }));
-
- return downloadCounter.get();
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java b/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java
deleted file mode 100644
index 6134e18..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java
+++ /dev/null
@@ -1,257 +0,0 @@
-package de.grimsi.gameyfin.service;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
-import de.grimsi.gameyfin.dto.LibraryScanResult;
-import de.grimsi.gameyfin.entities.DetectedGame;
-import de.grimsi.gameyfin.entities.Library;
-import de.grimsi.gameyfin.entities.Platform;
-import de.grimsi.gameyfin.entities.UnmappableFile;
-import de.grimsi.gameyfin.igdb.IgdbWrapper;
-import de.grimsi.gameyfin.mapper.GameMapper;
-import de.grimsi.gameyfin.repositories.DetectedGameRepository;
-import de.grimsi.gameyfin.repositories.LibraryRepository;
-import de.grimsi.gameyfin.repositories.PlatformRepository;
-import de.grimsi.gameyfin.repositories.UnmappableFileRepository;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpStatus;
-import org.springframework.stereotype.Service;
-import org.springframework.util.StopWatch;
-import org.springframework.web.server.ResponseStatusException;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static de.grimsi.gameyfin.util.FilenameUtil.getFilenameWithoutAdditions;
-import static de.grimsi.gameyfin.util.FilenameUtil.hasGameArchiveExtension;
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toSet;
-import static org.apache.commons.lang3.StringUtils.isBlank;
-
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class LibraryService {
-
- @Value("${gameyfin.sources}")
- private List libraryFolders;
-
- private final IgdbWrapper igdbWrapper;
- private final GameMapper gameMapper;
- private final DetectedGameRepository detectedGameRepository;
- private final UnmappableFileRepository unmappableFileRepository;
- private final LibraryRepository libraryRepository;
- private final PlatformRepository platformRepository;
- private final FilesystemService filesystemService;
-
- public List getGameFiles() {
- return getGameFiles(null);
- }
-
- public List getGameFiles(String path) {
- List gamefiles = new ArrayList<>();
-
- libraryFolders.stream().map(Path::of).filter(allPathsOrSpecific(path)).forEach(
- folder -> {
- try (Stream stream = Files.list(folder)) {
- // return all sub-folders (non-recursive) and files that have an extension that indicates that they are a downloadable file
- List gameFilesFromThisFolder = stream
- .filter(p -> Files.isDirectory(p) || hasGameArchiveExtension(p))
- // filter out all hidden files and folders
- .filter(p -> {
- try {
- return !(Files.isHidden(p));
- } catch (IOException e) {
- throw new RuntimeException("Error while checking if '%s' is hidden.".formatted(p), e);
- }
- })
- // filter out all empty directories
- .filter(p -> {
- if (!Files.isDirectory(p)) return true;
-
- try (DirectoryStream s = Files.newDirectoryStream(p)) {
- return s.iterator().hasNext();
- } catch (IOException e) {
- throw new RuntimeException("Error while checking if folder '%s' is empty.".formatted(p), e);
- }
- })
- .toList();
-
- gamefiles.addAll(gameFilesFromThisFolder);
-
- } catch (IOException e) {
- throw new RuntimeException("Error while opening library folder '%s'".formatted(folder), e);
- }
- }
- );
-
- return gamefiles;
- }
-
- public LibraryScanResult scanGameLibrary(Library library) {
- StopWatch stopWatch = new StopWatch();
-
- log.info("Starting scan...");
- stopWatch.start();
-
- AtomicInteger newUnmappedFilesCounter = new AtomicInteger();
-
- String libraryPath = library.getPath();
- List gameFiles = getGameFiles(libraryPath);
-
- // Check if any games that are in the library have been removed from the file system
- // This would include renamed files, but they will be re-detected by the next step
- List deletedGames = detectedGameRepository.getAllByPathNotInAndPathStartsWith(gameFiles, libraryPath);
- detectedGameRepository.deleteAll(deletedGames);
- deletedGames.forEach(g -> log.info("Game '{}' has been moved or deleted.", g.getPath()));
-
- // Now check if there are any unmapped files that have been removed from the file system
- List deletedUnmappableFiles = unmappableFileRepository.getAllByPathNotInAndPathStartsWith(gameFiles, libraryPath);
- unmappableFileRepository.deleteAll(deletedUnmappableFiles);
- deletedUnmappableFiles.forEach(g -> log.info("Unmapped file '{}' has been moved or deleted.", g.getPath()));
-
- // Filter out the games we already know and the ones we already tried to map to a game without success
- gameFiles = gameFiles.stream()
- .filter(g -> !detectedGameRepository.existsByPath(g.toString()))
- .filter(g -> !unmappableFileRepository.existsByPath(g.toString()))
- .peek(p -> log.info("Found new potential game: {}", p))
- .toList();
-
- // Check if library has assigned platforms, so we can search for matching games by specific platforms to get a more accurate match
- Set platformsFilter = libraryRepository.findByPath(libraryPath).map(Library::getPlatforms)
- .map(platforms -> platforms.stream()
- .map(Platform::getSlug)
- .collect(Collectors.toSet()))
- .orElse(Set.of());
-
- // For each new game, load the info from IGDB
- // If a game is not found on IGDB, add it to the list of unmapped files, so we won't query the API later on for the same path
- // If a game is not found on IGDB, blacklist the path, so we won't query the API later for the same path
- List newDetectedGames = gameFiles.stream()
- .map(p -> {
- Optional optionalGame = igdbWrapper.searchForGameByTitle(getFilenameWithoutAdditions(p), platformsFilter);
-
- if (optionalGame.isPresent() && detectedGameRepository.existsBySlug(optionalGame.get().getSlug())) {
- log.warn("Game with slug '{}' already exists in database", optionalGame.get().getSlug());
- optionalGame = Optional.empty();
- }
-
- return optionalGame.map(game -> Map.entry(p, game)).or(() -> {
- unmappableFileRepository.save(new UnmappableFile(p.toString()));
- newUnmappedFilesCounter.getAndIncrement();
- log.info("Added path '{}' to list of unmapped files", p);
- return Optional.empty();
- });
- })
- .filter(Optional::isPresent)
- .map(Optional::get)
- .peek(e -> log.info("Mapped file '{}' to game '{}' (slug: {})", e.getKey(), e.getValue().getName(), e.getValue().getSlug()))
- .map(e -> gameMapper.toDetectedGame(e.getValue(), e.getKey(), library))
- .collect(toList());
-
- List duplicateGames = getDuplicates(newDetectedGames);
- newUnmappedFilesCounter.getAndAdd(duplicateGames.size());
- newDetectedGames.removeAll(duplicateGames);
-
- try {
- newDetectedGames = detectedGameRepository.saveAll(newDetectedGames);
- } catch (Exception e) {
- log.error("Could not save {} detected games!", newDetectedGames.size());
- List unmappableFiles = newDetectedGames.stream()
- .map(game -> new UnmappableFile(game.getPath())).toList();
- unmappableFileRepository.saveAll(unmappableFiles);
- }
-
- stopWatch.stop();
-
- log.info("Scan finished in {} seconds: Found {} new games, deleted {} games, could not map {} files/folders, {} games total.",
- (int) stopWatch.getTotalTimeSeconds(), newDetectedGames.size(), deletedGames.size() + deletedUnmappableFiles.size(), newUnmappedFilesCounter.get(), detectedGameRepository.count());
-
- return LibraryScanResult.builder()
- .newGames(newDetectedGames.size())
- .deletedGames(deletedGames.size() + deletedUnmappableFiles.size())
- .newUnmappableFiles(newUnmappedFilesCounter.get())
- .totalGames((int) detectedGameRepository.count())
- .build();
- }
-
- public List getAutocompleteSuggestions(String searchTerm, int limit) {
- return igdbWrapper.findPossibleMatchingTitles(searchTerm, limit);
- }
-
- private List getDuplicates(List gamesToFilter) {
- return gamesToFilter.stream().filter(g -> Collections.frequency(gamesToFilter, g) > 1)
- .peek(d -> {
- log.warn("Found duplicate for game '{}' under path '{}'. Mapping must be done manually.", d.getTitle(), d.getPath());
- unmappableFileRepository.save(new UnmappableFile(d.getPath()));
- })
- .toList();
- }
-
- public List getLibraries() {
- return libraryRepository.findAll();
- }
-
- public Library getLibrary(String path) {
- return libraryRepository.findByPath(path).orElse(null);
- }
-
- public List getOrCreateLibraries() {
- libraryFolders.stream().map(Path::of)
- .filter(path -> path.toFile().isDirectory()) // check if path is a valid directory
- .filter(path -> !libraryRepository.existsByPathIgnoreCase(path.toString()))
- .forEach(path -> {
- // save new paths as library without platforms
- Library library = new Library(path.toString(), List.of());
- libraryRepository.save(library);
- });
-
- List libraries = libraryRepository.findAll();
- libraries.forEach(library -> {
- // remap existing games to this library as well
- List gamesWithoutLibraryAssignment =
- detectedGameRepository.findByPathStartsWithAndLibraryIsNull(library.getPath());
- gamesWithoutLibraryAssignment.forEach(game -> game.setLibrary(library));
- detectedGameRepository.saveAll(gamesWithoutLibraryAssignment);
- });
- return libraries;
- }
-
- public List getPlatforms(String searchTerm, int limit) {
- return igdbWrapper.findPlatforms(searchTerm, limit);
- }
-
- public Library mapPlatformsToLibrary(String path, String slugs) {
- Library library = libraryRepository.findByPath(path)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find library for path %s".formatted(path)));
-
- Set platformSlugs = Arrays.stream(slugs.split(",")).collect(toSet());
-
- List platforms = platformSlugs.stream()
- .map(slug -> {
- Optional p = platformRepository.findBySlug(slug);
- return p.isPresent() ? p : igdbWrapper.getPlatformBySlug(slug);
- })
- .filter(Optional::isPresent)
- .map(Optional::get)
- .collect(toList());
-
- library.setPlatforms(platforms);
- libraryRepository.save(library);
-
- return library;
- }
-
- private Predicate allPathsOrSpecific(String path) {
- return p -> isBlank(path) || p.equals(filesystemService.getPath(path));
- }
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/util/FilenameUtil.java b/backend/src/main/java/de/grimsi/gameyfin/util/FilenameUtil.java
deleted file mode 100644
index 36b29d0..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/util/FilenameUtil.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package de.grimsi.gameyfin.util;
-
-import de.grimsi.gameyfin.config.properties.GameyfinProperties;
-import org.apache.commons.io.FilenameUtils;
-import org.springframework.stereotype.Service;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Service
-public class FilenameUtil {
-
- private static List possibleGameFileExtensions;
- private static List possibleGameFileSuffixes;
- // matches v1.1.1 v1.1 v1 version numbers
- private static final Pattern versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)");
-
- // Suppress SONAR detecting this as a potential stack overflow
- // SONAR is correct, but I honestly don't know how to fix it
- // Also it would require 6k+ character long filenames to really overflow the JVM stack
- @SuppressWarnings("java:S5998")
- private static final Pattern trailingNoisePattern = Pattern.compile("( |\\(\\)|\\[]|[-_.])+$");
- @SuppressWarnings("java:S5998")
- private static final Pattern headingNoisePattern = Pattern.compile("^( |\\(\\)|\\[]|[-_.])+");
-
- public FilenameUtil(GameyfinProperties gameyfinProperties) {
- possibleGameFileExtensions = gameyfinProperties.fileExtensions();
-
- // Sort in descending length, so for example "windows" gets checked before "win"
- FilenameUtil.possibleGameFileSuffixes = gameyfinProperties.fileSuffixes();
- possibleGameFileSuffixes.sort((s1,s2) -> Integer.compare(s2.length(), s1.length()));
- }
-
- public static String getFilenameWithoutExtension(Path p) {
-
- // If the path points to a folder, return the folder name
- // Folders like "Counter Strike 1.6" would otherwise be returned as "Counter Strike 1"
- if (Files.isDirectory(p)) return FilenameUtils.getName(p.toString());
-
- return FilenameUtils.getBaseName(p.toString());
- }
-
- public static String getFilenameWithExtension(Path p) {
- return FilenameUtils.getName(p.toString());
- }
-
- public static boolean hasGameArchiveExtension(Path p) {
- return possibleGameFileExtensions.contains(FilenameUtils.getExtension(p.getFileName().toString()));
- }
-
- public static String getFilenameWithoutAdditions(Path p) {
- String name = getFilenameWithoutExtension(p).toLowerCase();
- for(String suffix : possibleGameFileSuffixes) {
- name = name.replace(suffix, "");
- }
- name = removePattern(name, versionPattern);
- name = removePattern(name, trailingNoisePattern);
- name = removePattern(name, headingNoisePattern);
-
- // sanity check to never return an empty name
- return name.isBlank() ? getFilenameWithoutExtension(p) : name;
- }
-
- public static String removePattern(String string, Pattern pattern) {
- Matcher matcher = pattern.matcher(string);
- if (matcher.find()) {
- return matcher.replaceAll("");
- }
- return string;
- }
-
-}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/util/ProtobufUtil.java b/backend/src/main/java/de/grimsi/gameyfin/util/ProtobufUtil.java
deleted file mode 100644
index 68439f2..0000000
--- a/backend/src/main/java/de/grimsi/gameyfin/util/ProtobufUtil.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package de.grimsi.gameyfin.util;
-
-import com.google.protobuf.Timestamp;
-
-import java.time.Instant;
-
-public class ProtobufUtil {
- public static Instant toInstant(Timestamp t) {
- return Instant.ofEpochSecond(t.getSeconds(), t.getNanos());
- }
-}
diff --git a/backend/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/backend/src/main/resources/META-INF/additional-spring-configuration-metadata.json
deleted file mode 100644
index e346e41..0000000
--- a/backend/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "properties": [
- {
- "name": "gameyfin.sources",
- "type": "java.lang.String[]",
- "description": "List of directories Gameyfin should scan for games."
- }
- ]
-}
\ No newline at end of file
diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml
deleted file mode 100644
index a9cd889..0000000
--- a/backend/src/main/resources/application-dev.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-gameyfin:
- user: admin
- password: password
-
-logging:
- level:
- de.grimsi: debug
- # org.springframework.web.reactive.function.client.ExchangeFunctions: debug
- org.apache.catalina.core.ContainerBase: info
-
-spring.mvc.log-request-details: true
\ No newline at end of file
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
deleted file mode 100644
index 708facf..0000000
--- a/backend/src/main/resources/application.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-# General
-logging.level:
- root: info
- # Hides an error log on the first aborted download
- org.apache.catalina.core.ContainerBase: off
diff --git a/backend/src/main/resources/banner.txt b/backend/src/main/resources/banner.txt
deleted file mode 100644
index c29a8a8..0000000
--- a/backend/src/main/resources/banner.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-${AnsiColor.GREEN}
- _____ ___ _
- / ___/ ___ _ __ _ ___ __ __ / _/ (_) ___
-/ (_ / / _ `/ / ' \/ -_) / // / / _/ / / / _ \
-\___/ \_,_/ /_/_/_/\__/ \_, / /_/ /_/ /_//_/
- /___/
-${AnsiColor.WHITE}
-${application.name} ${application.version}
-Powered by Spring Boot ${spring-boot.version}
-
diff --git a/backend/src/main/resources/config/database.yml b/backend/src/main/resources/config/database.yml
deleted file mode 100644
index 7551f4d..0000000
--- a/backend/src/main/resources/config/database.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-spring:
- jpa:
- open-in-view: true
- properties:
- hibernate:
- enable_lazy_load_no_trans: true
- event:
- merge:
- entity_copy_observer: allow
- database-platform: org.hibernate.dialect.H2Dialect
- hibernate:
- ddl-auto: none
- flyway:
- baseline-on-migrate: true
- datasource:
- username: gfadmin
- password: gameyfin
- db-name: gameyfin_db
- url: jdbc:h2:file:${gameyfin.db}/${spring.datasource.db-name}
- driverClassName: org.h2.Driver
\ No newline at end of file
diff --git a/backend/src/main/resources/config/gameyfin.yml b/backend/src/main/resources/config/gameyfin.yml
deleted file mode 100644
index c7f6eb1..0000000
--- a/backend/src/main/resources/config/gameyfin.yml
+++ /dev/null
@@ -1,301 +0,0 @@
-gameyfin:
- folders:
- data: ""
- file-extensions:
- - 'rar' # Compressed Archive -- Use WinRAR or 7-Zip to open and extract.
- - '7z' # Compressed Archive -- Use WinRAR or 7-Zip to open and extract.
- - '7zip' # Compressed Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'zip' # Compressed Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'tar.gz' # Compressed Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'n.gz' # Compressed Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'tar' # Archive -- Use 7-Zip to open and extract.
- - '001' # Split Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'part1' # Split Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'r01' # Split Archive -- Use WinRAR or 7-Zip to open and extract.
- - 'gzip' # Compressed Archive -- Use 7-Zip to open and extract. Can be loaded by PCSX2 directly.
- - 'iso' # Disk Image for Multiple systems
- - 'img' # Disk Image for Multiple systems
- - 'ccd' # CloneCD Control File for Multiple systems -- Usually comes with .img files.
- - 'bin' # Binary File for Multiple systems -- Usually accompanied with, and used by, a cue sheet.
- - 'cue' # Cue Sheet for Multiple systems -- Metadata file for .bin/.iso/.img tracks.
- - 'chd' # Compressed Hard Disk for Multiple systems -- Originally made for MAME, now used by multiple emulators
- - 'mdf' # Media Descriptor File for Multiple systems -- Disc image made from Alcohol 120%
- - 'mds' # Media Descriptor Sheet for Multiple systems -- Similar to .cue or .ccd, but for .mdf files.
- - 'ecm' # Error Code Modeler for PlayStation -- Compressed PS1 disc image, use Unecm to decompress it.
- - 'cso' # Compressed ISO for PS2/PSP/GC/Wii -- Compressed disc image, see here to convert to and from .iso
- - 'gcz' # Compressed Dolphin ISO for GC/Wii -- Compressed Wii/GC disc image, see here to convert to and from .iso or Compressed GameCube Disc Image for GameCube -- Can be either a compressed .gcm or .iso
- - 'rvz' # Modern Dolphin Format for GC/Wii -- A new compressed format developed by the Dolphin team able to efficiently compress junk/padding data, allowing both small file sizes and archival quality.
- - 'cdi' # CD Image for Dreamcast -- Typically used with Dreamcast, CDI is a compressed GDI
- - 'gdi' # CD Image for Dreamcast -- Typically used with Dreamcast, CDI is a compressed GDI
- - 'sbI' # CD Image -- Sub-channel data. Generally used in the case of PS1 for Digital Rights Management
- - 'fds' # Famicom Disk System for NES
- - 'ndd' # Nintendo 64DD for N64
- - 'wav' # Waveform Audio File Format for Tape Based Systems -- Raw data in audio format†
- - 'tap' # Tape File for Tape Based Systems -- Raw tape data usable by appropriate emulators
- - 'tzx' # ZX Spectrum Tape File for Compatible Tape Based Systems (ZXS, CPC, C64, etc.) -- Prefered file type for all emulators, perfect representation
- - 'cdc' # CPC Digital Tape for Amstrad CPC -- Identical to the .tzx file but useable only by Amstrad CPC emulators
- - 'cas' # PC-6000 and PC-6601 Cassette for NEC PC-6000 series
- - 'nes' # NES ROM for NES -- .unf and .unif correspond to the now deprecated Universal NES Image Format.
- - 'nez' # NES ROM for NES -- .unf and .unif correspond to the now deprecated Universal NES Image Format.
- - 'unf' # NES ROM for NES -- .unf and .unif correspond to the now deprecated Universal NES Image Format.
- - 'unif' # NES ROM for NES -- .unf and .unif correspond to the now deprecated Universal NES Image Format.
- - 'smc' # Super Magicom for SNES -- Headered ROMs dumped from a Super Magicom copier.
- - 'sfc' # Super Famicom ROM for SNES -- Headerless SNES ROM
- - 'md' # Multi Game Doctor for Genesis -- ROMs dumped from a Multi Game Doctor
- - 'smd' # Super Magic Drive for Genesis -- Headered ROMs dumped from a Super Magic Drive
- - 'gen' # Genesis ROM for Genesis -- Headerless Genesis ROM
- - 'gg' # Game Gear ROM for Game Gear
- - 'z64' # Zip Diskette N64 ROM for Nintendo 64 -- Headered N64 ROMS dumped with a Mr. Backup Z64
- - 'v64' # Doctor V64 for Nintendo 64 -- Headered N64 ROMS dumped with a Doctor V64
- - 'n64' # N64 ROM for Nintendo 64
- - 'gb' # Game Boy ROM for Game Boy
- - 'gbc' # Game Boy Color ROM for Game Boy Color
- - 'gba' # Game Boy Advance ROM for Game Boy Advance
- - 'gcm' # GameCube Master Image for GameCube -- GC disc dumped the way it's read
- - 'nds' # Nintendo DS ROM for Nintendo DS or Game Boy Advance ROM for Game Boy Advance
- - 'srl' # Nintendo DS ROM for Nintendo DS
- - 'dsi' # DSiWare ROM for DSiWare -- Different from regular DS ROMs.
- - 'app' # DSiWare ROM for DSiWare -- Different from regular DS ROMs.
- - 'ids' # iQue DS ROM for iQue/Nintendo DS -- The same thing as regular Nintendo DS ROMs, but for the Chinese iQue DS.
- - 'wbfs' # Wii Backup File System for Wii -- Use WBFS Manager to convert to and from .iso
- - 'wad' # Wii Application Data for Wii or "Where's all the data files?" for Doom -- Used for Wii channels or Doom
- - 'cia' # CTR Importable Archive for 3DS
- - '3ds' # Nintendo 3DS ROM for 3DS
- - 'nsp' # Nintendo Switch ROM for Nintendo Switch -- Dump of SD and NAND games, Updates
- - 'xci' # Nintendo Switch ROM for Nintendo Switch -- Dump of a game cartridge
- - 'ngp' # NGP/C ROM for Neo Geo Pocket/Color
- - 'ngc' # NGP/C ROM for Neo Geo Pocket/Color
- - 'pce' # PC Engine ROM for PC Engine
- - 'vpk' # PlayStation Vita ROM for PlayStation Vita -- Compressed files forming a PlayStation Vita ROM
- - 'vb' # Virtual Boy ROM for Virtual Boy -- Also used for Visual Basic source files
- - 'ws' # WonderSwan (Color) ROM for WonderSwan/Color
- - 'wsc' # WonderSwan (Color) ROM for WonderSwan/Color
- - 'ipa' # Apple iPhone App Data for iOS
- - 'apk' # Android App Data for Android
- - 'obb' # Android App Data Resources for Android
- - 'elf' # Executable and Linkable Format for PS2/PS3/GC/Wii -- Typically a homebrew/small application file
- - 'pbp' # Perl Builder File for PlayStation Portable -- Game and homebrew eboot file, also used for PS1 ISO storage (eboot.pbp)
- - 'dol' # Dolphin File for GameCube/Wii -- Executable
- - 'xbe' # Xbox Executable for Xbox/X360 -- Xbox Executable. Usually comes with a folder with game data. X360 ones can be loaded by Xenia.
- - 'xex' # Xbox Executable for Xbox/X360 -- Xbox Executable. Usually comes with a folder with game data. X360 ones can be loaded by Xenia.
- - 'cfg' # Configuration File for Multiple systems -- Use a text editor such as notepad/Notepad++ to edit
- - 'ini' # Configuration File for Multiple systems -- Use a text editor such as notepad/Notepad++ to edit
- - 'dll' # Dynamic Link Library for Multiple systems -- Typically used for plugins/emulation cores in emulators
- - 'so' # Shared Library for Multiple systems -- Typically used for plugins/emulation cores in emulators
- - 'xml' # Extensible Markup Language for MAME/MESS -- Contains various information about emulated systems and ROMs.
- - 'hsi' # XML-like for MESS -- Contains various information about ROMs.
- - 'lay' # Layout for MAME/MESS -- XML-like file used to describe the visual layout of artwork overlays and placement of emulation input/output.
- - 'nv' # Non-Volatile RAM for MAME/MESS -- Created by default to store nvram of emulated systems or software in the NVRAM folder.
- - 'm3u' # Playlist file for Multiple systems -- Used to play multiple discs back to back, automates disc swapping.
- file-suffixes: windows, win, english, win32, win64, opengl, stable
- igdb:
- api:
- endpoints:
- auth: https://id.twitch.tv/oauth2/token
- base: https://api.igdb.com/v4/
- client-id:
- client-secret:
- max-concurrent-requests: 2
- max-requests-per-second: 4
- config:
- preferred-platforms:
- - 3 # Linux or GNU/Linux
- - 4 # Nintendo 64 or N64
- - 5 # Wii or Revolution
- - 6 # PC (Microsoft Windows) or mswin
- - 7 # PlayStation or PSX, PSOne, PS
- - 8 # PlayStation 2 or PS2
- - 9 # PlayStation 3 or PS3
- - 11 # Xbox
- - 12 # Xbox 360 or X360
- # - 13 # DOS or PC DOS
- - 14 # Mac or Mac OS
- # - 15 # Commodore C64/128/MAX or C64/C128/MAX
- # - 16 # Amiga
- - 18 # Nintendo Entertainment System or NES
- - 19 # Super Nintendo Entertainment System or SNES, Super Nintendo
- - 20 # Nintendo DS or NDS
- - 21 # Nintendo GameCube or GCN
- - 22 # Game Boy Color or GBC
- - 23 # Dreamcast or DC
- - 24 # Game Boy Advance or GBA
- # - 25 # Amstrad CPC or Colour Personal Computer
- # - 26 # ZX Spectrum
- # - 27 # MSX
- - 29 # Sega Mega Drive/Genesis or Sega Genesis
- # - 30 # Sega 32X
- - 32 # Sega Saturn or JVC Saturn, Hi-Saturn, Samsung Saturn, V-Saturn
- - 33 # Game Boy or GB
- # - 34 # Android or Infocusa3
- - 35 # Sega Game Gear or GG
- - 37 # Nintendo 3DS or 3DS
- - 38 # PlayStation Portable or PSP
- # - 39 # iOS
- - 41 # Wii U
- # - 42 # N-Gage or NGage
- # - 44 # Tapwave Zodiac
- - 46 # PlayStation Vita or PS Vita
- - 47 # Virtual Console
- - 48 # PlayStation 4 or PS4
- # - 49 # Xbox One or XONE
- # - 50 # 3DO Interactive Multiplayer or 3DO
- # - 51 # Family Computer Disk System or Famicom Disk System, FDS
- # - 52 # Arcade
- # - 53 # MSX2
- # - 55 # Legacy Mobile Device or Legacy Cellphone
- # - 57 # WonderSwan or WS
- - 58 # Super Famicom or SFC
- # - 59 # Atari 2600 or Atari VCS
- # - 60 # Atari 7800 or Atari 7800 ProSystem
- # - 61 # Atari Lynx
- # - 62 # Atari Jaguar
- # - 63 # Atari ST/STE
- - 64 # Sega Master System/Mark III or SMS, Mark III
- # - 65 # Atari 8-bit
- # - 66 # Atari 5200 or Atari 5200 SuperSystem
- # - 67 # Intellivision
- # - 68 # ColecoVision
- # - 69 # BBC Microcomputer System or BBC Micro
- # - 70 # Vectrex
- # - 71 # Commodore VIC-20
- # - 72 # Ouya
- # - 73 # BlackBerry OS
- # - 74 # Windows Phone or WP
- # - 75 # Apple II or apple ][
- # - 77 # Sharp X1
- # - 78 # Sega CD or Mega CD
- # - 79 # Neo Geo MVS or Neo Geo Multi Video System
- # - 80 # Neo Geo AES or AES
- # - 82 # Web browser or Internet
- # - 84 # SG-1000 or Sega Game 1000
- # - 85 # Donner Model 30
- # - 86 # TurboGrafx-16/PC Engine
- # - 87 # Virtual Boy or VB
- # - 88 # Odyssey or Magnavox Odyssey; Odysee; Odisea; Odissea
- # - 89 # Microvision
- # - 90 # Commodore PET
- # - 91 # Bally Astrocade or Bally Arcade
- # - 93 # Commodore 16 or C16
- # - 94 # Commodore Plus/4
- # - 95 # PDP-1 or Programmed Data Processor-1
- # - 96 # PDP-10
- # - 97 # PDP-8
- # - 98 # DEC GT40
- # - 99 # Family Computer or Famicom
- # - 100 # Analogue electronics
- # - 101 # Ferranti Nimrod Computer
- # - 102 # EDSAC or Electronic Delay Storage Automatic Calculator
- # - 103 # PDP-7
- # - 104 # HP 2100
- # - 105 # HP 3000
- # - 106 # SDS Sigma 7
- # - 107 # Call-A-Computer time-shared mainframe computer system
- # - 108 # PDP-11
- # - 109 # CDC Cyber 70
- # - 110 # PLATO or Programmed Logic for Automatic Teaching Operations
- # - 111 # Imlac PDS-1
- # - 112 # Microcomputer
- # - 113 # OnLive Game System
- # - 114 # Amiga CD32
- # - 115 # Apple IIGS
- # - 116 # Acorn Archimedes
- # - 117 # Philips CD-i
- # - 118 # FM Towns
- # - 119 # Neo Geo Pocket or NGP
- # - 120 # Neo Geo Pocket Color or NGPC
- # - 121 # Sharp X68000
- # - 122 # Nuon
- # - 123 # WonderSwan Color or WSC
- # - 124 # SwanCrystal
- # - 125 # PC-8801
- # - 126 # TRS-80
- # - 127 # Fairchild Channel F
- # - 128 # PC Engine SuperGrafx
- # - 129 # Texas Instruments TI-99 or Texas Instruments TI-99/4A
- - 130 # Nintendo Switch or NX
- # - 131 # Nintendo PlayStation or Nintendo Super Disc
- # - 132 # Amazon Fire TV
- # - 133 # Odyssey 2 / Videopac G7000 or Magnavox Odyssey²
- # - 134 # Acorn Electron
- # - 135 # Hyper Neo Geo 64
- # - 136 # Neo Geo CD or NGCD
- - 137 # New Nintendo 3DS or n3DS
- # - 138 # VC 4000
- # - 139 # 1292 Advanced Programmable Video System
- # - 140 # AY-3-8500
- # - 141 # AY-3-8610
- # - 142 # PC-50X Family
- # - 143 # AY-3-8760
- # - 144 # AY-3-8710
- # - 145 # AY-3-8603
- # - 146 # AY-3-8605
- # - 147 # AY-3-8606
- # - 148 # AY-3-8607
- # - 149 # PC-98
- # - 150 # Turbografx-16/PC Engine CD or TG-16CD/PCECD
- # - 151 # TRS-80 Color Computer or Tandy Color Computer
- # - 152 # FM-7 or Fujitsu Micro 7
- # - 153 # Dragon 32/64
- # - 154 # Amstrad PCW
- # - 155 # Tatung Einstein
- # - 156 # Thomson MO5
- # - 157 # NEC PC-6000 Series
- # - 158 # Commodore CDTV or Commodore Dynamic Total Vision
- - 159 # Nintendo DSi
- # - 161 # Windows Mixed Reality or WMR
- # - 162 # Oculus VR
- # - 163 # SteamVR
- # - 164 # Daydream
- - 165 # PlayStation VR or PSVR
-# - 166 # Pokémon mini
-# - 167 # PlayStation 5 or PS5
-# - 169 # Xbox Series X|S or XSX
-# - 170 # Google Stadia or Stadia
-# - 203 # DUPLICATE Stadia
-# - 236 # Exidy Sorcerer
-# - 237 # Sol-20
-# - 238 # DVD Player or Digital Versatile Disc Player
-# - 239 # Blu-ray Player
-# - 240 # Zeebo
-# - 274 # PC-FX
-# - 306 # Satellaview
-# - 307 # Game & Watch or Tricotronic, GW, G&W
-# - 308 # Playdia
-# - 309 # Evercade
-# - 339 # Sega Pico or Kids Computer Pico
-# - 372 # OOParts
-# - 373 # Sinclair ZX81 or ZX81
-# - 374 # Sharp MZ-2200
-# - 375 # Epoch Cassette Vision
-# - 376 # Epoch Super Cassette Vision or YENO Super Cassette Vision
-# - 377 # Plug & Play or TV Game
-# - 378 # Gamate or Super Boy
-# - 379 # Game.com or Tiger Game.com
-# - 380 # Casio Loopy or Loopy
-# - 381 # Playdate
-# - 382 # Intellivision Amico
-# - 384 # Oculus Quest or Quest
-# - 385 # Oculus Rift or Rift
-# - 386 # Meta Quest 2 or Quest 2
-# - 387 # Oculus Go or Go
-# - 388 # Gear VR or Samsung Gear VR
-# - 389 # AirConsole
-# - 390 # PlayStation VR2 or PSVR2
-# - 405 # Windows Mobile or Pocket PC
-# - 406 # Sinclair QL or Sinclair Quantum Leap
-# - 407 # HyperScan
-# - 408 # Mega Duck/Cougar Boy or WG-108
-# - 409 # Legacy Computer
-# - 410 # Atari Jaguar CD or Jag CD
-# - 411 # Handheld Electronic LCD or Handheld LCD Game
-# - 412 # Leapster or Leapster Learning Game System
-# - 413 # Leapster Explorer/LeadPad Explorer
-# - 414 # LeapTV or LeapTV VCD
-# - 415 # Watara/QuickShot Supervision
-# - 416 # Nintendo 64DD or 64DD
-# - 417 # Palm OS or Garnet OS
-# - 438 # Arduboy
-# - 439 # V.Smile or V.SMILE TV LEARNING SYSTEM
-# - 440 # Visual Memory Unit / Visual Memory System or VMU / VMS
-# - 441 # PocketStation
-# - 471 # Meta Quest 3
diff --git a/backend/src/main/resources/config/secure.yml b/backend/src/main/resources/config/secure.yml
deleted file mode 100644
index d400a0a..0000000
--- a/backend/src/main/resources/config/secure.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-application:
- version: '@project.version@'
- name: Gameyfin
-
-server:
- servlet:
- context-path: /
- error:
- include-stacktrace: never
-
-spring:
- jackson:
- deserialization:
- fail-on-unknown-properties: false
- default-property-inclusion: non_null
- mapper:
- accept-case-insensitive-enums: true
- mvc:
- async:
- request-timeout: -1
-
-management:
- endpoint:
- health:
- enabled: true
- endpoints:
- enabled-by-default: false
-
-gameyfin:
- internal-folder: .gameyfin
diff --git a/backend/src/main/resources/db/migration/V1_0_0__Initial_Database_Setup.sql b/backend/src/main/resources/db/migration/V1_0_0__Initial_Database_Setup.sql
deleted file mode 100644
index 1c3687f..0000000
--- a/backend/src/main/resources/db/migration/V1_0_0__Initial_Database_Setup.sql
+++ /dev/null
@@ -1,148 +0,0 @@
--- Automatically generated by JPA
-
--- Hibernate sequence
-create sequence HIBERNATE_SEQUENCE start with 1 increment by 1;
-
--- Detected Games
-create table DETECTED_GAME
-(
- slug varchar(255) not null,
- category varchar(255),
- confirmed_match boolean default false,
- cover_id varchar(255) not null,
- critics_rating integer,
- disk_size bigint not null,
- lan_support boolean not null,
- max_players integer not null,
- offline_coop boolean not null,
- online_coop boolean not null,
- path varchar(255) not null,
- release_date timestamp,
- summary CLOB,
- title varchar(255) not null,
- total_rating integer,
- user_rating integer,
- primary key (slug)
-);
-
--- Companies
-create table COMPANY
-(
- slug varchar(255) not null,
- logo_id varchar(255),
- name varchar(255) not null,
- primary key (slug)
-);
-
--- Genres
-create table GENRE
-(
- slug varchar(255) not null,
- name varchar(255),
- primary key (slug)
-);
-
--- Themes
-create table THEME
-(
- slug varchar(255) not null,
- name varchar(255),
- primary key (slug)
-);
-
--- Keywords
-create table KEYWORD
-(
- slug varchar(255) not null,
- name varchar(255),
- primary key (slug)
-);
-
--- Player Perspectives
-create table PLAYER_PERSPECTIVE
-(
- slug varchar(255) not null,
- name varchar(255),
- primary key (slug)
-);
-
--- Unmappable files
-create table UNMAPPABLE_FILE
-(
- id bigint not null,
- path varchar(255),
- primary key (id)
-);
-
--- Game <-> Companies
-create table DETECTED_GAME_COMPANIES
-(
- detected_game_slug varchar(255) not null,
- companies_slug varchar(255) not null
-);
-alter table DETECTED_GAME_COMPANIES
- add constraint companies_company_slug foreign key (companies_slug) references company;
-alter table DETECTED_GAME_COMPANIES
- add constraint companies_detected_game_slug foreign key (detected_game_slug) references detected_game;
-
--- Game <-> Genres
-create table DETECTED_GAME_GENRES
-(
- detected_game_slug varchar(255) not null,
- genres_slug varchar(255) not null
-);
-alter table DETECTED_GAME_GENRES
- add constraint genres_genre_slug foreign key (genres_slug) references genre;
-alter table DETECTED_GAME_GENRES
- add constraint genres_detected_game_slug foreign key (detected_game_slug) references detected_game;
-
--- Game <-> Themes
-create table DETECTED_GAME_THEMES
-(
- detected_game_slug varchar(255) not null,
- themes_slug varchar(255) not null
-);
-alter table DETECTED_GAME_THEMES
- add constraint themes_theme_slug foreign key (themes_slug) references theme;
-alter table DETECTED_GAME_THEMES
- add constraint themes_detected_game_slug foreign key (detected_game_slug) references detected_game;
-
--- Game <-> Keywords
-create table DETECTED_GAME_KEYWORDS
-(
- detected_game_slug varchar(255) not null,
- keywords_slug varchar(255) not null
-);
-alter table DETECTED_GAME_KEYWORDS
- add constraint keywords_keyword_slug foreign key (keywords_slug) references keyword;
-alter table DETECTED_GAME_KEYWORDS
- add constraint keywords_detected_game_slug foreign key (detected_game_slug) references detected_game;
-
--- Game <-> Player Perspectives
-create table DETECTED_GAME_PLAYER_PERSPECTIVES
-(
- detected_game_slug varchar(255) not null,
- player_perspectives_slug varchar(255) not null
-);
-alter table DETECTED_GAME_PLAYER_PERSPECTIVES
- add constraint player_perspectives_player_perspective_slug foreign key (player_perspectives_slug) references player_perspective;
-alter table DETECTED_GAME_PLAYER_PERSPECTIVES
- add constraint player_perspectives_detected_game_slug foreign key (detected_game_slug) references detected_game;
-
--- Game <-> Videos
-create table DETECTED_GAME_VIDEO_IDS
-(
- detected_game_slug varchar(255) not null,
- video_ids varchar(255)
-);
-alter table DETECTED_GAME_VIDEO_IDS
- add constraint video_ids_detected_game_slug foreign key (detected_game_slug) references detected_game;
-
--- Game <-> Screenshots
-create table DETECTED_GAME_SCREENSHOT_IDS
-(
- detected_game_slug varchar(255) not null,
- screenshot_ids varchar(255)
-);
-alter table DETECTED_GAME_SCREENSHOT_IDS
- add constraint screenshot_ids_detected_game_slug foreign key (detected_game_slug) references detected_game;
diff --git a/backend/src/main/resources/db/migration/V1_1_0__Add_Field_addedToLibrary_to_DetectedGame.sql b/backend/src/main/resources/db/migration/V1_1_0__Add_Field_addedToLibrary_to_DetectedGame.sql
deleted file mode 100644
index e75fe44..0000000
--- a/backend/src/main/resources/db/migration/V1_1_0__Add_Field_addedToLibrary_to_DetectedGame.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- Add field "addedToLibrary" to the "DetectedGame" table with the default value of CURRENT_TIMESTAMP()
-
-alter table DETECTED_GAME
-add added_to_library timestamp not null default CURRENT_TIMESTAMP()
\ No newline at end of file
diff --git a/backend/src/main/resources/db/migration/V1_3_0__Add_Platforms_to_Detected_Game.sql b/backend/src/main/resources/db/migration/V1_3_0__Add_Platforms_to_Detected_Game.sql
deleted file mode 100644
index 6b6b67c..0000000
--- a/backend/src/main/resources/db/migration/V1_3_0__Add_Platforms_to_Detected_Game.sql
+++ /dev/null
@@ -1,45 +0,0 @@
--- Add platforms
-
--- Platforms
-CREATE TABLE platform
-(
- slug VARCHAR(255) NOT NULL,
- name VARCHAR(255),
- logo_id VARCHAR(255),
- PRIMARY KEY (slug)
-);
-
--- Game <-> Platforms
-CREATE TABLE detected_game_platforms
-(
- detected_game_slug VARCHAR(255) NOT NULL,
- platforms_slug VARCHAR(255) NOT NULL
-);
-ALTER TABLE detected_game_platforms
- ADD CONSTRAINT platforms_platform_slug FOREIGN KEY (platforms_slug) REFERENCES platform;
-ALTER TABLE detected_game_platforms
- ADD CONSTRAINT platforms_detected_game_slug FOREIGN KEY (detected_game_slug) REFERENCES detected_game;
-
--- Add libraries
-
--- Libraries
-CREATE TABLE library
-(
- path VARCHAR(255) NOT NULL,
- PRIMARY KEY (path)
-);
-
--- Library <-> Platforms
-CREATE TABLE library_platforms
-(
- library_path VARCHAR(255) NOT NULL,
- platforms_slug VARCHAR(255) NOT NULL
-);
-ALTER TABLE library_platforms
- ADD CONSTRAINT libraries_platform_slug FOREIGN KEY (platforms_slug) REFERENCES platform;
-ALTER TABLE library_platforms
- ADD CONSTRAINT libraries_library_path FOREIGN KEY (library_path) REFERENCES library;
-
--- Library <-> Game
-ALTER TABLE detected_game
- ADD library VARCHAR(255);
\ No newline at end of file
diff --git a/backend/src/main/resources/proto/igdbapi.proto b/backend/src/main/resources/proto/igdbapi.proto
deleted file mode 100644
index 1fea700..0000000
--- a/backend/src/main/resources/proto/igdbapi.proto
+++ /dev/null
@@ -1,1173 +0,0 @@
-syntax = "proto3";
-
-package com.igdb.proto;
-
-import "google/protobuf/timestamp.proto";
-
-option java_outer_classname = "Igdb"; // Needed in order to be able to differentiate between Gameyfin and IGDB types in code (e.g. "Game" = Gameyfin, "Igdb.Game" = IGDB)
-//option java_multiple_files = true; // Disabled because it clashes with java_outer_classname
-option optimize_for = CODE_SIZE;
-
-message Count {
- int64 count = 1;
-}
-
-message MultiQueryResult {
- string name = 1;
- repeated bytes results = 2;
- int64 count = 3;
-}
-
-message MultiQueryResultArray {
- repeated MultiQueryResult result = 1;
-}
-
-message AgeRatingResult {
- repeated AgeRating ageratings = 1;
-}
-
-message AgeRating {
- uint64 id = 1;
- AgeRatingCategoryEnum category = 2;
- repeated AgeRatingContentDescription content_descriptions = 3;
- AgeRatingRatingEnum rating = 4;
- string rating_cover_url = 5;
- string synopsis = 6;
- string checksum = 7;
-}
-
-
-enum AgeRatingCategoryEnum {
- AGERATING_CATEGORY_NULL = 0;
- ESRB = 1;
- PEGI = 2;
- CERO = 3;
- USK = 4;
- GRAC = 5;
- CLASS_IND = 6;
- ACB = 7;
-}
-
-
-enum AgeRatingRatingEnum {
- AGERATING_RATING_NULL = 0;
- THREE = 1;
- SEVEN = 2;
- TWELVE = 3;
- SIXTEEN = 4;
- EIGHTEEN = 5;
- RP = 6;
- EC = 7;
- E = 8;
- E10 = 9;
- T = 10;
- M = 11;
- AO = 12;
- CERO_A = 13;
- CERO_B = 14;
- CERO_C = 15;
- CERO_D = 16;
- CERO_Z = 17;
- USK_0 = 18;
- USK_6 = 19;
- USK_12 = 20;
- USK_16 = 21;
- USK_18 = 22;
- GRAC_ALL = 23;
- GRAC_TWELVE = 24;
- GRAC_FIFTEEN = 25;
- GRAC_EIGHTEEN = 26;
- GRAC_TESTING = 27;
- CLASS_IND_L = 28;
- CLASS_IND_TEN = 29;
- CLASS_IND_TWELVE = 30;
- CLASS_IND_FOURTEEN = 31;
- CLASS_IND_SIXTEEN = 32;
- CLASS_IND_EIGHTEEN = 33;
- ACB_G = 34;
- ACB_PG = 35;
- ACB_M = 36;
- ACB_MA15 = 37;
- ACB_R18 = 38;
- ACB_RC = 39;
-}
-
-message AgeRatingContentDescriptionResult {
- repeated AgeRatingContentDescription ageratingcontentdescriptions = 1;
-}
-
-message AgeRatingContentDescription {
- uint64 id = 1;
- AgeRatingContentDescriptionCategoryEnum category = 2;
- string description = 3;
- string checksum = 4;
-}
-
-
-enum AgeRatingContentDescriptionCategoryEnum {
- AGERATINGCONTENTDESCRIPTION_CATEGORY_NULL = 0;
- ESRB_ALCOHOL_REFERENCE = 1;
- ESRB_ANIMATED_BLOOD = 2;
- ESRB_BLOOD = 3;
- ESRB_BLOOD_AND_GORE = 4;
- ESRB_CARTOON_VIOLENCE = 5;
- ESRB_COMIC_MISCHIEF = 6;
- ESRB_CRUDE_HUMOR = 7;
- ESRB_DRUG_REFERENCE = 8;
- ESRB_FANTASY_VIOLENCE = 9;
- ESRB_INTENSE_VIOLENCE = 10;
- ESRB_LANGUAGE = 11;
- ESRB_LYRICS = 12;
- ESRB_MATURE_HUMOR = 13;
- ESRB_NUDITY = 14;
- ESRB_PARTIAL_NUDITY = 15;
- ESRB_REAL_GAMBLING = 16;
- ESRB_SEXUAL_CONTENT = 17;
- ESRB_SEXUAL_THEMES = 18;
- ESRB_SEXUAL_VIOLENCE = 19;
- ESRB_SIMULATED_GAMBLING = 20;
- ESRB_STRONG_LANGUAGE = 21;
- ESRB_STRONG_LYRICS = 22;
- ESRB_STRONG_SEXUAL_CONTENT = 23;
- ESRB_SUGGESTIVE_THEMES = 24;
- ESRB_TOBACCO_REFERENCE = 25;
- ESRB_USE_OF_ALCOHOL = 26;
- ESRB_USE_OF_DRUGS = 27;
- ESRB_USE_OF_TOBACCO = 28;
- ESRB_VIOLENCE = 29;
- ESRB_VIOLENT_REFERENCES = 30;
- ESRB_ANIMATED_VIOLENCE = 31;
- ESRB_MILD_LANGUAGE = 32;
- ESRB_MILD_VIOLENCE = 33;
- ESRB_USE_OF_DRUGS_AND_ALCOHOL = 34;
- ESRB_DRUG_AND_ALCOHOL_REFERENCE = 35;
- ESRB_MILD_SUGGESTIVE_THEMES = 36;
- ESRB_MILD_CARTOON_VIOLENCE = 37;
- ESRB_MILD_BLOOD = 38;
- ESRB_REALISTIC_BLOOD_AND_GORE = 39;
- ESRB_REALISTIC_VIOLENCE = 40;
- ESRB_ALCOHOL_AND_TOBACCO_REFERENCE = 41;
- ESRB_MATURE_SEXUAL_THEMES = 42;
- ESRB_MILD_ANIMATED_VIOLENCE = 43;
- ESRB_MILD_SEXUAL_THEMES = 44;
- ESRB_USE_OF_ALCOHOL_AND_TOBACCO = 45;
- ESRB_ANIMATED_BLOOD_AND_GORE = 46;
- ESRB_MILD_FANTASY_VIOLENCE = 47;
- ESRB_MILD_LYRICS = 48;
- ESRB_REALISTIC_BLOOD = 49;
- PEGI_VIOLENCE = 50;
- PEGI_SEX = 51;
- PEGI_DRUGS = 52;
- PEGI_FEAR = 53;
- PEGI_DISCRIMINATION = 54;
- PEGI_BAD_LANGUAGE = 55;
- PEGI_GAMBLING = 56;
- PEGI_ONLINE_GAMEPLAY = 57;
- PEGI_IN_GAME_PURCHASES = 58;
- CERO_LOVE = 59;
- CERO_SEXUAL_CONTENT = 60;
- CERO_VIOLENCE = 61;
- CERO_HORROR = 62;
- CERO_DRINKING_SMOKING = 63;
- CERO_GAMBLING = 64;
- CERO_CRIME = 65;
- CERO_CONTROLLED_SUBSTANCES = 66;
- CERO_LANGUAGES_AND_OTHERS = 67;
- GRAC_SEXUALITY = 68;
- GRAC_VIOLENCE = 69;
- GRAC_FEAR_HORROR_THREATENING = 70;
- GRAC_LANGUAGE = 71;
- GRAC_ALCOHOL_TOBACCO_DRUG = 72;
- GRAC_CRIME_ANTI_SOCIAL = 73;
- GRAC_GAMBLING = 74;
- CLASS_IND_VIOLENCIA = 75;
- CLASS_IND_VIOLENCIA_EXTREMA = 76;
- CLASS_IND_CONTEUDO_SEXUAL = 77;
- CLASS_IND_NUDEZ = 78;
- CLASS_IND_SEXO = 79;
- CLASS_IND_SEXO_EXPLICITO = 80;
- CLASS_IND_DROGAS = 81;
- CLASS_IND_DROGAS_LICITAS = 82;
- CLASS_IND_DROGAS_ILICITAS = 83;
- CLASS_IND_LINGUAGEM_IMPROPRIA = 84;
- CLASS_IND_ATOS_CRIMINOSOS = 85;
-}
-
-message AlternativeNameResult {
- repeated AlternativeName alternativenames = 1;
-}
-
-message AlternativeName {
- uint64 id = 1;
- string comment = 2;
- Game game = 3;
- string name = 4;
- string checksum = 5;
-}
-
-message ArtworkResult {
- repeated Artwork artworks = 1;
-}
-
-message Artwork {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- Game game = 4;
- int32 height = 5;
- string image_id = 6;
- string url = 7;
- int32 width = 8;
- string checksum = 9;
-}
-
-message CharacterResult {
- repeated Character characters = 1;
-}
-
-message Character {
- uint64 id = 1;
- repeated string akas = 2;
- string country_name = 3;
- google.protobuf.Timestamp created_at = 4;
- string description = 5;
- repeated Game games = 6;
- GenderGenderEnum gender = 7;
- CharacterMugShot mug_shot = 8;
- string name = 9;
- string slug = 10;
- CharacterSpeciesEnum species = 11;
- google.protobuf.Timestamp updated_at = 12;
- string url = 13;
- string checksum = 14;
-}
-
-
-enum GenderGenderEnum {
- MALE = 0;
- FEMALE = 1;
- OTHER = 2;
-}
-
-
-enum CharacterSpeciesEnum {
- CHARACTER_SPECIES_NULL = 0;
- HUMAN = 1;
- ALIEN = 2;
- ANIMAL = 3;
- ANDROID = 4;
- UNKNOWN = 5;
-}
-
-message CharacterMugShotResult {
- repeated CharacterMugShot charactermugshots = 1;
-}
-
-message CharacterMugShot {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- int32 height = 4;
- string image_id = 5;
- string url = 6;
- int32 width = 7;
- string checksum = 8;
-}
-
-message CollectionResult {
- repeated Collection collections = 1;
-}
-
-message Collection {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- repeated Game games = 3;
- string name = 4;
- string slug = 5;
- google.protobuf.Timestamp updated_at = 6;
- string url = 7;
- string checksum = 8;
-}
-
-message CompanyResult {
- repeated Company companies = 1;
-}
-
-message Company {
- uint64 id = 1;
- google.protobuf.Timestamp change_date = 2;
- DateFormatChangeDateCategoryEnum change_date_category = 3;
- Company changed_company_id = 4;
- int32 country = 5;
- google.protobuf.Timestamp created_at = 6;
- string description = 7;
- repeated Game developed = 8;
- CompanyLogo logo = 9;
- string name = 10;
- Company parent = 11;
- repeated Game published = 12;
- string slug = 13;
- google.protobuf.Timestamp start_date = 14;
- DateFormatChangeDateCategoryEnum start_date_category = 15;
- google.protobuf.Timestamp updated_at = 16;
- string url = 17;
- repeated CompanyWebsite websites = 18;
- string checksum = 19;
-}
-
-
-enum DateFormatChangeDateCategoryEnum {
- YYYYMMMMDD = 0;
- YYYYMMMM = 1;
- YYYY = 2;
- YYYYQ1 = 3;
- YYYYQ2 = 4;
- YYYYQ3 = 5;
- YYYYQ4 = 6;
- TBD = 7;
-}
-
-message CompanyLogoResult {
- repeated CompanyLogo companylogos = 1;
-}
-
-message CompanyLogo {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- int32 height = 4;
- string image_id = 5;
- string url = 6;
- int32 width = 7;
- string checksum = 8;
-}
-
-message CompanyWebsiteResult {
- repeated CompanyWebsite companywebsites = 1;
-}
-
-message CompanyWebsite {
- uint64 id = 1;
- WebsiteCategoryEnum category = 2;
- bool trusted = 3;
- string url = 4;
- string checksum = 5;
-}
-
-
-enum WebsiteCategoryEnum {
- WEBSITE_CATEGORY_NULL = 0;
- WEBSITE_OFFICIAL = 1;
- WEBSITE_WIKIA = 2;
- WEBSITE_WIKIPEDIA = 3;
- WEBSITE_FACEBOOK = 4;
- WEBSITE_TWITTER = 5;
- WEBSITE_TWITCH = 6;
- WEBSITE_INSTAGRAM = 8;
- WEBSITE_YOUTUBE = 9;
- WEBSITE_IPHONE = 10;
- WEBSITE_IPAD = 11;
- WEBSITE_ANDROID = 12;
- WEBSITE_STEAM = 13;
- WEBSITE_REDDIT = 14;
- WEBSITE_ITCH = 15;
- WEBSITE_EPICGAMES = 16;
- WEBSITE_GOG = 17;
- WEBSITE_DISCORD = 18;
-}
-
-message CoverResult {
- repeated Cover covers = 1;
-}
-
-message Cover {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- Game game = 4;
- int32 height = 5;
- string image_id = 6;
- string url = 7;
- int32 width = 8;
- string checksum = 9;
- GameLocalization game_localization = 10;
-}
-
-message EventResult {
- repeated Event events = 1;
-}
-
-message Event {
- uint64 id = 1;
- string name = 2;
- string description = 3;
- string slug = 4;
- EventLogo event_logo = 5;
- repeated EventEventCategoriesEnum event_categories = 6;
- google.protobuf.Timestamp start_time = 7;
- string time_zone = 8;
- google.protobuf.Timestamp end_time = 9;
- string live_stream_url = 10;
- repeated Game games = 11;
- repeated GameVideo videos = 12;
- repeated EventNetwork event_networks = 13;
- google.protobuf.Timestamp created_at = 14;
- google.protobuf.Timestamp updated_at = 15;
- string checksum = 16;
-}
-
-
-enum EventEventCategoriesEnum {
- EVENT_EVENT_CATEGORIES_NULL = 0;
- INDUSTRY_CONFERENCE = 1;
- STREAM_EVENT = 2;
- AWARD = 3;
- GAME_EXPO = 4;
- E_SPORT = 5;
- OTHER_MISC = 6;
-}
-
-message EventLogoResult {
- repeated EventLogo eventlogos = 1;
-}
-
-message EventLogo {
- uint64 id = 1;
- Event event = 2;
- bool alpha_channel = 3;
- bool animated = 4;
- int32 height = 5;
- string image_id = 6;
- string url = 7;
- int32 width = 8;
- google.protobuf.Timestamp created_at = 9;
- google.protobuf.Timestamp updated_at = 10;
- string checksum = 11;
-}
-
-message EventNetworkResult {
- repeated EventNetwork eventnetworks = 1;
-}
-
-message EventNetwork {
- uint64 id = 1;
- Event event = 2;
- string url = 3;
- NetworkType network_type = 4;
- google.protobuf.Timestamp created_at = 5;
- google.protobuf.Timestamp updated_at = 6;
- string checksum = 7;
-}
-
-message ExternalGameResult {
- repeated ExternalGame externalgames = 1;
-}
-
-message ExternalGame {
- uint64 id = 1;
- ExternalGameCategoryEnum category = 2;
- google.protobuf.Timestamp created_at = 3;
- Game game = 4;
- string name = 5;
- string uid = 6;
- google.protobuf.Timestamp updated_at = 7;
- string url = 8;
- int32 year = 9;
- ExternalGameMediaEnum media = 10;
- Platform platform = 11;
- repeated int32 countries = 12;
- string checksum = 13;
-}
-
-
-enum ExternalGameCategoryEnum {
- EXTERNALGAME_CATEGORY_NULL = 0;
- EXTERNALGAME_STEAM = 1;
- EXTERNALGAME_GOG = 5;
- EXTERNALGAME_YOUTUBE = 10;
- EXTERNALGAME_MICROSOFT = 11;
- EXTERNALGAME_APPLE = 13;
- EXTERNALGAME_TWITCH = 14;
- EXTERNALGAME_ANDROID = 15;
- EXTERNALGAME_AMAZON_ASIN = 20;
- EXTERNALGAME_AMAZON_LUNA = 22;
- EXTERNALGAME_AMAZON_ADG = 23;
- EXTERNALGAME_EPIC_GAME_STORE = 26;
- EXTERNALGAME_OCULUS = 28;
- EXTERNALGAME_UTOMIK = 29;
- EXTERNALGAME_ITCH_IO = 30;
- EXTERNALGAME_XBOX_MARKETPLACE = 31;
- EXTERNALGAME_KARTRIDGE = 32;
- EXTERNALGAME_PLAYSTATION_STORE_US = 36;
- EXTERNALGAME_FOCUS_ENTERTAINMENT = 37;
- EXTERNALGAME_XBOX_GAME_PASS_ULTIMATE_CLOUD = 54;
- EXTERNALGAME_GAMEJOLT = 55;
-}
-
-
-enum ExternalGameMediaEnum {
- EXTERNALGAME_MEDIA_NULL = 0;
- EXTERNALGAME_DIGITAL = 1;
- EXTERNALGAME_PHYSICAL = 2;
-}
-
-message FranchiseResult {
- repeated Franchise franchises = 1;
-}
-
-message Franchise {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- repeated Game games = 3;
- string name = 4;
- string slug = 5;
- google.protobuf.Timestamp updated_at = 6;
- string url = 7;
- string checksum = 8;
-}
-
-message GameResult {
- repeated Game games = 1;
-}
-
-message Game {
- uint64 id = 1;
- repeated AgeRating age_ratings = 2;
- double aggregated_rating = 3;
- int32 aggregated_rating_count = 4;
- repeated AlternativeName alternative_names = 5;
- repeated Artwork artworks = 6;
- repeated Game bundles = 7;
- GameCategoryEnum category = 8;
- Collection collection = 9;
- Cover cover = 10;
- google.protobuf.Timestamp created_at = 11;
- repeated Game dlcs = 12;
- repeated Game expansions = 13;
- repeated ExternalGame external_games = 14;
- google.protobuf.Timestamp first_release_date = 15;
- int32 follows = 16;
- Franchise franchise = 17;
- repeated Franchise franchises = 18;
- repeated GameEngine game_engines = 19;
- repeated GameMode game_modes = 20;
- repeated Genre genres = 21;
- int32 hypes = 22;
- repeated InvolvedCompany involved_companies = 23;
- repeated Keyword keywords = 24;
- repeated MultiplayerMode multiplayer_modes = 25;
- string name = 26;
- Game parent_game = 27;
- repeated Platform platforms = 28;
- repeated PlayerPerspective player_perspectives = 29;
- double rating = 30;
- int32 rating_count = 31;
- repeated ReleaseDate release_dates = 32;
- repeated Screenshot screenshots = 33;
- repeated Game similar_games = 34;
- string slug = 35;
- repeated Game standalone_expansions = 36;
- GameStatusEnum status = 37;
- string storyline = 38;
- string summary = 39;
- repeated int32 tags = 40;
- repeated Theme themes = 41;
- double total_rating = 42;
- int32 total_rating_count = 43;
- google.protobuf.Timestamp updated_at = 44;
- string url = 45;
- Game version_parent = 46;
- string version_title = 47;
- repeated GameVideo videos = 48;
- repeated Website websites = 49;
- string checksum = 50;
- repeated Game remakes = 51;
- repeated Game remasters = 52;
- repeated Game expanded_games = 53;
- repeated Game ports = 54;
- repeated Game forks = 55;
- repeated LanguageSupport language_supports = 56;
- repeated GameLocalization game_localizations = 57;
-}
-
-
-enum GameCategoryEnum {
- MAIN_GAME = 0;
- DLC_ADDON = 1;
- EXPANSION = 2;
- BUNDLE = 3;
- STANDALONE_EXPANSION = 4;
- MOD = 5;
- EPISODE = 6;
- SEASON = 7;
- REMAKE = 8;
- REMASTER = 9;
- EXPANDED_GAME = 10;
- PORT = 11;
- FORK = 12;
- PACK = 13;
- UPDATE = 14;
-}
-
-
-enum GameStatusEnum {
- RELEASED = 0;
- ALPHA = 2;
- BETA = 3;
- EARLY_ACCESS = 4;
- OFFLINE = 5;
- CANCELLED = 6;
- RUMORED = 7;
- DELISTED = 8;
-}
-
-message GameEngineResult {
- repeated GameEngine gameengines = 1;
-}
-
-message GameEngine {
- uint64 id = 1;
- repeated Company companies = 2;
- google.protobuf.Timestamp created_at = 3;
- string description = 4;
- GameEngineLogo logo = 5;
- string name = 6;
- repeated Platform platforms = 7;
- string slug = 8;
- google.protobuf.Timestamp updated_at = 9;
- string url = 10;
- string checksum = 11;
-}
-
-message GameEngineLogoResult {
- repeated GameEngineLogo gameenginelogos = 1;
-}
-
-message GameEngineLogo {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- int32 height = 4;
- string image_id = 5;
- string url = 6;
- int32 width = 7;
- string checksum = 8;
-}
-
-message GameLocalizationResult {
- repeated GameLocalization gamelocalizations = 1;
-}
-
-message GameLocalization {
- uint64 id = 1;
- string name = 2;
- Cover cover = 3;
- Game game = 4;
- Region region = 5;
- google.protobuf.Timestamp created_at = 6;
- google.protobuf.Timestamp updated_at = 7;
- string checksum = 8;
-}
-
-message GameModeResult {
- repeated GameMode gamemodes = 1;
-}
-
-message GameMode {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- string name = 3;
- string slug = 4;
- google.protobuf.Timestamp updated_at = 5;
- string url = 6;
- string checksum = 7;
-}
-
-message GameVersionResult {
- repeated GameVersion gameversions = 1;
-}
-
-message GameVersion {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- repeated GameVersionFeature features = 3;
- Game game = 4;
- repeated Game games = 5;
- google.protobuf.Timestamp updated_at = 6;
- string url = 7;
- string checksum = 8;
-}
-
-message GameVersionFeatureResult {
- repeated GameVersionFeature gameversionfeatures = 1;
-}
-
-message GameVersionFeature {
- uint64 id = 1;
- GameVersionFeatureCategoryEnum category = 2;
- string description = 3;
- int32 position = 4;
- string title = 5;
- repeated GameVersionFeatureValue values = 6;
- string checksum = 7;
-}
-
-
-enum GameVersionFeatureCategoryEnum {
- BOOLEAN = 0;
- DESCRIPTION = 1;
-}
-
-message GameVersionFeatureValueResult {
- repeated GameVersionFeatureValue gameversionfeaturevalues = 1;
-}
-
-message GameVersionFeatureValue {
- uint64 id = 1;
- Game game = 2;
- GameVersionFeature game_feature = 3;
- GameVersionFeatureValueIncludedFeatureEnum included_feature = 4;
- string note = 5;
- string checksum = 6;
-}
-
-
-enum GameVersionFeatureValueIncludedFeatureEnum {
- NOT_INCLUDED = 0;
- INCLUDED = 1;
- PRE_ORDER_ONLY = 2;
-}
-
-message GameVideoResult {
- repeated GameVideo gamevideos = 1;
-}
-
-message GameVideo {
- uint64 id = 1;
- Game game = 2;
- string name = 3;
- string video_id = 4;
- string checksum = 5;
-}
-
-message GenreResult {
- repeated Genre genres = 1;
-}
-
-message Genre {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- string name = 3;
- string slug = 4;
- google.protobuf.Timestamp updated_at = 5;
- string url = 6;
- string checksum = 7;
-}
-
-message InvolvedCompanyResult {
- repeated InvolvedCompany involvedcompanies = 1;
-}
-
-message InvolvedCompany {
- uint64 id = 1;
- Company company = 2;
- google.protobuf.Timestamp created_at = 3;
- bool developer = 4;
- Game game = 5;
- bool porting = 6;
- bool publisher = 7;
- bool supporting = 8;
- google.protobuf.Timestamp updated_at = 9;
- string checksum = 10;
-}
-
-message KeywordResult {
- repeated Keyword keywords = 1;
-}
-
-message Keyword {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- string name = 3;
- string slug = 4;
- google.protobuf.Timestamp updated_at = 5;
- string url = 6;
- string checksum = 7;
-}
-
-message LanguageResult {
- repeated Language languages = 1;
-}
-
-message Language {
- uint64 id = 1;
- string name = 2;
- string native_name = 3;
- string locale = 4;
- google.protobuf.Timestamp created_at = 5;
- google.protobuf.Timestamp updated_at = 6;
- string checksum = 7;
-}
-
-message LanguageSupportResult {
- repeated LanguageSupport languagesupports = 1;
-}
-
-message LanguageSupport {
- uint64 id = 1;
- Game game = 2;
- Language language = 3;
- LanguageSupportType language_support_type = 4;
- google.protobuf.Timestamp created_at = 5;
- google.protobuf.Timestamp updated_at = 6;
- string checksum = 7;
-}
-
-message LanguageSupportTypeResult {
- repeated LanguageSupportType languagesupporttypes = 1;
-}
-
-message LanguageSupportType {
- uint64 id = 1;
- string name = 2;
- google.protobuf.Timestamp created_at = 3;
- google.protobuf.Timestamp updated_at = 4;
- string checksum = 5;
-}
-
-message MultiplayerModeResult {
- repeated MultiplayerMode multiplayermodes = 1;
-}
-
-message MultiplayerMode {
- uint64 id = 1;
- bool campaigncoop = 2;
- bool dropin = 3;
- Game game = 4;
- bool lancoop = 5;
- bool offlinecoop = 6;
- int32 offlinecoopmax = 7;
- int32 offlinemax = 8;
- bool onlinecoop = 9;
- int32 onlinecoopmax = 10;
- int32 onlinemax = 11;
- Platform platform = 12;
- bool splitscreen = 13;
- bool splitscreenonline = 14;
- string checksum = 15;
-}
-
-message NetworkTypeResult {
- repeated NetworkType networktypes = 1;
-}
-
-message NetworkType {
- uint64 id = 1;
- string name = 2;
- repeated EventNetwork event_networks = 3;
- google.protobuf.Timestamp created_at = 4;
- google.protobuf.Timestamp updated_at = 5;
- string checksum = 6;
-}
-
-message PlatformResult {
- repeated Platform platforms = 1;
-}
-
-message Platform {
- uint64 id = 1;
- string abbreviation = 2;
- string alternative_name = 3;
- PlatformCategoryEnum category = 4;
- google.protobuf.Timestamp created_at = 5;
- int32 generation = 6;
- string name = 7;
- PlatformLogo platform_logo = 8;
- PlatformFamily platform_family = 9;
- string slug = 10;
- string summary = 11;
- google.protobuf.Timestamp updated_at = 12;
- string url = 13;
- repeated PlatformVersion versions = 14;
- repeated PlatformWebsite websites = 15;
- string checksum = 16;
-}
-
-
-enum PlatformCategoryEnum {
- PLATFORM_CATEGORY_NULL = 0;
- CONSOLE = 1;
- ARCADE = 2;
- PLATFORM = 3;
- OPERATING_SYSTEM = 4;
- PORTABLE_CONSOLE = 5;
- COMPUTER = 6;
-}
-
-message PlatformFamilyResult {
- repeated PlatformFamily platformfamilies = 1;
-}
-
-message PlatformFamily {
- uint64 id = 1;
- string name = 2;
- string slug = 3;
- string checksum = 4;
-}
-
-message PlatformLogoResult {
- repeated PlatformLogo platformlogos = 1;
-}
-
-message PlatformLogo {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- int32 height = 4;
- string image_id = 5;
- string url = 6;
- int32 width = 7;
- string checksum = 8;
-}
-
-message PlatformVersionResult {
- repeated PlatformVersion platformversions = 1;
-}
-
-message PlatformVersion {
- uint64 id = 1;
- repeated PlatformVersionCompany companies = 2;
- string connectivity = 3;
- string cpu = 4;
- string graphics = 5;
- PlatformVersionCompany main_manufacturer = 6;
- string media = 7;
- string memory = 8;
- string name = 9;
- string online = 10;
- string os = 11;
- string output = 12;
- PlatformLogo platform_logo = 13;
- repeated PlatformVersionReleaseDate platform_version_release_dates = 14;
- string resolutions = 15;
- string slug = 16;
- string sound = 17;
- string storage = 18;
- string summary = 19;
- string url = 20;
- string checksum = 21;
-}
-
-message PlatformVersionCompanyResult {
- repeated PlatformVersionCompany platformversioncompanies = 1;
-}
-
-message PlatformVersionCompany {
- uint64 id = 1;
- string comment = 2;
- Company company = 3;
- bool developer = 4;
- bool manufacturer = 5;
- string checksum = 6;
-}
-
-message PlatformVersionReleaseDateResult {
- repeated PlatformVersionReleaseDate platformversionreleasedates = 1;
-}
-
-message PlatformVersionReleaseDate {
- uint64 id = 1;
- DateFormatChangeDateCategoryEnum category = 2;
- google.protobuf.Timestamp created_at = 3;
- google.protobuf.Timestamp date = 4;
- string human = 5;
- int32 m = 6;
- PlatformVersion platform_version = 7;
- RegionRegionEnum region = 8;
- google.protobuf.Timestamp updated_at = 9;
- int32 y = 10;
- string checksum = 11;
-}
-
-
-enum RegionRegionEnum {
- REGION_REGION_NULL = 0;
- EUROPE = 1;
- NORTH_AMERICA = 2;
- AUSTRALIA = 3;
- NEW_ZEALAND = 4;
- JAPAN = 5;
- CHINA = 6;
- ASIA = 7;
- WORLDWIDE = 8;
- KOREA = 9;
- BRAZIL = 10;
-}
-
-message PlatformWebsiteResult {
- repeated PlatformWebsite platformwebsites = 1;
-}
-
-message PlatformWebsite {
- uint64 id = 1;
- WebsiteCategoryEnum category = 2;
- bool trusted = 3;
- string url = 4;
- string checksum = 5;
-}
-
-message PlayerPerspectiveResult {
- repeated PlayerPerspective playerperspectives = 1;
-}
-
-message PlayerPerspective {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- string name = 3;
- string slug = 4;
- google.protobuf.Timestamp updated_at = 5;
- string url = 6;
- string checksum = 7;
-}
-
-message RegionResult {
- repeated Region regions = 1;
-}
-
-message Region {
- uint64 id = 1;
- string name = 2;
- string category = 3;
- string identifier = 4;
- google.protobuf.Timestamp created_at = 5;
- google.protobuf.Timestamp updated_at = 6;
- string checksum = 7;
-}
-
-message ReleaseDateResult {
- repeated ReleaseDate releasedates = 1;
-}
-
-message ReleaseDate {
- uint64 id = 1;
- DateFormatChangeDateCategoryEnum category = 2;
- google.protobuf.Timestamp created_at = 3;
- google.protobuf.Timestamp date = 4;
- Game game = 5;
- string human = 6;
- int32 m = 7;
- Platform platform = 8;
- RegionRegionEnum region = 9;
- google.protobuf.Timestamp updated_at = 10;
- int32 y = 11;
- string checksum = 12;
- ReleaseDateStatus status = 13;
-}
-
-message ReleaseDateStatusResult {
- repeated ReleaseDateStatus releasedatestatuses = 1;
-}
-
-message ReleaseDateStatus {
- uint64 id = 1;
- string name = 2;
- string description = 3;
- google.protobuf.Timestamp created_at = 4;
- google.protobuf.Timestamp updated_at = 5;
- string checksum = 6;
-}
-
-message ScreenshotResult {
- repeated Screenshot screenshots = 1;
-}
-
-message Screenshot {
- uint64 id = 1;
- bool alpha_channel = 2;
- bool animated = 3;
- Game game = 4;
- int32 height = 5;
- string image_id = 6;
- string url = 7;
- int32 width = 8;
- string checksum = 9;
-}
-
-message SearchResult {
- repeated Search searches = 1;
-}
-
-message Search {
- uint64 id = 1;
- string alternative_name = 2;
- Character character = 3;
- Collection collection = 4;
- Company company = 5;
- string description = 6;
- Game game = 7;
- string name = 8;
- Platform platform = 9;
- google.protobuf.Timestamp published_at = 10;
- TestDummy test_dummy = 11;
- Theme theme = 12;
- string checksum = 13;
-}
-
-message TestDummyResult {
- repeated TestDummy testdummies = 1;
-}
-
-message TestDummy {
- uint64 id = 1;
- bool bool_value = 2;
- google.protobuf.Timestamp created_at = 3;
- TestDummyEnumTestEnum enum_test = 4;
- double float_value = 5;
- Game game = 6;
- repeated int32 integer_array = 7;
- int32 integer_value = 8;
- string name = 9;
- int32 new_integer_value = 10;
- bool private = 11;
- string slug = 12;
- repeated string string_array = 13;
- repeated TestDummy test_dummies = 14;
- TestDummy test_dummy = 15;
- google.protobuf.Timestamp updated_at = 16;
- string url = 17;
- string checksum = 18;
-}
-
-
-enum TestDummyEnumTestEnum {
- TESTDUMMY_ENUM_TEST_NULL = 0;
- ENUM1 = 1;
- ENUM2 = 2;
-}
-
-message ThemeResult {
- repeated Theme themes = 1;
-}
-
-message Theme {
- uint64 id = 1;
- google.protobuf.Timestamp created_at = 2;
- string name = 3;
- string slug = 4;
- google.protobuf.Timestamp updated_at = 5;
- string url = 6;
- string checksum = 7;
-}
-
-message WebsiteResult {
- repeated Website websites = 1;
-}
-
-message Website {
- uint64 id = 1;
- WebsiteCategoryEnum category = 2;
- Game game = 3;
- bool trusted = 4;
- string url = 5;
- string checksum = 6;
-}
diff --git a/backend/src/test/java/de/grimsi/gameyfin/igdb/IgdbWrapperTest.java b/backend/src/test/java/de/grimsi/gameyfin/igdb/IgdbWrapperTest.java
deleted file mode 100644
index f0b84f1..0000000
--- a/backend/src/test/java/de/grimsi/gameyfin/igdb/IgdbWrapperTest.java
+++ /dev/null
@@ -1,373 +0,0 @@
-package de.grimsi.gameyfin.igdb;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.protobuf.Message;
-import com.google.protobuf.Timestamp;
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.config.WebClientConfig;
-import de.grimsi.gameyfin.config.properties.GameyfinProperties;
-import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
-import de.grimsi.gameyfin.entities.Platform;
-import de.grimsi.gameyfin.igdb.dto.TwitchOAuthTokenDto;
-import de.grimsi.gameyfin.mapper.GameMapper;
-import io.github.resilience4j.bulkhead.Bulkhead;
-import io.github.resilience4j.bulkhead.BulkheadConfig;
-import io.github.resilience4j.ratelimiter.RateLimiter;
-import io.github.resilience4j.ratelimiter.RateLimiterConfig;
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import okio.Buffer;
-import org.jeasy.random.EasyRandom;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mockito;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.test.util.ReflectionTestUtils;
-import org.springframework.web.reactive.function.client.WebClient;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import static de.grimsi.gameyfin.igdb.IgdbApiQueryBuilder.equal;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@ExtendWith(MockitoExtension.class)
-class IgdbWrapperTest {
-
- private static final MockWebServer igdbApiMock = new MockWebServer();
- private static final MockWebServer twitchApiMock = new MockWebServer();
- private static final EasyRandom easyRandom = new EasyRandom();
- private static final ObjectMapper objectMapper = new ObjectMapper();
-
- private static IgdbWrapper target;
-
- @BeforeAll
- static void setup() throws IOException, InterruptedException {
- WebClientConfig webClientConfigMock = mock(WebClientConfig.class);
- GameMapper gameMapperMock = mock(GameMapper.class);
- GameyfinProperties gameyfinPropertiesMock = mock(GameyfinProperties.class, Mockito.RETURNS_DEEP_STUBS);
-
- target = new IgdbWrapper(WebClient.builder(), webClientConfigMock, gameMapperMock, gameyfinPropertiesMock);
-
- igdbApiMock.start();
- twitchApiMock.start();
-
- ReflectionTestUtils.setField(target, "clientId", "client_id_value");
- ReflectionTestUtils.setField(target, "clientSecret", "client_secret_value");
- ReflectionTestUtils.setField(target, "igdbApiBaseUrl", "http://localhost:%s".formatted(igdbApiMock.getPort()));
- ReflectionTestUtils.setField(target, "twitchAuthUrl", "http://localhost:%s/oauth2/token".formatted(twitchApiMock.getPort()));
-
- when(gameyfinPropertiesMock.igdb().config().preferredPlatforms()).thenReturn(List.of(6));
-
- when(webClientConfigMock.getIgdbConcurrencyLimiter()).thenReturn(Bulkhead.of("test_bulkhead", BulkheadConfig.ofDefaults()));
- when(webClientConfigMock.getIgdbRateLimiter()).thenReturn(RateLimiter.of("test_ratelimiter", RateLimiterConfig.ofDefaults()));
-
- TwitchOAuthTokenDto mockToken = easyRandom.nextObject(TwitchOAuthTokenDto.class);
-
- twitchApiMock.enqueue(new MockResponse()
- .setBody(objectMapper.writeValueAsString(mockToken))
- .addHeader("Content-Type", "application/json"));
-
- target.init();
-
- RecordedRequest r = twitchApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/oauth2/token");
- assertThat(r.getRequestUrl().queryParameter("client_id")).isEqualTo("client_id_value");
- assertThat(r.getRequestUrl().queryParameter("client_secret")).isEqualTo("client_secret_value");
- assertThat(r.getRequestUrl().queryParameter("grant_type")).isEqualTo("client_credentials");
-
- twitchApiMock.shutdown();
- }
-
- @AfterAll
- static void tearDown() throws IOException {
- igdbApiMock.shutdown();
- }
-
- @Test
- void getGameById() throws InterruptedException {
- //Igdb.GameResult gameResult = easyRandom.nextObject(Igdb.GameResult.class);
- Igdb.GameResult gameResult = Igdb.GameResult.newBuilder()
- .addAllGames(List.of(
- Igdb.Game.newBuilder().setId(easyRandom.nextLong()).build(),
- Igdb.Game.newBuilder().setId(easyRandom.nextLong()).build(),
- Igdb.Game.newBuilder().setId(easyRandom.nextLong()).build()))
- .build();
-
- Long gameId = gameResult.getGames(0).getId();
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(gameResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- Optional gameOptional = target.getGameById(gameId);
-
- assertThat(gameOptional).isPresent();
-
- Igdb.Game game = gameOptional.get();
-
- assertThat(game.getId()).isEqualTo(gameId);
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String expectedQuery = "fields %s;limit 1;where id = %d;".formatted(IgdbApiProperties.GAME_QUERY_FIELDS_STRING, gameId);
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- @Test
- void getGameBySlug() throws InterruptedException {
- Igdb.GameResult gameResult = Igdb.GameResult.newBuilder()
- .addAllGames(List.of(
- Igdb.Game.newBuilder().setSlug("game_slug_1").build(),
- Igdb.Game.newBuilder().setSlug("game_slug_2").build(),
- Igdb.Game.newBuilder().setSlug("game_slug_3").build()))
- .build();
-
- String gameSlug = gameResult.getGames(0).getSlug();
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(gameResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- Optional gameOptional = target.getGameBySlug("game_slug_1");
-
- assertThat(gameOptional).isPresent();
-
- Igdb.Game game = gameOptional.get();
-
- assertThat(game.getSlug()).isEqualTo(gameSlug);
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String expectedQuery = "fields %s;limit 1;where slug = \"%s\";".formatted(IgdbApiProperties.GAME_QUERY_FIELDS_STRING, gameSlug);
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- @Test
- void findPossibleMatchingTitles() throws InterruptedException {
- Igdb.GameResult gameResult = Igdb.GameResult.newBuilder()
- .addAllGames(List.of(
- Igdb.Game.newBuilder().setName("title_1").build(),
- Igdb.Game.newBuilder().setName("title_2").build(),
- Igdb.Game.newBuilder().setName("title_3").build()))
- .build();
-
- String gameTitle = gameResult.getGames(0).getName();
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(gameResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- List suggestions = target.findPossibleMatchingTitles(gameTitle, gameResult.getGamesCount());
-
- assertThat(suggestions).hasSize(gameResult.getGamesCount());
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String expectedQuery = "search \"%s\";fields slug,name,first_release_date,platforms.name;limit %d;where platforms = (6);".formatted(gameTitle, gameResult.getGamesCount());
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- @Test
- void searchForGameByTitle_exactMatch() throws InterruptedException {
- Igdb.GameResult gameResult = Igdb.GameResult.newBuilder()
- .addAllGames(List.of(
- Igdb.Game.newBuilder().setName("title_1").build(),
- Igdb.Game.newBuilder().setName("title_2").build(),
- Igdb.Game.newBuilder().setName("title_3").build()))
- .build();
-
- String searchTerm = gameResult.getGames(0).getName();
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(gameResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- Optional gameOptional = target.searchForGameByTitle(searchTerm);
-
- assertThat(gameOptional).isPresent();
-
- Igdb.Game game = gameOptional.get();
-
- assertThat(game.getName()).isEqualTo(searchTerm);
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String expectedQuery = "search \"%s\";fields %s;where platforms = (6);".formatted(searchTerm, IgdbApiProperties.GAME_QUERY_FIELDS_STRING);
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- @Test
- void searchForGameByTitle_EndsWith() throws InterruptedException {
- Igdb.GameResult gameResult = Igdb.GameResult.newBuilder()
- .addAllGames(List.of(
- Igdb.Game.newBuilder().setName("some_prefix title_1").build(),
- Igdb.Game.newBuilder().setName("title_2)").build(),
- Igdb.Game.newBuilder().setName("title_3").build()))
- .build();
-
- String searchTerm = "title_1";
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(gameResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- Optional gameOptional = target.searchForGameByTitle(searchTerm);
-
- assertThat(gameOptional).isPresent();
-
- Igdb.Game game = gameOptional.get();
-
- assertThat(game.getName()).isEqualTo(gameResult.getGames(0).getName());
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String expectedQuery = "search \"%s\";fields %s;where platforms = (6);".formatted(searchTerm, IgdbApiProperties.GAME_QUERY_FIELDS_STRING);
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- @Test
- void searchForGameByTitle_Brackets() throws InterruptedException {
- Igdb.GameResult gameResult = Igdb.GameResult.newBuilder()
- .addAllGames(List.of(
- Igdb.Game.newBuilder().setName("title_1").build(),
- Igdb.Game.newBuilder().setName("title_2").build(),
- Igdb.Game.newBuilder().setName("title_3").build()))
- .build();
-
- String searchTerm = gameResult.getGames(0).getName() + " (Text in brackets should be ignored)";
-
- // First request should result in an empty response
- igdbApiMock.enqueue(new MockResponse().setHeader("Content-Type", "application/protobuf"));
-
- // Second request should contain the same query, but with brackets removed
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(gameResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- Optional gameOptional = target.searchForGameByTitle(searchTerm);
-
- assertThat(gameOptional).isPresent();
-
- Igdb.Game game = gameOptional.get();
-
- // Result should be game with title equal to search term with brackets removed
- assertThat(game.getName()).isEqualTo(gameResult.getGames(0).getName());
-
- // First query (should contain brackets)
- RecordedRequest r1 = igdbApiMock.takeRequest();
- assertThat(r1.getRequestUrl()).isNotNull();
- assertThat(r1.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String r1_expectedQuery = "search \"%s\";fields %s;where platforms = (6);".formatted(searchTerm, IgdbApiProperties.GAME_QUERY_FIELDS_STRING);
-
- assertThat(r1.getBody().readUtf8()).isEqualTo(r1_expectedQuery);
-
- // Second query (should not contain brackets)
- RecordedRequest r2 = igdbApiMock.takeRequest();
- assertThat(r2.getRequestUrl()).isNotNull();
- assertThat(r2.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_GAMES_PROTOBUF));
-
- String r2_expectedQuery = "search \"%s\";fields %s;where platforms = (6);".formatted(gameResult.getGames(0).getName(), IgdbApiProperties.GAME_QUERY_FIELDS_STRING);
-
- assertThat(r2.getBody().readUtf8()).isEqualTo(r2_expectedQuery);
- }
-
- @Test
- void findPlatforms() throws InterruptedException {
- Igdb.PlatformResult platformResult = Igdb.PlatformResult.newBuilder()
- .addAllPlatforms(List.of(
- Igdb.Platform.newBuilder().setSlug("platform_1").setName("Platform 1").build(),
- Igdb.Platform.newBuilder().setSlug("platform_2").setName("Platform 2").build(),
- Igdb.Platform.newBuilder().setSlug("platform_3").setName("Platform 3").build()))
- .build();
-
- String searchTerm = platformResult.getPlatforms(0).getSlug();
- int limit = 10;
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(platformResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- List result = target.findPlatforms(searchTerm, limit);
-
- assertThat(result.get(0).getSlug()).isEqualTo(searchTerm);
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_PLATFORMS_PROTOBUF));
-
- String expectedQuery = "search \"%s\";fields slug,name;limit %s;".formatted(searchTerm, limit);
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- @Test
- void getPlatformBySlug() throws InterruptedException {
- Igdb.PlatformResult platformResult = Igdb.PlatformResult.newBuilder()
- .addAllPlatforms(List.of(
- Igdb.Platform.newBuilder().setSlug("platform_1").setName("Platform 1").build()))
- .build();
-
- String slug = platformResult.getPlatforms(0).getSlug();
-
- igdbApiMock.enqueue(new MockResponse()
- .setBody(toBuffer(platformResult))
- .setHeader("Content-Type", "application/protobuf")
- );
-
- Optional result = target.getPlatformBySlug(slug);
-
- assertThat(result).isPresent();
-
- Platform platform = result.get();
-
- assertThat(platform.getSlug()).isEqualTo(slug);
-
- RecordedRequest r = igdbApiMock.takeRequest();
- assertThat(r.getRequestUrl()).isNotNull();
- assertThat(r.getRequestUrl().encodedPath()).isEqualTo("/%s".formatted(IgdbApiProperties.ENDPOINT_PLATFORMS_PROTOBUF));
-
- String expectedQuery = "fields slug,name,platform_logo;where slug = \"%s\";".formatted(slug);
-
- assertThat(r.getBody().readUtf8()).isEqualTo(expectedQuery);
- }
-
- private static Buffer toBuffer(Message input) {
- Buffer b = new Buffer();
- b.write(input.toByteArray());
- return b;
- }
-}
diff --git a/backend/src/test/java/de/grimsi/gameyfin/mapper/CompanyMapperTest.java b/backend/src/test/java/de/grimsi/gameyfin/mapper/CompanyMapperTest.java
deleted file mode 100644
index ea5bcaf..0000000
--- a/backend/src/test/java/de/grimsi/gameyfin/mapper/CompanyMapperTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Company;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-import java.util.UUID;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class CompanyMapperTest extends RandomMapperTest {
-
- @Test
- void toCompany() {
- Igdb.InvolvedCompany input = generateRandomInput();
-
- Company output = CompanyMapper.toCompany(input);
-
- assertThat(output.getSlug()).isEqualTo(input.getCompany().getSlug());
- assertThat(output.getName()).isEqualTo(input.getCompany().getName());
- assertThat(output.getLogoId()).isEqualTo(input.getCompany().getLogo().getImageId());
- }
-
- @Test
- void toCompanies() {
- List input = List.of(generateRandomInput(), generateRandomInput(), generateRandomInput());
-
- List output = CompanyMapper.toCompanies(input);
-
- for (int i = 0; i < output.size(); i++) {
- assertThat(output.get(i).getSlug()).isEqualTo(input.get(i).getCompany().getSlug());
- assertThat(output.get(i).getName()).isEqualTo(input.get(i).getCompany().getName());
- assertThat(output.get(i).getLogoId()).isEqualTo(input.get(i).getCompany().getLogo().getImageId());
- }
- }
-}
diff --git a/backend/src/test/java/de/grimsi/gameyfin/mapper/GenreMapperTest.java b/backend/src/test/java/de/grimsi/gameyfin/mapper/GenreMapperTest.java
deleted file mode 100644
index 22a79e5..0000000
--- a/backend/src/test/java/de/grimsi/gameyfin/mapper/GenreMapperTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.igdb.proto.Igdb;
-import de.grimsi.gameyfin.entities.Genre;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-class GenreMapperTest extends RandomMapperTest {
-
- @Test
- void toGenre() {
- Igdb.Genre input = generateRandomInput();
-
- Genre output = GenreMapper.toGenre(input);
-
- assertThat(output.getSlug()).isEqualTo(input.getSlug());
- assertThat(output.getName()).isEqualTo(input.getName());
- }
-
- @Test
- void toGenres() {
- List input = List.of(generateRandomInput(), generateRandomInput(), generateRandomInput());
-
- List output = GenreMapper.toGenres(input);
-
- for (int i = 0; i < output.size(); i++) {
- assertThat(output.get(i).getSlug()).isEqualTo(input.get(i).getSlug());
- assertThat(output.get(i).getName()).isEqualTo(input.get(i).getName());
- }
- }
-}
diff --git a/backend/src/test/java/de/grimsi/gameyfin/mapper/RandomMapperTest.java b/backend/src/test/java/de/grimsi/gameyfin/mapper/RandomMapperTest.java
deleted file mode 100644
index fba494b..0000000
--- a/backend/src/test/java/de/grimsi/gameyfin/mapper/RandomMapperTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package de.grimsi.gameyfin.mapper;
-
-import com.google.protobuf.Message;
-import org.jeasy.random.EasyRandom;
-import org.jeasy.random.EasyRandomParameters;
-import org.springframework.core.GenericTypeResolver;
-
-import java.util.List;
-
-public class RandomMapperTest {
-
- private static final int DEFAULT_COUNT = 5;
- protected final EasyRandom easyRandom = new EasyRandom();
-
- private final Class inputClass;
- private final Class
-
diff --git a/frontend/src/app/components/game-cover/game-cover.component.scss b/frontend/src/app/components/game-cover/game-cover.component.scss
deleted file mode 100644
index f7831d0..0000000
--- a/frontend/src/app/components/game-cover/game-cover.component.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-@use '@angular/material' as mat;
-
-.game-cover-container {
-
- height: 352px;
- width: 264px;
- background-repeat: no-repeat;
- background-position: center bottom;
- @include mat.elevation(4);
-}
-
-.enlarge {
- transition: transform 280ms ease-out;
-
- &:hover,
- &:focus {
- transform: scale(1.05);
- }
-}
-
-.shine {
- position: relative;
- overflow: hidden;
-
- &::before {
- background: linear-gradient(
- to right,
- fade_out(#fff, 1) 0%,
- fade_out(#fff, 0.7) 100%
- );
- content: "";
- display: block;
- height: 100%;
- left: -100%;
- position: absolute;
- top: 0;
- transform: skewX(-25deg);
- width: 50%;
- z-index: 2;
- }
-
- &:hover,
- &:focus {
- &::before {
- animation: shine 0.85s;
- }
- }
-
- @keyframes shine {
- 100% {
- left: 125%;
- }
- }
-}
-
-.no-link-styling a:hover, a:visited, a:link, a:active {
- text-decoration: none;
- color: black;
-}
diff --git a/frontend/src/app/components/game-cover/game-cover.component.spec.ts b/frontend/src/app/components/game-cover/game-cover.component.spec.ts
deleted file mode 100644
index a61e5d9..0000000
--- a/frontend/src/app/components/game-cover/game-cover.component.spec.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { GameCoverComponent } from './game-cover.component';
-
-describe('GameCoverComponent', () => {
- let component: GameCoverComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- declarations: [ GameCoverComponent ]
- })
- .compileComponents();
-
- fixture = TestBed.createComponent(GameCoverComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/frontend/src/app/components/game-cover/game-cover.component.ts b/frontend/src/app/components/game-cover/game-cover.component.ts
deleted file mode 100644
index 72b1f80..0000000
--- a/frontend/src/app/components/game-cover/game-cover.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {Component, Input, OnInit} from '@angular/core';
-import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
-
-@Component({
- selector: 'game-cover',
- templateUrl: './game-cover.component.html',
- styleUrls: ['./game-cover.component.scss']
-})
-export class GameCoverComponent implements OnInit {
-
- @Input() game!: DetectedGameDto;
-
- constructor() {
- }
-
- ngOnInit(): void {
- }
-
-}
diff --git a/frontend/src/app/components/game-detail-view/game-detail-view.component.html b/frontend/src/app/components/game-detail-view/game-detail-view.component.html
deleted file mode 100644
index 0fad110..0000000
--- a/frontend/src/app/components/game-detail-view/game-detail-view.component.html
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
-
-
-
-

-
-
-