mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
2.0.0.beta5 (#629)
* Remove unnecessary "requiresLogin" handles * Rename property in UserInfoDto * Fix #628 * Fix ant matchers (again) * Major performance improvements for game matching * Minor logging improvements
This commit is contained in:
@@ -8,6 +8,7 @@ import {PaperPlaneRight, Pencil} from "@phosphor-icons/react";
|
|||||||
import MessageTemplateDto from "Frontend/generated/org/gameyfin/app/messages/templates/MessageTemplateDto";
|
import MessageTemplateDto from "Frontend/generated/org/gameyfin/app/messages/templates/MessageTemplateDto";
|
||||||
import SendTestNotificationModal from "Frontend/components/administration/messages/SendTestNotificationModal";
|
import SendTestNotificationModal from "Frontend/components/administration/messages/SendTestNotificationModal";
|
||||||
import EditTemplateModal from "Frontend/components/administration/messages/EditTemplateModel";
|
import EditTemplateModal from "Frontend/components/administration/messages/EditTemplateModel";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
function MessageManagementLayout({getConfig, formik}: any) {
|
function MessageManagementLayout({getConfig, formik}: any) {
|
||||||
|
|
||||||
@@ -126,4 +127,21 @@ function MessageManagementLayout({getConfig, formik}: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageManagement = withConfigPage(MessageManagementLayout, "Messages", "messages");
|
const validationSchema = Yup.object({
|
||||||
|
messages: Yup.object({
|
||||||
|
providers: Yup.object({
|
||||||
|
email: Yup.object({
|
||||||
|
enabled: Yup.boolean().required("Required"),
|
||||||
|
host: Yup.string().required("Host is required"),
|
||||||
|
port: Yup.number().required("Port is required")
|
||||||
|
.min(0, "Port must be between 0 and 65535")
|
||||||
|
.max(65535, "Port must be between 0 and 65535"),
|
||||||
|
username: Yup.string()
|
||||||
|
.email("Invalid email address")
|
||||||
|
.required("Username is required"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MessageManagement = withConfigPage(MessageManagementLayout, "Messages", validationSchema);
|
||||||
@@ -23,20 +23,18 @@ import SearchView from "Frontend/views/SearchView";
|
|||||||
import RecentlyAddedView from "Frontend/views/RecentlyAddedView";
|
import RecentlyAddedView from "Frontend/views/RecentlyAddedView";
|
||||||
import LibraryView from "Frontend/views/LibraryView";
|
import LibraryView from "Frontend/views/LibraryView";
|
||||||
import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js";
|
import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js";
|
||||||
import {ConfigEndpoint} from "Frontend/generated/endpoints";
|
|
||||||
|
|
||||||
export const {router, routes} = new RouterConfigurationBuilder()
|
export const {router, routes} = new RouterConfigurationBuilder()
|
||||||
.withReactRoutes([
|
.withReactRoutes([
|
||||||
{
|
{
|
||||||
element: <App/>,
|
element: <App/>,
|
||||||
handle: {requiresLogin: false},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
element: <MainLayout/>,
|
element: <MainLayout/>,
|
||||||
handle: {requiresLogin: !ConfigEndpoint.isPublicAccessEnabled()},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true, element: <HomeView/>
|
index: true,
|
||||||
|
element: <HomeView/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'search',
|
path: 'search',
|
||||||
@@ -65,7 +63,6 @@ export const {router, routes} = new RouterConfigurationBuilder()
|
|||||||
{
|
{
|
||||||
path: 'administration',
|
path: 'administration',
|
||||||
element: <AdministrationView/>,
|
element: <AdministrationView/>,
|
||||||
handle: {requiresLogin: true},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'libraries',
|
path: 'libraries',
|
||||||
@@ -86,19 +83,19 @@ export const {router, routes} = new RouterConfigurationBuilder()
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'login', element: <LoginView/>, handle: {requiresLogin: false}
|
path: 'login', element: <LoginView/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'setup', element: <SetupView/>, handle: {requiresLogin: false}
|
path: 'setup', element: <SetupView/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'accept-invitation', element: <InvitationRegistrationView/>, handle: {requiresLogin: false}
|
path: 'accept-invitation', element: <InvitationRegistrationView/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'reset-password', element: <PasswordResetView/>, handle: {requiresLogin: false}
|
path: 'reset-password', element: <PasswordResetView/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'confirm-email', element: <EmailConfirmationView/>, handle: {requiresLogin: true}
|
path: 'confirm-email', element: <EmailConfirmationView/>
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class SecurityConfig(
|
|||||||
.requestMatchers("/reset-password").permitAll()
|
.requestMatchers("/reset-password").permitAll()
|
||||||
.requestMatchers("/accept-invitation").permitAll()
|
.requestMatchers("/accept-invitation").permitAll()
|
||||||
.requestMatchers("/public/**").permitAll()
|
.requestMatchers("/public/**").permitAll()
|
||||||
|
.requestMatchers("/images/**").permitAll()
|
||||||
|
|
||||||
// Dynamic public access for certain endpoints
|
// Dynamic public access for certain endpoints
|
||||||
auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config))
|
auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config))
|
||||||
@@ -51,8 +52,6 @@ class SecurityConfig(
|
|||||||
.requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config))
|
.requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||||
.requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config))
|
.requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||||
.requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config))
|
.requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||||
.requestMatchers("/images/**").access(DynamicPublicAccessAuthorizationManager(config))
|
|
||||||
.requestMatchers("/images/**").access(DynamicPublicAccessAuthorizationManager(config))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.sessionManagement { sessionManagement ->
|
http.sessionManagement { sessionManagement ->
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import java.net.URI
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.Future
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.toJavaDuration
|
import kotlin.time.toJavaDuration
|
||||||
import org.gameyfin.pluginapi.gamemetadata.GameMetadata as PluginApiMetadata
|
import org.gameyfin.pluginapi.gamemetadata.GameMetadata as PluginApiMetadata
|
||||||
@@ -71,6 +73,8 @@ class GameService(
|
|||||||
fun emit(event: GameEvent) {
|
fun emit(event: GameEvent) {
|
||||||
gameEvents.tryEmitNext(event)
|
gameEvents.tryEmitNext(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val executor = Executors.newVirtualThreadPerTaskExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val metadataPlugins: List<GameMetadataProvider>
|
private val metadataPlugins: List<GameMetadataProvider>
|
||||||
@@ -237,15 +241,18 @@ class GameService(
|
|||||||
|
|
||||||
fun getPotentialMatches(searchTerm: String): List<GameSearchResultDto> {
|
fun getPotentialMatches(searchTerm: String): List<GameSearchResultDto> {
|
||||||
// 1. Query all plugins for up to 10 results each
|
// 1. Query all plugins for up to 10 results each
|
||||||
val results = metadataPlugins.flatMap { plugin ->
|
val futures: List<Future<List<Pair<GameMetadataProvider, PluginApiMetadata>>>> = metadataPlugins.map { plugin ->
|
||||||
|
executor.submit<List<Pair<GameMetadataProvider, PluginApiMetadata>>> {
|
||||||
try {
|
try {
|
||||||
plugin.fetchByTitle(searchTerm, 10)
|
plugin.fetchByTitle(searchTerm, 10).map { plugin to it }
|
||||||
.map { plugin to it }
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error(e) { "Error fetching metadata for game with plugin ${plugin.javaClass.name}" }
|
log.error(e) { "Error fetching metadata for searchterm '$searchTerm' with plugin ${plugin.javaClass.name}" }
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
val results = futures.flatMap { it.get() }
|
||||||
|
|
||||||
val providerToManagementEntry =
|
val providerToManagementEntry =
|
||||||
results.toMap().entries.associate { it.key to pluginService.getPluginManagementEntry(it.key.javaClass) }
|
results.toMap().entries.associate { it.key to pluginService.getPluginManagementEntry(it.key.javaClass) }
|
||||||
|
|
||||||
@@ -423,20 +430,17 @@ class GameService(
|
|||||||
* @return A map of metadata plugins and their respective results
|
* @return A map of metadata plugins and their respective results
|
||||||
*/
|
*/
|
||||||
private fun queryPlugins(gameTitle: String): Map<GameMetadataProvider, PluginApiMetadata?> {
|
private fun queryPlugins(gameTitle: String): Map<GameMetadataProvider, PluginApiMetadata?> {
|
||||||
return runBlocking {
|
val futures = metadataPlugins.associateWith { plugin ->
|
||||||
coroutineScope {
|
executor.submit<PluginApiMetadata?> {
|
||||||
metadataPlugins.associateWith {
|
|
||||||
async {
|
|
||||||
try {
|
try {
|
||||||
it.fetchByTitle(gameTitle).firstOrNull()
|
plugin.fetchByTitle(gameTitle).firstOrNull()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
log.error(e) { "Error fetching metadata for game with plugin ${it.javaClass.name}" }
|
log.error { "Error fetching metadata for game title '$gameTitle' with plugin ${plugin.javaClass.name}" }
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}.await()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return futures.mapValues { it.value.get() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ class UserService(
|
|||||||
username = user.username,
|
username = user.username,
|
||||||
email = user.email,
|
email = user.email,
|
||||||
emailConfirmed = user.emailConfirmed,
|
emailConfirmed = user.emailConfirmed,
|
||||||
isEnabled = user.enabled,
|
enabled = user.enabled,
|
||||||
hasAvatar = user.avatar != null,
|
hasAvatar = user.avatar != null,
|
||||||
avatarId = user.avatar?.id,
|
avatarId = user.avatar?.id,
|
||||||
managedBySso = user.oidcProviderId != null,
|
managedBySso = user.oidcProviderId != null,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ data class UserInfoDto(
|
|||||||
val managedBySso: Boolean,
|
val managedBySso: Boolean,
|
||||||
val email: String,
|
val email: String,
|
||||||
val emailConfirmed: Boolean,
|
val emailConfirmed: Boolean,
|
||||||
val isEnabled: Boolean,
|
val enabled: Boolean,
|
||||||
val hasAvatar: Boolean,
|
val hasAvatar: Boolean,
|
||||||
val avatarId: Long? = null,
|
val avatarId: Long? = null,
|
||||||
var roles: List<Role>
|
var roles: List<Role>
|
||||||
|
|||||||
+8
-2
@@ -1,5 +1,8 @@
|
|||||||
package org.gameyfin.plugins.metadata.steamgriddb
|
package org.gameyfin.plugins.metadata.steamgriddb
|
||||||
|
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.gameyfin.pluginapi.core.config.*
|
import org.gameyfin.pluginapi.core.config.*
|
||||||
import org.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
|
import org.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
|
||||||
@@ -81,8 +84,9 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
|
|||||||
override fun fetchByTitle(gameTitle: String, maxResults: Int): List<GameMetadata> {
|
override fun fetchByTitle(gameTitle: String, maxResults: Int): List<GameMetadata> {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
val results = searchSteamGridDb(gameTitle)
|
val results = searchSteamGridDb(gameTitle)
|
||||||
|
coroutineScope {
|
||||||
results.map { game ->
|
results.map { game ->
|
||||||
|
async {
|
||||||
val grids = getGridsForGame(game.id)
|
val grids = getGridsForGame(game.id)
|
||||||
val heroes = getHeroesForGame(game.id)
|
val heroes = getHeroesForGame(game.id)
|
||||||
GameMetadata(
|
GameMetadata(
|
||||||
@@ -92,7 +96,9 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
|
|||||||
coverUrls = grids?.map { URI(it.url) },
|
coverUrls = grids?.map { URI(it.url) },
|
||||||
headerUrls = heroes?.map { URI(it.url) }
|
headerUrls = heroes?.map { URI(it.url) }
|
||||||
)
|
)
|
||||||
}.take(maxResults)
|
}
|
||||||
|
}.awaitAll().take(maxResults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user