From 6b81d3904c0d3334fc15369918563d1853c0b28e Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Sun, 31 Aug 2025 15:56:56 +0200
Subject: [PATCH 01/32] Start implementation of game requests
---
.../core/plugins/dto/ExternalProviderIdDto.kt | 10 ++++
.../app/core/security/SecurityUtils.kt | 7 ++-
.../org/gameyfin/app/games/GameEndpoint.kt | 13 ++++-
.../org/gameyfin/app/games/GameService.kt | 18 +++---
.../app/games/dto/GameSearchResultDto.kt | 12 +---
.../org/gameyfin/app/media/ImageEndpoint.kt | 7 +--
.../gameyfin/app/messages/MessageService.kt | 5 +-
.../org/gameyfin/app/requests/GameRequest.kt | 42 ++++++++++++++
.../app/requests/GameRequestRepository.kt | 5 ++
.../app/requests/GameRequestService.kt | 57 +++++++++++++++++++
.../requests/dto/GameRequestCreationDto.kt | 10 ++++
.../app/requests/dto/GameRequestDto.kt | 13 +++++
.../app/requests/dto/GameRequestEvent.kt | 9 +++
.../extensions/GameRequestExtensions.kt | 15 +++++
.../app/requests/status/GameRequestStatus.kt | 8 +++
.../org/gameyfin/app/setup/SetupEndpoint.kt | 4 +-
.../org/gameyfin/app/setup/SetupService.kt | 6 +-
.../org/gameyfin/app/users/SessionService.kt | 14 ++---
.../org/gameyfin/app/users/UserEndpoint.kt | 16 +++---
.../org/gameyfin/app/users/UserService.kt | 18 +++---
.../{UserInfoDto.kt => UserInfoAdminDto.kt} | 2 +-
.../gameyfin/app/users/dto/UserInfoUserDto.kt | 7 +++
.../EmailConfirmationEndpoint.kt | 7 +--
.../preferences/UserPreferencesService.kt | 6 +-
.../users/registration/InvitationService.kt | 12 ++--
25 files changed, 250 insertions(+), 73 deletions(-)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/ExternalProviderIdDto.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt
rename app/src/main/kotlin/org/gameyfin/app/users/dto/{UserInfoDto.kt => UserInfoAdminDto.kt} (91%)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/ExternalProviderIdDto.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/ExternalProviderIdDto.kt
new file mode 100644
index 0000000..5881d5c
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/dto/ExternalProviderIdDto.kt
@@ -0,0 +1,10 @@
+package org.gameyfin.app.core.plugins.dto
+
+class ExternalProviderIdDto(
+ val pluginId: String,
+ val externalProviderId: String,
+) {
+ override fun toString(): String {
+ return "$pluginId:$externalProviderId"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
index 1b0adf1..45fc1de 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
@@ -1,9 +1,14 @@
package org.gameyfin.app.core.security
import org.gameyfin.app.core.Role
+import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
+fun getCurrentAuth(): Authentication {
+ return SecurityContextHolder.getContext().authentication
+}
+
fun isCurrentUserAdmin(): Boolean {
- return SecurityContextHolder.getContext().authentication?.authorities?.any { it.authority == Role.Names.ADMIN || it.authority == Role.Names.SUPERADMIN }
+ return getCurrentAuth().authorities?.any { it.authority == Role.Names.ADMIN || it.authority == Role.Names.SUPERADMIN }
?: false
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
index 5ecd8dd..9f33c51 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
@@ -5,8 +5,12 @@ import com.vaadin.hilla.Endpoint
import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.annotations.DynamicPublicAccess
+import org.gameyfin.app.core.plugins.dto.ExternalProviderIdDto
import org.gameyfin.app.core.security.isCurrentUserAdmin
-import org.gameyfin.app.games.dto.*
+import org.gameyfin.app.games.dto.GameDto
+import org.gameyfin.app.games.dto.GameEvent
+import org.gameyfin.app.games.dto.GameSearchResultDto
+import org.gameyfin.app.games.dto.GameUpdateDto
import org.gameyfin.app.libraries.LibraryCoreService
import org.gameyfin.app.libraries.LibraryService
import reactor.core.publisher.Flux
@@ -45,7 +49,12 @@ class GameEndpoint(
}
@RolesAllowed(Role.Names.ADMIN)
- fun matchManually(originalIds: Map, path: String, libraryId: Long, replaceGameId: Long?) {
+ fun matchManually(
+ originalIds: Map,
+ path: String,
+ libraryId: Long,
+ replaceGameId: Long?
+ ) {
val library = libraryService.getById(libraryId)
val game = gameService.matchManually(originalIds, Path.of(path), library, replaceGameId)
if (game != null) {
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
index 751082e..5d42048 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
@@ -12,10 +12,12 @@ import org.gameyfin.app.core.alphaNumeric
import org.gameyfin.app.core.filesystem.FilesystemService
import org.gameyfin.app.core.filterValuesNotNull
import org.gameyfin.app.core.plugins.PluginService
+import org.gameyfin.app.core.plugins.dto.ExternalProviderIdDto
import org.gameyfin.app.core.plugins.management.GameyfinPluginDescriptor
import org.gameyfin.app.core.plugins.management.GameyfinPluginManager
import org.gameyfin.app.core.plugins.management.PluginManagementEntry
import org.gameyfin.app.core.replaceRomanNumerals
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.games.dto.*
import org.gameyfin.app.games.entities.*
import org.gameyfin.app.games.extensions.toDtos
@@ -25,7 +27,6 @@ import org.gameyfin.app.media.ImageService
import org.gameyfin.app.users.UserService
import org.gameyfin.pluginapi.gamemetadata.*
import org.springframework.data.repository.findByIdOrNull
-import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Service
@@ -149,8 +150,7 @@ class GameService(
val existingGame = gameRepository.findByIdOrNull(gameUpdateDto.id)
?: throw IllegalArgumentException("Game with ID $gameUpdateDto.id not found")
- val userDetails = SecurityContextHolder.getContext().authentication.principal
- val user = when (userDetails) {
+ val user = when (val userDetails = getCurrentAuth().principal) {
is UserDetails -> userService.getByUsernameNonNull(userDetails.username)
is OidcUser -> userService.getByUsernameNonNull(userDetails.preferredUsername)
else -> throw IllegalStateException("Unkown user type: ${userDetails::class.java.name}")
@@ -260,12 +260,12 @@ class GameService(
val game = getById(game.id!!)
- val originalIds: Map = game.metadata.originalIds
+ val originalIds: Map = game.metadata.originalIds
.map { (provider, originalId) ->
val providerId = pluginManager.getExtensions(provider.pluginId).first()?.javaClass?.name ?: return null
val pluginId = provider.pluginId
val originalId = originalId
- providerId to OriginalIdDto(pluginId, originalId)
+ providerId to ExternalProviderIdDto(pluginId, originalId)
}
.toMap()
@@ -504,12 +504,12 @@ class GameService(
sorted.mapNotNull { selector(it.second) }.firstOrNull { it.isNotEmpty() }
// Collect originalIds for this group
- val originalIds: Map = group
+ val originalIds: Map = group
.mapNotNull { (provider, metadata) ->
val providerId = provider.javaClass.name
val pluginId = providerToManagementEntry[provider]?.pluginId ?: return@mapNotNull null
val originalId = metadata.originalId
- if (providerId != null) providerId to OriginalIdDto(pluginId, originalId) else null
+ if (providerId != null) providerId to ExternalProviderIdDto(pluginId, originalId) else null
}
.toMap()
@@ -567,7 +567,7 @@ class GameService(
}
fun matchManually(
- originalIds: Map,
+ originalIds: Map,
path: Path,
library: Library,
replaceGameId: Long? = null,
@@ -578,7 +578,7 @@ class GameService(
coroutineScope {
metadataPlugins.associateWith { plugin ->
async {
- val originalId = originalIds[plugin.javaClass.name]?.originalId ?: return@async null
+ val originalId = originalIds[plugin.javaClass.name]?.externalProviderId ?: return@async null
try {
return@async plugin.fetchById(originalId)
} catch (e: Exception) {
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameSearchResultDto.kt b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameSearchResultDto.kt
index 45f98c5..4af2da0 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameSearchResultDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameSearchResultDto.kt
@@ -1,5 +1,6 @@
package org.gameyfin.app.games.dto
+import org.gameyfin.app.core.plugins.dto.ExternalProviderIdDto
import java.time.Instant
import java.util.*
@@ -11,18 +12,9 @@ class GameSearchResultDto(
val release: Instant?,
val publishers: Collection?,
val developers: Collection?,
- val originalIds: Map
+ val originalIds: Map
)
-class OriginalIdDto(
- val pluginId: String,
- val originalId: String,
-) {
- override fun toString(): String {
- return "$pluginId:$originalId"
- }
-}
-
class UrlWithSourceDto(
val url: String,
val pluginId: String
diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
index 0d8b74b..725df8a 100644
--- a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
@@ -7,6 +7,7 @@ import org.gameyfin.app.core.Role
import org.gameyfin.app.core.Utils
import org.gameyfin.app.core.annotations.DynamicPublicAccess
import org.gameyfin.app.core.plugins.PluginService
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.games.entities.Image
import org.gameyfin.app.games.entities.ImageType
import org.gameyfin.app.users.UserService
@@ -15,8 +16,6 @@ import org.springframework.core.io.InputStreamResource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
-import org.springframework.security.core.Authentication
-import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
@@ -61,7 +60,7 @@ class ImageEndpoint(
@PermitAll
@PostMapping("/avatar/upload")
fun uploadAvatar(@RequestParam("file") file: MultipartFile) {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth = getCurrentAuth()
val image: Image = if (!userService.hasAvatar(auth.name)) {
imageService.createFile(ImageType.AVATAR, file.inputStream, file.contentType!!)
@@ -76,7 +75,7 @@ class ImageEndpoint(
@PermitAll
@PostMapping("/avatar/delete")
fun deleteAvatar() {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth = getCurrentAuth()
userService.deleteAvatar(auth.name)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
index 68a1605..a0e48b8 100644
--- a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
@@ -3,6 +3,7 @@ package org.gameyfin.app.messages
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.core.events.*
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.messages.providers.AbstractMessageProvider
import org.gameyfin.app.messages.templates.MessageTemplateService
import org.gameyfin.app.messages.templates.MessageTemplates
@@ -10,8 +11,6 @@ import org.gameyfin.app.users.UserService
import org.springframework.context.ApplicationContext
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
-import org.springframework.security.core.Authentication
-import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Service
import java.util.*
@@ -61,7 +60,7 @@ class MessageService(
}
try {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth = getCurrentAuth()
val user = userService.getByUsername(auth.name) ?: throw IllegalStateException("User not found")
val template = templateService.getMessageTemplate(templateKey)
sendNotification(user.email, "[Gameyfin] Test Notification", template, placeholders)
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt
new file mode 100644
index 0000000..6808044
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt
@@ -0,0 +1,42 @@
+package org.gameyfin.app.requests
+
+import jakarta.persistence.*
+import org.gameyfin.app.requests.status.GameRequestStatus
+import org.gameyfin.app.users.entities.User
+import org.hibernate.annotations.CreationTimestamp
+import org.hibernate.annotations.UpdateTimestamp
+import java.time.Instant
+
+typealias ExternalProviderIds = Map
+
+@Entity
+class GameRequest(
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ var id: Long? = null,
+
+ @CreationTimestamp
+ @Column(nullable = false, updatable = false)
+ var createdAt: Instant? = null,
+
+ @UpdateTimestamp
+ @Column(nullable = false)
+ var updatedAt: Instant? = null,
+
+ @Column(nullable = false)
+ val title: String,
+
+ @Column(nullable = false)
+ val release: Instant,
+
+ @Column(nullable = false)
+ var status: GameRequestStatus,
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ var requester: User? = null,
+
+ var voters: MutableList = mutableListOf(),
+
+ @ElementCollection
+ val externalProviderIds: ExternalProviderIds
+)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
new file mode 100644
index 0000000..20ea18b
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
@@ -0,0 +1,5 @@
+package org.gameyfin.app.requests
+
+import org.springframework.data.jpa.repository.JpaRepository
+
+interface GameRequestRepository : JpaRepository
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
new file mode 100644
index 0000000..0ea279b
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -0,0 +1,57 @@
+package org.gameyfin.app.requests
+
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.gameyfin.app.core.security.getCurrentAuth
+import org.gameyfin.app.games.dto.GameUserEvent
+import org.gameyfin.app.requests.dto.GameRequestCreationDto
+import org.gameyfin.app.requests.status.GameRequestStatus
+import org.gameyfin.app.users.UserService
+import org.springframework.stereotype.Service
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Sinks
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.toJavaDuration
+
+@Service
+class GameRequestService(
+ private val gameRequestRepository: GameRequestRepository,
+ private val userService: UserService
+) {
+
+ companion object {
+ private val log = KotlinLogging.logger {}
+
+ /* Websockets */
+ private val gameRequestEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
+
+ fun subscribe(): Flux> {
+ log.debug { "New user subscription for gameRequestEvents" }
+ return gameRequestEvents.asFlux()
+ .buffer(100.milliseconds.toJavaDuration())
+ .doOnSubscribe {
+ log.debug { "Subscriber added to gameRequestEvents [${gameRequestEvents.currentSubscriberCount()}]" }
+ }
+ .doFinally {
+ log.debug { "Subscriber removed from gameRequestEvents with signal type $it [${gameRequestEvents.currentSubscriberCount()}]" }
+ }
+ }
+
+ fun emit(event: GameUserEvent) {
+ gameRequestEvents.tryEmitNext(event)
+ }
+ }
+
+ fun createRequest(gameRequest: GameRequestCreationDto) {
+ val currentUser = userService.getByUsername(getCurrentAuth().name)
+
+ val gameRequest = GameRequest(
+ title = gameRequest.title,
+ release = gameRequest.release,
+ status = GameRequestStatus.PENDING,
+ externalProviderIds = gameRequest.externalProviderIds,
+ requester = currentUser
+ )
+
+ gameRequestRepository.save(gameRequest)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt
new file mode 100644
index 0000000..a31f8ed
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt
@@ -0,0 +1,10 @@
+package org.gameyfin.app.requests.dto
+
+import java.time.Instant
+
+
+class GameRequestCreationDto(
+ val title: String,
+ val release: Instant,
+ val externalProviderIds: Map
+)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
new file mode 100644
index 0000000..1a4169d
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
@@ -0,0 +1,13 @@
+package org.gameyfin.app.requests.dto
+
+import org.gameyfin.app.requests.status.GameRequestStatus
+import org.gameyfin.app.users.dto.UserInfoAdminDto
+import java.time.Instant
+
+class GameRequestDto(
+ val id: Long,
+ val title: String,
+ val release: Instant,
+ val status: GameRequestStatus,
+ val requester: UserInfoAdminDto
+)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt
new file mode 100644
index 0000000..f006d3a
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt
@@ -0,0 +1,9 @@
+package org.gameyfin.app.requests.dto
+
+sealed class GameRequestEvent {
+ abstract val type: String
+
+ data class Created(val game: GameRequestDto, override val type: String = "created") : GameRequestEvent()
+ data class Updated(val game: GameRequestDto, override val type: String = "updated") : GameRequestEvent()
+ data class Deleted(val gameRequestId: Long, override val type: String = "deleted") : GameRequestEvent()
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
new file mode 100644
index 0000000..8136b13
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
@@ -0,0 +1,15 @@
+package org.gameyfin.app.requests.extensions
+
+import org.gameyfin.app.requests.GameRequest
+import org.gameyfin.app.requests.dto.GameRequestDto
+
+fun GameRequest.toDto(): GameRequestDto {
+ return GameRequestDto(
+ id = this.id!!,
+ title = this.title,
+ release = this.release,
+ externalProviderIds = this.externalProviderIds,
+ status = this.status,
+ requester = this.requester.toDto()
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt b/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt
new file mode 100644
index 0000000..290c677
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt
@@ -0,0 +1,8 @@
+package org.gameyfin.app.requests.status
+
+enum class GameRequestStatus {
+ PENDING,
+ APPROVED,
+ FULFILLED,
+ REJECTED
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt
index f9be5a1..e020434 100644
--- a/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt
@@ -3,7 +3,7 @@ package org.gameyfin.app.setup
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
import com.vaadin.hilla.exception.EndpointException
-import org.gameyfin.app.users.dto.UserInfoDto
+import org.gameyfin.app.users.dto.UserInfoAdminDto
import org.gameyfin.app.users.dto.UserRegistrationDto
@Endpoint
@@ -16,7 +16,7 @@ class SetupEndpoint(
}
@AnonymousAllowed
- fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoDto {
+ fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoAdminDto {
if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed")
return setupService.createInitialAdminUser(superAdminRegistration)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt b/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt
index f54fd77..1a6c441 100644
--- a/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt
@@ -1,9 +1,9 @@
package org.gameyfin.app.setup
-import org.gameyfin.app.users.UserService
import org.gameyfin.app.core.Role
import org.gameyfin.app.users.RoleService
-import org.gameyfin.app.users.dto.UserInfoDto
+import org.gameyfin.app.users.UserService
+import org.gameyfin.app.users.dto.UserInfoAdminDto
import org.gameyfin.app.users.dto.UserRegistrationDto
import org.gameyfin.app.users.entities.User
import org.springframework.stereotype.Service
@@ -26,7 +26,7 @@ class SetupService(
/**
* Creates the initial user with Super-Admin permissions
*/
- fun createInitialAdminUser(registration: UserRegistrationDto): UserInfoDto {
+ fun createInitialAdminUser(registration: UserRegistrationDto): UserInfoAdminDto {
val superAdmin = User(
username = registration.username,
password = registration.password,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt b/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt
index ad5ceb1..3aa2d6c 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt
@@ -1,7 +1,7 @@
package org.gameyfin.app.users
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.users.entities.User
-import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.session.SessionInformation
import org.springframework.security.core.session.SessionRegistry
@@ -11,14 +11,12 @@ import org.springframework.stereotype.Service
class SessionService(private val sessionRegistry: SessionRegistry) {
fun logoutAllSessions() {
- val auth: Authentication? = SecurityContextHolder.getContext().authentication
- if (auth != null) {
- val sessions: List = sessionRegistry.getAllSessions(auth.principal, false)
- for (sessionInfo in sessions) {
- sessionInfo.expireNow()
- }
- SecurityContextHolder.clearContext()
+ val auth = getCurrentAuth()
+ val sessions: List = sessionRegistry.getAllSessions(auth.principal, false)
+ for (sessionInfo in sessions) {
+ sessionInfo.expireNow()
}
+ SecurityContextHolder.clearContext()
}
fun logoutAllSessions(user: User) {
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
index 1fc3774..a114da8 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
@@ -5,11 +5,11 @@ import com.vaadin.hilla.Endpoint
import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role
-import org.gameyfin.app.users.dto.UserInfoDto
+import org.gameyfin.app.core.security.getCurrentAuth
+import org.gameyfin.app.users.dto.UserInfoAdminDto
import org.gameyfin.app.users.dto.UserUpdateDto
import org.gameyfin.app.users.enums.RoleAssignmentResult
import org.springframework.security.core.Authentication
-import org.springframework.security.core.context.SecurityContextHolder
@Endpoint
@@ -18,15 +18,15 @@ class UserEndpoint(
private val roleService: RoleService
) {
@AnonymousAllowed
- fun getUserInfo(): UserInfoDto? {
- val auth = SecurityContextHolder.getContext().authentication
+ fun getUserInfo(): UserInfoAdminDto? {
+ val auth = getCurrentAuth()
if (!auth.isAuthenticated || auth.principal == "anonymousUser") return null
return userService.getUserInfo()
}
@PermitAll
fun updateUser(updates: UserUpdateDto) {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth: Authentication = getCurrentAuth()
userService.updateUser(auth.name, updates)
}
@@ -36,7 +36,7 @@ class UserEndpoint(
}
@RolesAllowed(Role.Names.ADMIN)
- fun getAllUsers(): List {
+ fun getAllUsers(): List {
return userService.getAllUsers()
}
@@ -52,7 +52,7 @@ class UserEndpoint(
@PermitAll
fun deleteUser() {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth: Authentication = getCurrentAuth()
userService.deleteUser(auth.name)
}
@@ -68,7 +68,7 @@ class UserEndpoint(
@RolesAllowed(Role.Names.ADMIN)
fun getRolesBelow(): List {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth: Authentication = getCurrentAuth()
return roleService.getRolesBelowAuth(auth).map { it.roleName }
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
index 82ec4ab..e22bcda 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
@@ -6,9 +6,10 @@ import org.gameyfin.app.config.ConfigService
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.Utils
import org.gameyfin.app.core.events.*
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.games.entities.Image
import org.gameyfin.app.media.ImageService
-import org.gameyfin.app.users.dto.UserInfoDto
+import org.gameyfin.app.users.dto.UserInfoAdminDto
import org.gameyfin.app.users.dto.UserRegistrationDto
import org.gameyfin.app.users.dto.UserUpdateDto
import org.gameyfin.app.users.emailconfirmation.EmailConfirmationService
@@ -17,7 +18,6 @@ import org.gameyfin.app.users.persistence.UserRepository
import org.springframework.context.ApplicationEventPublisher
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
-import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
@@ -77,7 +77,7 @@ class UserService(
fun findByOidcProviderId(oidcProviderId: String): org.gameyfin.app.users.entities.User? =
userRepository.findByOidcProviderId(oidcProviderId)
- fun getAllUsers(): List {
+ fun getAllUsers(): List {
return userRepository.findAll().map { u -> toUserInfo(u) }
}
@@ -93,8 +93,8 @@ class UserService(
return userRepository.findByUsername(username) ?: throw UsernameNotFoundException("Unknown user '$username'")
}
- fun getUserInfo(): UserInfoDto {
- val auth = SecurityContextHolder.getContext().authentication
+ fun getUserInfo(): UserInfoAdminDto {
+ val auth = getCurrentAuth()
val principal = auth.principal
if (principal is OidcUser) {
@@ -238,7 +238,7 @@ class UserService(
return RoleAssignmentResult.NO_ROLES_PROVIDED
}
- val currentUser = SecurityContextHolder.getContext().authentication
+ val currentUser = getCurrentAuth()
val targetUser = getByUsernameNonNull(username)
if (!canManage(targetUser)) {
@@ -266,7 +266,7 @@ class UserService(
}
fun canManage(targetUser: org.gameyfin.app.users.entities.User): Boolean {
- val currentUser = SecurityContextHolder.getContext().authentication
+ val currentUser = getCurrentAuth()
val currentUserLevel = roleService.getHighestRoleFromAuthorities(currentUser.authorities).powerLevel
val targetUserLevel = roleService.getHighestRole(targetUser.roles).powerLevel
return currentUserLevel > targetUserLevel
@@ -285,8 +285,8 @@ class UserService(
eventPublisher.publishEvent(AccountDeletedEvent(this, user, Utils.Companion.getBaseUrl()))
}
- fun toUserInfo(user: org.gameyfin.app.users.entities.User): UserInfoDto {
- return UserInfoDto(
+ fun toUserInfo(user: org.gameyfin.app.users.entities.User): UserInfoAdminDto {
+ return UserInfoAdminDto(
username = user.username,
email = user.email,
emailConfirmed = user.emailConfirmed,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoAdminDto.kt
similarity index 91%
rename from app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
rename to app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoAdminDto.kt
index 26d3d96..5a93cde 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoAdminDto.kt
@@ -2,7 +2,7 @@ package org.gameyfin.app.users.dto
import org.gameyfin.app.core.Role
-data class UserInfoDto(
+data class UserInfoAdminDto(
val username: String,
val managedBySso: Boolean,
val email: String,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt
new file mode 100644
index 0000000..5bbbfbc
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt
@@ -0,0 +1,7 @@
+package org.gameyfin.app.users.dto
+
+data class UserInfoUserDto(
+ val username: String,
+ val hasAvatar: Boolean,
+ val avatarId: Long? = null,
+)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt
index ddb1975..4a84f83 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt
@@ -1,11 +1,10 @@
package org.gameyfin.app.users.emailconfirmation
import com.vaadin.hilla.Endpoint
-import org.gameyfin.app.users.UserService
import jakarta.annotation.security.PermitAll
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.shared.token.TokenValidationResult
-import org.springframework.security.core.Authentication
-import org.springframework.security.core.context.SecurityContextHolder
+import org.gameyfin.app.users.UserService
@Endpoint
class EmailConfirmationEndpoint(
@@ -20,7 +19,7 @@ class EmailConfirmationEndpoint(
@PermitAll
fun resendEmailConfirmation() {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth = getCurrentAuth()
userService.getByUsername(auth.name)?.let {
emailConfirmationService.resendEmailConfirmation(it)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt b/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt
index 963644e..617b5c8 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt
@@ -1,8 +1,8 @@
package org.gameyfin.app.users.preferences
-import org.gameyfin.app.users.UserService
import io.github.oshai.kotlinlogging.KotlinLogging
-import org.springframework.security.core.context.SecurityContextHolder
+import org.gameyfin.app.core.security.getCurrentAuth
+import org.gameyfin.app.users.UserService
import org.springframework.stereotype.Service
import java.io.Serializable
@@ -135,7 +135,7 @@ class UserPreferencesService(
}
private fun id(key: String): UserPreferenceKey {
- val auth = SecurityContextHolder.getContext().authentication
+ val auth = getCurrentAuth()
val user = userService.getByUsernameNonNull(auth.name)
return UserPreferenceKey(key, user.id!!)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt b/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt
index b27602c..81d1974 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt
@@ -1,18 +1,17 @@
package org.gameyfin.app.users.registration
+import org.gameyfin.app.core.Utils
import org.gameyfin.app.core.events.AccountStatusChangedEvent
import org.gameyfin.app.core.events.UserInvitationEvent
+import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.shared.token.TokenDto
import org.gameyfin.app.shared.token.TokenRepository
-import org.gameyfin.app.users.UserService
-import org.gameyfin.app.core.Utils
import org.gameyfin.app.shared.token.TokenService
import org.gameyfin.app.shared.token.TokenType
+import org.gameyfin.app.users.UserService
import org.gameyfin.app.users.dto.UserRegistrationDto
import org.gameyfin.app.users.enums.UserInvitationAcceptanceResult
import org.springframework.context.ApplicationEventPublisher
-import org.springframework.security.core.Authentication
-import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Service
@Service
@@ -30,7 +29,7 @@ class InvitationService(
if (userService.existsByEmail(email))
throw IllegalStateException("User with email ${Utils.Companion.maskEmail(email)} is already registered")
- val auth: Authentication = SecurityContextHolder.getContext().authentication
+ val auth = getCurrentAuth()
val user = userService.getByUsername(auth.name) ?: throw IllegalStateException("User not found")
val payload = mapOf(EMAIL_KEY to email)
val token = super.generateWithPayload(user, payload)
@@ -45,7 +44,8 @@ class InvitationService(
}
fun acceptInvitation(secret: String, registration: UserRegistrationDto): UserInvitationAcceptanceResult {
- val invitationToken = super.get(secret, TokenType.Invitation) ?: return UserInvitationAcceptanceResult.TOKEN_INVALID
+ val invitationToken =
+ super.get(secret, TokenType.Invitation) ?: return UserInvitationAcceptanceResult.TOKEN_INVALID
val email = invitationToken.payload[EMAIL_KEY] ?: return UserInvitationAcceptanceResult.TOKEN_INVALID
if (invitationToken.expired) return UserInvitationAcceptanceResult.TOKEN_EXPIRED
From 0f2b45f5e9b75b61cca864ed4eb033e975c49993 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 1 Sep 2025 13:01:17 +0200
Subject: [PATCH 02/32] BE implementation of request system
---
app/package.json | 2 +-
.../app/core/filesystem/FilesystemService.kt | 4 +-
.../org/gameyfin/app/games/GameService.kt | 2 +-
.../org/gameyfin/app/games/entities/Game.kt | 2 +-
.../app/games/entities/GameEntityListener.kt | 12 ++---
.../app/libraries/LibraryCoreService.kt | 1 +
.../app/libraries/LibraryRepository.kt | 1 +
.../app/libraries/LibraryScanService.kt | 5 +-
.../gameyfin/app/libraries/LibraryService.kt | 2 +
.../app/libraries/dto/LibraryScanProgress.kt | 1 -
.../libraries/{ => dto}/LibraryScanResult.kt | 2 +-
.../{ => entities}/DirectoryMapping.kt | 2 +-
.../app/libraries/{ => entities}/Library.kt | 5 +-
.../entities/LibraryEntityListener.kt | 3 +-
.../libraries/extensions/LibraryExtensions.kt | 2 +-
.../app/requests/GameRequestEndpoint.kt | 47 ++++++++++++++++
.../app/requests/GameRequestRepository.kt | 1 +
.../app/requests/GameRequestService.kt | 48 +++++++++++++++--
.../app/requests/dto/GameRequestDto.kt | 9 +++-
.../requests/{ => entities}/GameRequest.kt | 25 +++++----
.../entities/GameRequestEntityListener.kt | 25 +++++++++
.../extensions/GameRequestExtensions.kt | 12 ++++-
.../org/gameyfin/app/setup/SetupEndpoint.kt | 4 +-
.../org/gameyfin/app/setup/SetupService.kt | 7 +--
.../org/gameyfin/app/users/UserEndpoint.kt | 6 +--
.../org/gameyfin/app/users/UserService.kt | 53 +++++++------------
...InfoAdminDto.kt => ExtendedUserInfoDto.kt} | 2 +-
.../{UserInfoUserDto.kt => UserInfoDto.kt} | 2 +-
.../app/users/extensions/UserExtensions.kt | 33 ++++++++++++
29 files changed, 233 insertions(+), 87 deletions(-)
rename app/src/main/kotlin/org/gameyfin/app/libraries/{ => dto}/LibraryScanResult.kt (94%)
rename app/src/main/kotlin/org/gameyfin/app/libraries/{ => entities}/DirectoryMapping.kt (84%)
rename app/src/main/kotlin/org/gameyfin/app/libraries/{ => entities}/Library.kt (90%)
rename app/src/main/kotlin/org/gameyfin/app/{games => libraries}/entities/LibraryEntityListener.kt (92%)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
rename app/src/main/kotlin/org/gameyfin/app/requests/{ => entities}/GameRequest.kt (75%)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequestEntityListener.kt
rename app/src/main/kotlin/org/gameyfin/app/users/dto/{UserInfoAdminDto.kt => ExtendedUserInfoDto.kt} (90%)
rename app/src/main/kotlin/org/gameyfin/app/users/dto/{UserInfoUserDto.kt => UserInfoDto.kt} (81%)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt
diff --git a/app/package.json b/app/package.json
index 4d7345d..59d3828 100644
--- a/app/package.json
+++ b/app/package.json
@@ -265,4 +265,4 @@
"disableUsageStatistics": true,
"hash": "962eccc3fa0735d5234901be4f9e384096113c45bec22564a53688096d62aef4"
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt b/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt
index f126d77..3d81dab 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/filesystem/FilesystemService.kt
@@ -1,10 +1,10 @@
package org.gameyfin.app.core.filesystem
-import org.gameyfin.app.config.ConfigService
import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.commons.io.FilenameUtils
import org.gameyfin.app.config.ConfigProperties
-import org.gameyfin.app.libraries.Library
+import org.gameyfin.app.config.ConfigService
+import org.gameyfin.app.libraries.entities.Library
import org.springframework.stereotype.Service
import java.io.File
import java.nio.file.FileSystems
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
index 5d42048..ba0b461 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
@@ -22,7 +22,7 @@ import org.gameyfin.app.games.dto.*
import org.gameyfin.app.games.entities.*
import org.gameyfin.app.games.extensions.toDtos
import org.gameyfin.app.games.repositories.GameRepository
-import org.gameyfin.app.libraries.Library
+import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.app.media.ImageService
import org.gameyfin.app.users.UserService
import org.gameyfin.pluginapi.gamemetadata.*
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt
index ce6991d..2cdbcdd 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt
@@ -1,7 +1,7 @@
package org.gameyfin.app.games.entities
import jakarta.persistence.*
-import org.gameyfin.app.libraries.Library
+import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.pluginapi.gamemetadata.GameFeature
import org.gameyfin.pluginapi.gamemetadata.Genre
import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
index efe500b..9a04b3e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
@@ -12,19 +12,19 @@ import org.gameyfin.app.games.extensions.toUserDto
class GameEntityListener {
@PostPersist
fun created(game: Game) {
- GameService.Companion.emitUser(GameUserEvent.Created(game.toUserDto()))
- GameService.Companion.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
+ GameService.emitUser(GameUserEvent.Created(game.toUserDto()))
+ GameService.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
}
@PostUpdate
fun updated(game: Game) {
- GameService.Companion.emitUser(GameUserEvent.Updated(game.toUserDto()))
- GameService.Companion.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
+ GameService.emitUser(GameUserEvent.Updated(game.toUserDto()))
+ GameService.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
}
@PostRemove
fun deleted(game: Game) {
- GameService.Companion.emitUser(GameUserEvent.Deleted(game.id!!))
- GameService.Companion.emitAdmin(GameAdminEvent.Deleted(game.id!!))
+ GameService.emitUser(GameUserEvent.Deleted(game.id!!))
+ GameService.emitAdmin(GameAdminEvent.Deleted(game.id!!))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryCoreService.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryCoreService.kt
index 6fc27eb..0f04c26 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryCoreService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryCoreService.kt
@@ -2,6 +2,7 @@ package org.gameyfin.app.libraries
import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.entities.Game
+import org.gameyfin.app.libraries.entities.Library
import org.springframework.stereotype.Service
import java.time.Instant
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryRepository.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryRepository.kt
index b60aefc..560f8de 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryRepository.kt
@@ -1,5 +1,6 @@
package org.gameyfin.app.libraries
+import org.gameyfin.app.libraries.entities.Library
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
index e66d23e..7ac4798 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
@@ -4,9 +4,8 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.core.filesystem.FilesystemService
import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.entities.Game
-import org.gameyfin.app.libraries.dto.LibraryScanProgress
-import org.gameyfin.app.libraries.dto.LibraryScanStatus
-import org.gameyfin.app.libraries.dto.LibraryScanStep
+import org.gameyfin.app.libraries.dto.*
+import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.app.libraries.enums.ScanType
import org.gameyfin.app.libraries.scan.*
import org.gameyfin.app.media.ImageService
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryService.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryService.kt
index f9c61f5..c359e00 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryService.kt
@@ -3,6 +3,8 @@ package org.gameyfin.app.libraries
import com.vaadin.hilla.exception.EndpointException
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.libraries.dto.*
+import org.gameyfin.app.libraries.entities.DirectoryMapping
+import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.app.libraries.enums.ScanType
import org.gameyfin.app.libraries.extensions.toDtos
import org.springframework.data.repository.findByIdOrNull
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanProgress.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanProgress.kt
index 3666cea..5e648b7 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanProgress.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanProgress.kt
@@ -1,6 +1,5 @@
package org.gameyfin.app.libraries.dto
-import org.gameyfin.app.libraries.LibraryScanResult
import org.gameyfin.app.libraries.enums.ScanType
import java.time.Instant
import java.util.*
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanResult.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanResult.kt
similarity index 94%
rename from app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanResult.kt
rename to app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanResult.kt
index 0454ea3..779b751 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanResult.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryScanResult.kt
@@ -1,4 +1,4 @@
-package org.gameyfin.app.libraries
+package org.gameyfin.app.libraries.dto
interface LibraryScanResult {
/**
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/DirectoryMapping.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/DirectoryMapping.kt
similarity index 84%
rename from app/src/main/kotlin/org/gameyfin/app/libraries/DirectoryMapping.kt
rename to app/src/main/kotlin/org/gameyfin/app/libraries/entities/DirectoryMapping.kt
index 48e0d51..627d955 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/DirectoryMapping.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/DirectoryMapping.kt
@@ -1,4 +1,4 @@
-package org.gameyfin.app.libraries
+package org.gameyfin.app.libraries.entities
import jakarta.persistence.*
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/Library.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt
similarity index 90%
rename from app/src/main/kotlin/org/gameyfin/app/libraries/Library.kt
rename to app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt
index 0b1b93d..938b877 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/Library.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/Library.kt
@@ -1,8 +1,7 @@
-package org.gameyfin.app.libraries
+package org.gameyfin.app.libraries.entities
-import org.gameyfin.app.games.entities.Game
import jakarta.persistence.*
-import org.gameyfin.app.games.entities.LibraryEntityListener
+import org.gameyfin.app.games.entities.Game
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp
import java.time.Instant
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/LibraryEntityListener.kt
similarity index 92%
rename from app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt
rename to app/src/main/kotlin/org/gameyfin/app/libraries/entities/LibraryEntityListener.kt
index c035ea2..1ec83d2 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/entities/LibraryEntityListener.kt
@@ -1,9 +1,8 @@
-package org.gameyfin.app.games.entities
+package org.gameyfin.app.libraries.entities
import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate
-import org.gameyfin.app.libraries.Library
import org.gameyfin.app.libraries.LibraryService
import org.gameyfin.app.libraries.dto.LibraryAdminEvent
import org.gameyfin.app.libraries.dto.LibraryUserEvent
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/extensions/LibraryExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/extensions/LibraryExtensions.kt
index a73f0d7..0e73176 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/extensions/LibraryExtensions.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/extensions/LibraryExtensions.kt
@@ -1,8 +1,8 @@
package org.gameyfin.app.libraries.extensions
import org.gameyfin.app.core.security.isCurrentUserAdmin
-import org.gameyfin.app.libraries.Library
import org.gameyfin.app.libraries.dto.*
+import org.gameyfin.app.libraries.entities.Library
fun Library.toDto(): LibraryDto {
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
new file mode 100644
index 0000000..4cc235a
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
@@ -0,0 +1,47 @@
+package org.gameyfin.app.requests
+
+import com.vaadin.flow.server.auth.AnonymousAllowed
+import com.vaadin.hilla.Endpoint
+import jakarta.annotation.security.PermitAll
+import jakarta.annotation.security.RolesAllowed
+import org.gameyfin.app.core.Role
+import org.gameyfin.app.core.annotations.DynamicPublicAccess
+import org.gameyfin.app.requests.dto.GameRequestCreationDto
+import org.gameyfin.app.requests.dto.GameRequestEvent
+import org.gameyfin.app.requests.status.GameRequestStatus
+import org.springframework.stereotype.Service
+import reactor.core.publisher.Flux
+
+@Endpoint
+@Service
+@DynamicPublicAccess
+@AnonymousAllowed
+class GameRequestEndpoint(
+ private val gameRequestService: GameRequestService
+) {
+
+ fun subscribe(): Flux> {
+ return GameRequestService.subscribe()
+ }
+
+ fun getAll() = gameRequestService.getAll()
+
+ fun create(gameRequest: GameRequestCreationDto) {
+ gameRequestService.createRequest(gameRequest)
+ }
+
+ @PermitAll
+ fun toggleVote(gameRequestId: Long) {
+ gameRequestService.toggleRequestVote(gameRequestId)
+ }
+
+ @RolesAllowed(Role.Names.ADMIN)
+ fun changeStatus(gameRequestId: Long, newStatus: GameRequestStatus) {
+ gameRequestService.changeRequestStatus(gameRequestId, newStatus)
+ }
+
+ @RolesAllowed(Role.Names.ADMIN)
+ fun delete(gameRequestId: Long) {
+ gameRequestService.deleteRequest(gameRequestId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
index 20ea18b..c045b4d 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
@@ -1,5 +1,6 @@
package org.gameyfin.app.requests
+import org.gameyfin.app.requests.entities.GameRequest
import org.springframework.data.jpa.repository.JpaRepository
interface GameRequestRepository : JpaRepository
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
index 0ea279b..dfef3f4 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -2,8 +2,11 @@ package org.gameyfin.app.requests
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.core.security.getCurrentAuth
-import org.gameyfin.app.games.dto.GameUserEvent
import org.gameyfin.app.requests.dto.GameRequestCreationDto
+import org.gameyfin.app.requests.dto.GameRequestDto
+import org.gameyfin.app.requests.dto.GameRequestEvent
+import org.gameyfin.app.requests.entities.GameRequest
+import org.gameyfin.app.requests.extensions.toDtos
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.UserService
import org.springframework.stereotype.Service
@@ -22,9 +25,9 @@ class GameRequestService(
private val log = KotlinLogging.logger {}
/* Websockets */
- private val gameRequestEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
+ private val gameRequestEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
- fun subscribe(): Flux> {
+ fun subscribe(): Flux> {
log.debug { "New user subscription for gameRequestEvents" }
return gameRequestEvents.asFlux()
.buffer(100.milliseconds.toJavaDuration())
@@ -36,11 +39,16 @@ class GameRequestService(
}
}
- fun emit(event: GameUserEvent) {
+ fun emit(event: GameRequestEvent) {
gameRequestEvents.tryEmitNext(event)
}
}
+ fun getAll(): List {
+ val entities = gameRequestRepository.findAll()
+ return entities.toDtos()
+ }
+
fun createRequest(gameRequest: GameRequestCreationDto) {
val currentUser = userService.getByUsername(getCurrentAuth().name)
@@ -54,4 +62,36 @@ class GameRequestService(
gameRequestRepository.save(gameRequest)
}
+
+ fun deleteRequest(id: Long) {
+ val gameRequest = gameRequestRepository.findById(id)
+ .orElseThrow { NoSuchElementException("No game request found with id $id") }
+ gameRequestRepository.delete(gameRequest)
+ }
+
+ fun changeRequestStatus(id: Long, status: GameRequestStatus) {
+ val gameRequest = gameRequestRepository.findById(id)
+ .orElseThrow { NoSuchElementException("No game request found with id $id") }
+ gameRequest.status = status
+ gameRequestRepository.save(gameRequest)
+ }
+
+ fun toggleRequestVote(id: Long) {
+ val currentUser =
+ userService.getByUsername(getCurrentAuth().name) ?: throw IllegalStateException("Current user not found")
+ val gameRequest = gameRequestRepository.findById(id)
+ .orElseThrow { NoSuchElementException("No game request found with id $id") }
+
+ if (gameRequest.requester?.id == currentUser.id) {
+ throw IllegalStateException("You cannot vote for your own request")
+ }
+
+ if (gameRequest.voters.contains(currentUser)) {
+ gameRequest.voters.remove(currentUser)
+ } else {
+ gameRequest.voters.add(currentUser)
+ }
+
+ gameRequestRepository.save(gameRequest)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
index 1a4169d..58f6743 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
@@ -1,13 +1,18 @@
package org.gameyfin.app.requests.dto
+import org.gameyfin.app.requests.entities.ExternalProviderIds
import org.gameyfin.app.requests.status.GameRequestStatus
-import org.gameyfin.app.users.dto.UserInfoAdminDto
+import org.gameyfin.app.users.dto.UserInfoDto
import java.time.Instant
class GameRequestDto(
val id: Long,
val title: String,
val release: Instant,
+ val externalProviderIds: ExternalProviderIds,
val status: GameRequestStatus,
- val requester: UserInfoAdminDto
+ val requester: UserInfoDto?,
+ val voters: List,
+ val createdAt: Instant?,
+ val updatedAt: Instant?
)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
similarity index 75%
rename from app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt
rename to app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
index 6808044..20947f4 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequest.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
@@ -1,42 +1,45 @@
-package org.gameyfin.app.requests
+package org.gameyfin.app.requests.entities
import jakarta.persistence.*
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.entities.User
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp
+import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.Instant
typealias ExternalProviderIds = Map
@Entity
+@EntityListeners(GameRequestEntityListener::class, AuditingEntityListener::class)
class GameRequest(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null,
- @CreationTimestamp
- @Column(nullable = false, updatable = false)
- var createdAt: Instant? = null,
-
- @UpdateTimestamp
- @Column(nullable = false)
- var updatedAt: Instant? = null,
-
@Column(nullable = false)
val title: String,
@Column(nullable = false)
val release: Instant,
+ @ElementCollection
+ val externalProviderIds: ExternalProviderIds,
+
@Column(nullable = false)
var status: GameRequestStatus,
@ManyToOne(fetch = FetchType.EAGER)
var requester: User? = null,
+ @OneToMany
var voters: MutableList = mutableListOf(),
- @ElementCollection
- val externalProviderIds: ExternalProviderIds
+ @CreationTimestamp
+ @Column(nullable = false, updatable = false)
+ var createdAt: Instant? = null,
+
+ @UpdateTimestamp
+ @Column(nullable = false)
+ var updatedAt: Instant? = null
)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequestEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequestEntityListener.kt
new file mode 100644
index 0000000..625010b
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequestEntityListener.kt
@@ -0,0 +1,25 @@
+package org.gameyfin.app.requests.entities
+
+import jakarta.persistence.PostPersist
+import jakarta.persistence.PostRemove
+import jakarta.persistence.PostUpdate
+import org.gameyfin.app.requests.GameRequestService
+import org.gameyfin.app.requests.dto.GameRequestEvent
+import org.gameyfin.app.requests.extensions.toDto
+
+class GameRequestEntityListener {
+ @PostPersist
+ fun created(gameRequest: GameRequest) {
+ GameRequestService.emit(GameRequestEvent.Created(gameRequest.toDto()))
+ }
+
+ @PostUpdate
+ fun updated(gameRequest: GameRequest) {
+ GameRequestService.emit(GameRequestEvent.Updated(gameRequest.toDto()))
+ }
+
+ @PostRemove
+ fun deleted(gameRequest: GameRequest) {
+ GameRequestService.emit(GameRequestEvent.Deleted(gameRequest.id!!))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
index 8136b13..7301814 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
@@ -1,7 +1,8 @@
package org.gameyfin.app.requests.extensions
-import org.gameyfin.app.requests.GameRequest
import org.gameyfin.app.requests.dto.GameRequestDto
+import org.gameyfin.app.requests.entities.GameRequest
+import org.gameyfin.app.users.extensions.toUserInfoDto
fun GameRequest.toDto(): GameRequestDto {
return GameRequestDto(
@@ -10,6 +11,13 @@ fun GameRequest.toDto(): GameRequestDto {
release = this.release,
externalProviderIds = this.externalProviderIds,
status = this.status,
- requester = this.requester.toDto()
+ requester = this.requester?.toUserInfoDto(),
+ voters = this.voters.map { it.toUserInfoDto() },
+ createdAt = this.createdAt,
+ updatedAt = this.updatedAt
)
+}
+
+fun Collection.toDtos(): List {
+ return this.map { it.toDto() }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt
index e020434..443f8b0 100644
--- a/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/setup/SetupEndpoint.kt
@@ -3,7 +3,7 @@ package org.gameyfin.app.setup
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
import com.vaadin.hilla.exception.EndpointException
-import org.gameyfin.app.users.dto.UserInfoAdminDto
+import org.gameyfin.app.users.dto.ExtendedUserInfoDto
import org.gameyfin.app.users.dto.UserRegistrationDto
@Endpoint
@@ -16,7 +16,7 @@ class SetupEndpoint(
}
@AnonymousAllowed
- fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoAdminDto {
+ fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): ExtendedUserInfoDto {
if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed")
return setupService.createInitialAdminUser(superAdminRegistration)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt b/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt
index 1a6c441..02eaa3e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/setup/SetupService.kt
@@ -3,9 +3,10 @@ package org.gameyfin.app.setup
import org.gameyfin.app.core.Role
import org.gameyfin.app.users.RoleService
import org.gameyfin.app.users.UserService
-import org.gameyfin.app.users.dto.UserInfoAdminDto
+import org.gameyfin.app.users.dto.ExtendedUserInfoDto
import org.gameyfin.app.users.dto.UserRegistrationDto
import org.gameyfin.app.users.entities.User
+import org.gameyfin.app.users.extensions.toExtendedUserInfoDto
import org.springframework.stereotype.Service
@Service
@@ -26,7 +27,7 @@ class SetupService(
/**
* Creates the initial user with Super-Admin permissions
*/
- fun createInitialAdminUser(registration: UserRegistrationDto): UserInfoAdminDto {
+ fun createInitialAdminUser(registration: UserRegistrationDto): ExtendedUserInfoDto {
val superAdmin = User(
username = registration.username,
password = registration.password,
@@ -36,6 +37,6 @@ class SetupService(
)
val user = userService.registerOrUpdateUser(superAdmin)
- return userService.toUserInfo(user)
+ return user.toExtendedUserInfoDto()
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
index a114da8..d7c5739 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
@@ -6,7 +6,7 @@ import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.security.getCurrentAuth
-import org.gameyfin.app.users.dto.UserInfoAdminDto
+import org.gameyfin.app.users.dto.ExtendedUserInfoDto
import org.gameyfin.app.users.dto.UserUpdateDto
import org.gameyfin.app.users.enums.RoleAssignmentResult
import org.springframework.security.core.Authentication
@@ -18,7 +18,7 @@ class UserEndpoint(
private val roleService: RoleService
) {
@AnonymousAllowed
- fun getUserInfo(): UserInfoAdminDto? {
+ fun getUserInfo(): ExtendedUserInfoDto? {
val auth = getCurrentAuth()
if (!auth.isAuthenticated || auth.principal == "anonymousUser") return null
return userService.getUserInfo()
@@ -36,7 +36,7 @@ class UserEndpoint(
}
@RolesAllowed(Role.Names.ADMIN)
- fun getAllUsers(): List {
+ fun getAllUsers(): List {
return userService.getAllUsers()
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
index e22bcda..dad6137 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
@@ -9,15 +9,15 @@ import org.gameyfin.app.core.events.*
import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.games.entities.Image
import org.gameyfin.app.media.ImageService
-import org.gameyfin.app.users.dto.UserInfoAdminDto
+import org.gameyfin.app.users.dto.ExtendedUserInfoDto
import org.gameyfin.app.users.dto.UserRegistrationDto
import org.gameyfin.app.users.dto.UserUpdateDto
import org.gameyfin.app.users.emailconfirmation.EmailConfirmationService
import org.gameyfin.app.users.enums.RoleAssignmentResult
+import org.gameyfin.app.users.extensions.toAuthorities
+import org.gameyfin.app.users.extensions.toExtendedUserInfoDto
import org.gameyfin.app.users.persistence.UserRepository
import org.springframework.context.ApplicationEventPublisher
-import org.springframework.security.core.GrantedAuthority
-import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
@@ -56,7 +56,7 @@ class UserService(
true,
true,
true,
- toAuthorities(user.roles)
+ user.roles.toAuthorities()
)
}
@@ -67,7 +67,7 @@ class UserService(
true,
true,
true,
- toAuthorities(user.roles)
+ user.roles.toAuthorities()
)
}
@@ -77,8 +77,8 @@ class UserService(
fun findByOidcProviderId(oidcProviderId: String): org.gameyfin.app.users.entities.User? =
userRepository.findByOidcProviderId(oidcProviderId)
- fun getAllUsers(): List {
- return userRepository.findAll().map { u -> toUserInfo(u) }
+ fun getAllUsers(): List {
+ return userRepository.findAll().map { it.toExtendedUserInfoDto() }
}
fun getByEmail(email: String): org.gameyfin.app.users.entities.User? {
@@ -93,20 +93,20 @@ class UserService(
return userRepository.findByUsername(username) ?: throw UsernameNotFoundException("Unknown user '$username'")
}
- fun getUserInfo(): UserInfoAdminDto {
+ fun getUserInfo(): ExtendedUserInfoDto {
val auth = getCurrentAuth()
val principal = auth.principal
if (principal is OidcUser) {
val oidcUser = org.gameyfin.app.users.entities.User(principal)
- val userInfoDto = toUserInfo(oidcUser)
+ val userInfoDto = oidcUser.toExtendedUserInfoDto()
userInfoDto.roles = roleService.extractGrantedAuthorities(principal.authorities)
- .mapNotNull { Role.Companion.safeValueOf(it.authority) }
+ .mapNotNull { Role.safeValueOf(it.authority) }
return userInfoDto
}
val user = getByUsernameNonNull(auth.name)
- return toUserInfo(user)
+ return user.toExtendedUserInfoDto()
}
fun getAvatar(username: String): Image? {
@@ -158,7 +158,7 @@ class UserService(
RegistrationAttemptWithExistingEmailEvent(
this,
it,
- Utils.Companion.getBaseUrl()
+ Utils.getBaseUrl()
)
)
return
@@ -179,12 +179,12 @@ class UserService(
if (adminNeedsToApprove) {
eventPublisher.publishEvent(UserRegistrationWaitingForApprovalEvent(this, user))
} else {
- eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.Companion.getBaseUrl()))
+ eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.getBaseUrl()))
}
if (!user.emailConfirmed) {
val token = emailConfirmationService.generate(user)
- eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.Companion.getBaseUrl()))
+ eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.getBaseUrl()))
}
}
@@ -222,7 +222,7 @@ class UserService(
user.email = it
user.emailConfirmed = false
val token = emailConfirmationService.generate(user)
- eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.Companion.getBaseUrl()))
+ eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.getBaseUrl()))
}
userRepository.save(user)
@@ -246,7 +246,7 @@ class UserService(
return RoleAssignmentResult.TARGET_POWER_LEVEL_TOO_HIGH
}
- val newAssignedRoles = roleNames.mapNotNull { r -> Role.Companion.safeValueOf(r) }
+ val newAssignedRoles = roleNames.mapNotNull { r -> Role.safeValueOf(r) }
val newAssignedRolesLevel = roleService.getHighestRole(newAssignedRoles).powerLevel
val currentUserLevel = roleService.getHighestRoleFromAuthorities(currentUser.authorities).powerLevel
@@ -276,29 +276,12 @@ class UserService(
val user = getByUsernameNonNull(username)
user.enabled = enabled
userRepository.save(user)
- eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.Companion.getBaseUrl()))
+ eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.getBaseUrl()))
}
fun deleteUser(username: String) {
val user = getByUsernameNonNull(username)
userRepository.delete(user)
- eventPublisher.publishEvent(AccountDeletedEvent(this, user, Utils.Companion.getBaseUrl()))
- }
-
- fun toUserInfo(user: org.gameyfin.app.users.entities.User): UserInfoAdminDto {
- return UserInfoAdminDto(
- username = user.username,
- email = user.email,
- emailConfirmed = user.emailConfirmed,
- enabled = user.enabled,
- hasAvatar = user.avatar != null,
- avatarId = user.avatar?.id,
- managedBySso = user.oidcProviderId != null,
- roles = user.roles
- )
- }
-
- private fun toAuthorities(roles: Collection): List {
- return roles.map { r -> SimpleGrantedAuthority(r.roleName) }
+ eventPublisher.publishEvent(AccountDeletedEvent(this, user, Utils.getBaseUrl()))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoAdminDto.kt b/app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt
similarity index 90%
rename from app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoAdminDto.kt
rename to app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt
index 5a93cde..d125756 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoAdminDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt
@@ -2,7 +2,7 @@ package org.gameyfin.app.users.dto
import org.gameyfin.app.core.Role
-data class UserInfoAdminDto(
+data class ExtendedUserInfoDto(
val username: String,
val managedBySso: Boolean,
val email: String,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
similarity index 81%
rename from app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt
rename to app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
index 5bbbfbc..d975a85 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoUserDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
@@ -1,6 +1,6 @@
package org.gameyfin.app.users.dto
-data class UserInfoUserDto(
+data class UserInfoDto(
val username: String,
val hasAvatar: Boolean,
val avatarId: Long? = null,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt
new file mode 100644
index 0000000..77e715f
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt
@@ -0,0 +1,33 @@
+package org.gameyfin.app.users.extensions
+
+import org.gameyfin.app.core.Role
+import org.gameyfin.app.users.dto.ExtendedUserInfoDto
+import org.gameyfin.app.users.dto.UserInfoDto
+import org.gameyfin.app.users.entities.User
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+
+fun User.toUserInfoDto(): UserInfoDto {
+ return UserInfoDto(
+ username = this.username,
+ hasAvatar = this.avatar != null,
+ avatarId = this.avatar?.id
+ )
+}
+
+fun User.toExtendedUserInfoDto(): ExtendedUserInfoDto {
+ return ExtendedUserInfoDto(
+ username = this.username,
+ email = this.email,
+ emailConfirmed = this.emailConfirmed,
+ enabled = this.enabled,
+ hasAvatar = this.avatar != null,
+ avatarId = this.avatar?.id,
+ managedBySso = this.oidcProviderId != null,
+ roles = this.roles
+ )
+}
+
+fun Collection.toAuthorities(): List {
+ return this.map { r -> SimpleGrantedAuthority(r.roleName) }
+}
\ No newline at end of file
From 9908c401e6b6fc7dc4ad30edf6c2375b25f60737 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 10:58:48 +0200
Subject: [PATCH 03/32] Update imports in LibraryScanService
---
.../kotlin/org/gameyfin/app/libraries/LibraryScanService.kt | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
index bbc28e9..62a53c7 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
@@ -5,9 +5,8 @@ import org.gameyfin.app.core.filesystem.FilesystemService
import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.entities.Game
import org.gameyfin.app.games.entities.Image
-import org.gameyfin.app.libraries.dto.LibraryScanProgress
-import org.gameyfin.app.libraries.dto.LibraryScanStatus
-import org.gameyfin.app.libraries.dto.LibraryScanStep
+import org.gameyfin.app.libraries.dto.*
+import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.app.libraries.enums.ScanType
import org.gameyfin.app.libraries.scan.*
import org.gameyfin.app.media.ImageService
From 3377f770f6d05143c68a0912c750458ff57a46ce Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 11:22:49 +0200
Subject: [PATCH 04/32] Implement automatic "mark as fulfilled" for requests
---
.../app/games/entities/GameEntityListener.kt | 23 ++++++++++++++-
.../app/requests/GameRequestRepository.kt | 10 ++++++-
.../app/requests/GameRequestService.kt | 29 +++++++++++++++++++
.../app/requests/dto/GameRequestEvent.kt | 4 +--
.../app/requests/entities/GameRequest.kt | 2 ++
5 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
index 9a04b3e..f1f8e53 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
@@ -8,12 +8,33 @@ import org.gameyfin.app.games.dto.GameAdminEvent
import org.gameyfin.app.games.dto.GameUserEvent
import org.gameyfin.app.games.extensions.toAdminDto
import org.gameyfin.app.games.extensions.toUserDto
+import org.gameyfin.app.requests.GameRequestService
+import org.springframework.context.ApplicationContext
+import org.springframework.context.ApplicationContextAware
+import org.springframework.stereotype.Component
+
+@Component
+class GameEntityListener : ApplicationContextAware {
+
+ companion object {
+ private lateinit var applicationContext: ApplicationContext
+ }
+
+ override fun setApplicationContext(context: ApplicationContext) {
+ applicationContext = context
+ }
+
+ private fun getGameRequestService(): GameRequestService {
+ return applicationContext.getBean(GameRequestService::class.java)
+ }
-class GameEntityListener {
@PostPersist
fun created(game: Game) {
GameService.emitUser(GameUserEvent.Created(game.toUserDto()))
GameService.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
+
+ // After a game is created, mark any matching game requests as FULFILLED
+ getGameRequestService().completeMatchingRequests(game)
}
@PostUpdate
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
index c045b4d..a8d2a95 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
@@ -2,5 +2,13 @@ package org.gameyfin.app.requests
import org.gameyfin.app.requests.entities.GameRequest
import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+import org.springframework.data.repository.query.Param
+import java.time.Instant
-interface GameRequestRepository : JpaRepository
\ No newline at end of file
+interface GameRequestRepository : JpaRepository {
+ fun findByTitleAndRelease(title: String, release: Instant): List
+
+ @Query("SELECT g FROM GameRequest g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release)")
+ fun findByTitleAndReleaseYear(@Param("title") title: String, @Param("release") release: Instant): List
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
index dfef3f4..9860d84 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -2,10 +2,12 @@ package org.gameyfin.app.requests
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.core.security.getCurrentAuth
+import org.gameyfin.app.games.entities.Game
import org.gameyfin.app.requests.dto.GameRequestCreationDto
import org.gameyfin.app.requests.dto.GameRequestDto
import org.gameyfin.app.requests.dto.GameRequestEvent
import org.gameyfin.app.requests.entities.GameRequest
+import org.gameyfin.app.requests.extensions.toDto
import org.gameyfin.app.requests.extensions.toDtos
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.UserService
@@ -94,4 +96,31 @@ class GameRequestService(
gameRequestRepository.save(gameRequest)
}
+
+ fun completeMatchingRequests(game: Game) {
+ val gameTitle = game.title
+ val gameRelease = game.release
+
+ if (gameTitle == null || gameRelease == null) {
+ log.debug { "Game '${game.id}' is missing title and/or release date, cannot complete matching requests" }
+ return
+ }
+
+ // First match by exact title and release date, if not result could be found then by title and release year only
+ val matchingRequestsByExactRelease = gameRequestRepository.findByTitleAndRelease(gameTitle, gameRelease)
+ val matchingRequestsByReleaseYear = matchingRequestsByExactRelease.ifEmpty {
+ gameRequestRepository.findByTitleAndReleaseYear(
+ gameTitle,
+ gameRelease
+ )
+ }
+
+ matchingRequestsByReleaseYear.forEach { request ->
+ request.status = GameRequestStatus.FULFILLED
+ request.linkedGameId = game.id
+ val persistedRequest = gameRequestRepository.save(request)
+ emit(GameRequestEvent.Updated(persistedRequest.toDto()))
+ log.info { "Marked game request '${request.title}' (${request.release}) as FULFILLED because game is now available" }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt
index f006d3a..d59bd9a 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestEvent.kt
@@ -3,7 +3,7 @@ package org.gameyfin.app.requests.dto
sealed class GameRequestEvent {
abstract val type: String
- data class Created(val game: GameRequestDto, override val type: String = "created") : GameRequestEvent()
- data class Updated(val game: GameRequestDto, override val type: String = "updated") : GameRequestEvent()
+ data class Created(val gameRequest: GameRequestDto, override val type: String = "created") : GameRequestEvent()
+ data class Updated(val gameRequest: GameRequestDto, override val type: String = "updated") : GameRequestEvent()
data class Deleted(val gameRequestId: Long, override val type: String = "deleted") : GameRequestEvent()
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
index 20947f4..832322c 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
@@ -35,6 +35,8 @@ class GameRequest(
@OneToMany
var voters: MutableList = mutableListOf(),
+ var linkedGameId: Long? = null,
+
@CreationTimestamp
@Column(nullable = false, updatable = false)
var createdAt: Instant? = null,
From 989a5ef18985366e976df5b49fccf069f5835c42 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 14:13:46 +0200
Subject: [PATCH 05/32] Display toast when Gameyfin restarts
---
.../administration/SystemManagement.tsx | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/app/src/main/frontend/components/administration/SystemManagement.tsx b/app/src/main/frontend/components/administration/SystemManagement.tsx
index 0057fde..54246ed 100644
--- a/app/src/main/frontend/components/administration/SystemManagement.tsx
+++ b/app/src/main/frontend/components/administration/SystemManagement.tsx
@@ -1,14 +1,25 @@
import React from "react";
import {SystemEndpoint} from "Frontend/generated/endpoints";
import withConfigPage from "Frontend/components/administration/withConfigPage";
-import {Button} from "@heroui/react";
+import {addToast, Button} from "@heroui/react";
import Section from "Frontend/components/general/Section";
function SystemManagementLayout() {
+
+ function restart() {
+ SystemEndpoint.restart().then(() =>
+ addToast({
+ title: "Restarting",
+ description: "Gameyfin is restarting. This may take a few moments.",
+ color: "success"
+ })
+ );
+ }
+
return (
- SystemEndpoint.restart()}>Restart
+ Restart
);
}
From 45e9f562e85e9a98072a6e90600360c291a387e8 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 14:14:30 +0200
Subject: [PATCH 06/32] Migrate UserInfoDto -> ExtendedUserInfoDto
---
.../frontend/components/administration/UserManagement.tsx | 4 ++--
.../components/general/cards/UserManagementCard.tsx | 6 +++---
.../frontend/components/general/modals/AssignRolesModal.tsx | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/src/main/frontend/components/administration/UserManagement.tsx b/app/src/main/frontend/components/administration/UserManagement.tsx
index 4c38313..99a4f69 100644
--- a/app/src/main/frontend/components/administration/UserManagement.tsx
+++ b/app/src/main/frontend/components/administration/UserManagement.tsx
@@ -3,16 +3,16 @@ import ConfigFormField from "Frontend/components/administration/ConfigFormField"
import withConfigPage from "Frontend/components/administration/withConfigPage";
import Section from "Frontend/components/general/Section";
import {UserEndpoint} from "Frontend/generated/endpoints";
-import UserInfoDto from "Frontend/generated/org/gameyfin/app/users/dto/UserInfoDto";
import {UserManagementCard} from "Frontend/components/general/cards/UserManagementCard";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
import {Info, UserPlus} from "@phosphor-icons/react";
import {Button, Divider, Tooltip, useDisclosure} from "@heroui/react";
import InviteUserModal from "Frontend/components/general/modals/InviteUserModal";
+import ExtendedUserInfoDto from "Frontend/generated/org/gameyfin/app/users/dto/ExtendedUserInfoDto";
function UserManagementLayout({getConfig, formik}: any) {
const inviteUserModal = useDisclosure();
- const [users, setUsers] = useState([]);
+ const [users, setUsers] = useState([]);
useEffect(() => {
UserEndpoint.getAllUsers().then(
diff --git a/app/src/main/frontend/components/general/cards/UserManagementCard.tsx b/app/src/main/frontend/components/general/cards/UserManagementCard.tsx
index 3bdc20e..5d75a38 100644
--- a/app/src/main/frontend/components/general/cards/UserManagementCard.tsx
+++ b/app/src/main/frontend/components/general/cards/UserManagementCard.tsx
@@ -7,11 +7,11 @@ import Avatar from "Frontend/components/general/Avatar";
import ConfirmUserDeletionModal from "Frontend/components/general/modals/ConfirmUserDeletionModal";
import PasswordResetTokenModal from "Frontend/components/general/modals/PasswortResetTokenModal";
import TokenDto from "Frontend/generated/org/gameyfin/app/shared/token/TokenDto";
-import UserInfoDto from "Frontend/generated/org/gameyfin/app/users/dto/UserInfoDto";
import RoleChip from "Frontend/components/general/RoleChip";
import AssignRolesModal from "Frontend/components/general/modals/AssignRolesModal";
+import ExtendedUserInfoDto from "Frontend/generated/org/gameyfin/app/users/dto/ExtendedUserInfoDto";
-export function UserManagementCard({user}: { user: UserInfoDto }) {
+export function UserManagementCard({user}: { user: ExtendedUserInfoDto }) {
const userDeletionConfirmationModal = useDisclosure();
const passwordResetTokenModal = useDisclosure();
const roleAssignmentModal = useDisclosure();
@@ -141,7 +141,7 @@ export function UserManagementCard({user}: { user: UserInfoDto }) {
{user.username}
{user.email}
{user.roles?.map((role) => (
-
+
))}
diff --git a/app/src/main/frontend/components/general/modals/AssignRolesModal.tsx b/app/src/main/frontend/components/general/modals/AssignRolesModal.tsx
index 58598c1..2fda413 100644
--- a/app/src/main/frontend/components/general/modals/AssignRolesModal.tsx
+++ b/app/src/main/frontend/components/general/modals/AssignRolesModal.tsx
@@ -12,14 +12,14 @@ import {
SelectItem
} from "@heroui/react";
import {UserEndpoint} from "Frontend/generated/endpoints";
-import UserInfoDto from "Frontend/generated/org/gameyfin/app/users/dto/UserInfoDto";
import RoleChip from "Frontend/components/general/RoleChip";
import RoleAssignmentResult from "Frontend/generated/org/gameyfin/app/users/enums/RoleAssignmentResult";
+import ExtendedUserInfoDto from "Frontend/generated/org/gameyfin/app/users/dto/ExtendedUserInfoDto";
interface AssignRolesModalProps {
isOpen: boolean;
onOpenChange: () => void;
- user: UserInfoDto;
+ user: ExtendedUserInfoDto;
}
interface Role {
From deb0a20bc0a114c645b78f74d99df6bf8b37d38a Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 14:14:40 +0200
Subject: [PATCH 07/32] Rename run configuration
---
.run/{GameyfinApplication.run.xml => Gameyfin.run.xml} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename .run/{GameyfinApplication.run.xml => Gameyfin.run.xml} (84%)
diff --git a/.run/GameyfinApplication.run.xml b/.run/Gameyfin.run.xml
similarity index 84%
rename from .run/GameyfinApplication.run.xml
rename to .run/Gameyfin.run.xml
index 31c486d..a35cfdb 100644
--- a/.run/GameyfinApplication.run.xml
+++ b/.run/Gameyfin.run.xml
@@ -1,5 +1,5 @@
-
+
From 6198c143db6ae048864145dd0f4f717486b34040 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 14:14:59 +0200
Subject: [PATCH 08/32] Fix error log in console when user has no avatar
---
.../frontend/components/general/Avatar.tsx | 23 +++++++++++++------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/app/src/main/frontend/components/general/Avatar.tsx b/app/src/main/frontend/components/general/Avatar.tsx
index 78df1de..7f1d217 100644
--- a/app/src/main/frontend/components/general/Avatar.tsx
+++ b/app/src/main/frontend/components/general/Avatar.tsx
@@ -15,13 +15,22 @@ const Avatar = ({...props}) => {
}
// TODO: Check if avatar can be loaded from SSO
- return (
-
- );
+ if (auth.state.user?.hasAvatar) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
}
export default Avatar;
\ No newline at end of file
From ae7a65ccbc56d325258f19e509501d4b9ad54c28 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 2 Sep 2025 18:49:51 +0200
Subject: [PATCH 09/32] Add minimal UI for game requests Fix some minor bugs
---
app/src/main/frontend/App.tsx | 2 +
.../general/modals/MatchGameModal.tsx | 4 +-
.../general/modals/RequestGameModal.tsx | 162 ++++++++++++++++++
.../main/frontend/state/GameRequestState.ts | 50 ++++++
app/src/main/frontend/views/MainLayout.tsx | 33 +++-
.../main/kotlin/org/gameyfin/app/core/Role.kt | 12 +-
.../org/gameyfin/app/core/events/Events.kt | 5 +-
.../app/core/security/SecurityUtils.kt | 4 +-
.../org/gameyfin/app/games/GameService.kt | 4 +-
.../app/games/entities/GameEntityListener.kt | 25 +--
.../org/gameyfin/app/media/ImageEndpoint.kt | 4 +-
.../gameyfin/app/messages/MessageService.kt | 2 +-
.../app/requests/GameRequestEndpoint.kt | 1 +
.../app/requests/GameRequestRepository.kt | 14 +-
.../app/requests/GameRequestService.kt | 37 ++--
.../requests/dto/GameRequestCreationDto.kt | 3 +-
.../app/requests/dto/GameRequestDto.kt | 4 +-
.../app/requests/entities/GameRequest.kt | 14 +-
.../extensions/GameRequestExtensions.kt | 3 +-
.../org/gameyfin/app/users/SessionService.kt | 2 +-
.../org/gameyfin/app/users/UserEndpoint.kt | 8 +-
.../org/gameyfin/app/users/UserService.kt | 6 +-
.../EmailConfirmationEndpoint.kt | 2 +-
.../preferences/UserPreferencesService.kt | 2 +-
.../users/registration/InvitationService.kt | 2 +-
25 files changed, 318 insertions(+), 87 deletions(-)
create mode 100644 app/src/main/frontend/components/general/modals/RequestGameModal.tsx
create mode 100644 app/src/main/frontend/state/GameRequestState.ts
diff --git a/app/src/main/frontend/App.tsx b/app/src/main/frontend/App.tsx
index 01f759e..1aadb07 100644
--- a/app/src/main/frontend/App.tsx
+++ b/app/src/main/frontend/App.tsx
@@ -15,6 +15,7 @@ import {initializePluginState} from "Frontend/state/PluginState";
import {isAdmin} from "Frontend/util/utils";
import {useRouteMetadata} from "Frontend/util/routing";
import {useEffect} from "react";
+import {initializeGameRequestState} from "Frontend/state/GameRequestState";
export default function App() {
client.middlewares = [ErrorHandlingMiddleware];
@@ -45,6 +46,7 @@ function ViewWithAuth() {
initializeLibraryState();
initializeGameState();
+ initializeGameRequestState();
if (isAdmin(auth)) {
initializeScanState();
diff --git a/app/src/main/frontend/components/general/modals/MatchGameModal.tsx b/app/src/main/frontend/components/general/modals/MatchGameModal.tsx
index f484648..6343ece 100644
--- a/app/src/main/frontend/components/general/modals/MatchGameModal.tsx
+++ b/app/src/main/frontend/components/general/modals/MatchGameModal.tsx
@@ -21,7 +21,7 @@ import {useSnapshot} from "valtio/react";
import {pluginState} from "Frontend/state/PluginState";
import PluginDto from "Frontend/generated/org/gameyfin/app/core/plugins/dto/PluginDto";
-interface EditGameMetadataModalProps {
+interface MatchGameModalProps {
path: string;
libraryId: number;
replaceGameId?: number;
@@ -37,7 +37,7 @@ export default function MatchGameModal({
initialSearchTerm,
isOpen,
onOpenChange
- }: EditGameMetadataModalProps) {
+ }: MatchGameModalProps) {
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
diff --git a/app/src/main/frontend/components/general/modals/RequestGameModal.tsx b/app/src/main/frontend/components/general/modals/RequestGameModal.tsx
new file mode 100644
index 0000000..453e66b
--- /dev/null
+++ b/app/src/main/frontend/components/general/modals/RequestGameModal.tsx
@@ -0,0 +1,162 @@
+import {
+ addToast,
+ Button,
+ Input,
+ Modal,
+ ModalBody,
+ ModalContent,
+ Table,
+ TableBody,
+ TableCell,
+ TableColumn,
+ TableHeader,
+ TableRow,
+ Tooltip
+} from "@heroui/react";
+import React, {useEffect, useState} from "react";
+import {ArrowRight, MagnifyingGlass} from "@phosphor-icons/react";
+import {GameEndpoint, GameRequestEndpoint} from "Frontend/generated/endpoints";
+import GameSearchResultDto from "Frontend/generated/org/gameyfin/app/games/dto/GameSearchResultDto";
+import PluginIcon from "../plugin/PluginIcon";
+import {useSnapshot} from "valtio/react";
+import {pluginState} from "Frontend/state/PluginState";
+import PluginDto from "Frontend/generated/org/gameyfin/app/core/plugins/dto/PluginDto";
+import GameRequestCreationDto from "Frontend/generated/org/gameyfin/app/requests/dto/GameRequestCreationDto";
+
+interface RequestGameModalProps {
+ isOpen: boolean;
+ onOpenChange: () => void;
+}
+
+export default function RequestGameModal({
+ isOpen,
+ onOpenChange
+ }: RequestGameModalProps) {
+ const [searchTerm, setSearchTerm] = useState("");
+ const [searchResults, setSearchResults] = useState([]);
+ const [isSearching, setIsSearching] = useState(false);
+ const [isRequesting, setIsRequesting] = useState(null);
+
+ const plugins = useSnapshot(pluginState).state;
+
+ useEffect(() => {
+ setSearchTerm("");
+ setSearchResults([]);
+ }, [isOpen]);
+
+ async function requestGame(game: GameSearchResultDto) {
+ const request: GameRequestCreationDto = {
+ title: game.title,
+ release: game.release
+ }
+ await GameRequestEndpoint.create(request);
+
+ addToast({
+ title: "Request submitted",
+ description: `Your request for "${game.title}" has been submitted.`,
+ color: "success"
+ })
+ }
+
+ async function search() {
+ setIsSearching(true);
+ const results = await GameEndpoint.getPotentialMatches(searchTerm);
+ setSearchResults(results);
+ setIsSearching(false);
+ }
+
+ return (
+
+
+ {(onClose) => (
+
+
+
Request a game
+
+
+ {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ await search();
+ }
+ }}
+ />
+
+
+
+
+
+
+
+
+ Title & Release
+ Developer(s)
+ Publisher(s)
+ {/* width={1} keeps the column as far to the right as possible*/}
+ Sources
+
+
+
+ {(item) => (
+
+
+ {item.title} ({item.release ? new Date(item.release).getFullYear() : "unknown"})
+
+
+
+ {item.developers ? item.developers.map(
+ developer =>
{developer}
+ ) : "unknown"}
+
+
+
+
+ {item.publishers ? item.publishers.map(
+ publisher =>
{publisher}
+ ) : "unknown"}
+
+
+
+
+ {Object.values(item.originalIds).map(
+ originalId =>
+ )}
+
+
+
+
+ {
+ setIsRequesting(item.id);
+ await requestGame(item);
+ setIsRequesting(null);
+ onClose();
+ }}>
+
+
+
+
+
+ )}
+
+
+
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/app/src/main/frontend/state/GameRequestState.ts b/app/src/main/frontend/state/GameRequestState.ts
new file mode 100644
index 0000000..00d47a4
--- /dev/null
+++ b/app/src/main/frontend/state/GameRequestState.ts
@@ -0,0 +1,50 @@
+import {Subscription} from "@vaadin/hilla-frontend";
+import {proxy} from "valtio/index";
+import {GameRequestEndpoint} from "Frontend/generated/endpoints";
+import GameRequestEvent from "Frontend/generated/org/gameyfin/app/requests/dto/GameRequestEvent";
+import GameRequestDto from "Frontend/generated/org/gameyfin/app/requests/dto/GameRequestDto";
+
+type GameRequestState = {
+ subscription?: Subscription;
+ isLoaded: boolean;
+ state: Record;
+ gameRequests: GameRequestDto[];
+};
+
+export const gameRequestState = proxy({
+ get isLoaded() {
+ return this.subscription != null;
+ },
+ state: {},
+ get gameRequests() {
+ return Object.values(this.state);
+ }
+});
+
+/** Subscribe to and process state updates from backend **/
+export async function initializeGameRequestState() {
+ if (gameRequestState.isLoaded) return;
+
+ // Fetch initial game request list
+ const initialEntries = await GameRequestEndpoint.getAll();
+ initialEntries.forEach((gameRequest: GameRequestDto) => {
+ gameRequestState.state[gameRequest.id] = gameRequest;
+ });
+
+ // Subscribe to real-time updates
+ gameRequestState.subscription = GameRequestEndpoint.subscribe().onNext((gameRequestEvents: GameRequestEvent[]) => {
+ gameRequestEvents.forEach((gameRequestEvent: GameRequestEvent) => {
+ switch (gameRequestEvent.type) {
+ case "created":
+ case "updated":
+ //@ts-ignore
+ gameRequestState.state[gameRequestEvent.id] = gameRequestEvent;
+ break;
+ case "deleted":
+ //@ts-ignore
+ delete gameRequestState.state[gameRequestEvent.id];
+ break;
+ }
+ })
+ });
+}
\ No newline at end of file
diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx
index a0d632e..b7c1acc 100644
--- a/app/src/main/frontend/views/MainLayout.tsx
+++ b/app/src/main/frontend/views/MainLayout.tsx
@@ -1,11 +1,21 @@
import {useEffect, useState} from 'react';
import ProfileMenu from "Frontend/components/ProfileMenu";
-import {Button, Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Tooltip} from "@heroui/react";
+import {
+ Button,
+ Divider,
+ Link,
+ Navbar,
+ NavbarBrand,
+ NavbarContent,
+ NavbarItem,
+ Tooltip,
+ useDisclosure
+} from "@heroui/react";
import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
import * as PackageJson from "../../../../package.json";
import {Outlet, useLocation, useNavigate} from "react-router";
import {useAuth} from "Frontend/util/auth";
-import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass, SignIn} from "@phosphor-icons/react";
+import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass, PlusCircle, SignIn} from "@phosphor-icons/react";
import Confetti, {ConfettiProps} from "react-confetti-boom";
import {useTheme} from "next-themes";
import {useUserPreferenceService} from "Frontend/util/user-preference-service";
@@ -14,6 +24,7 @@ import {useSnapshot} from "valtio/react";
import {gameState} from "Frontend/state/GameState";
import ScanProgressPopover from "Frontend/components/general/ScanProgressPopover";
import {isAdmin} from "Frontend/util/utils";
+import RequestGameModal from "Frontend/components/general/modals/RequestGameModal";
export default function MainLayout() {
const navigate = useNavigate();
@@ -26,6 +37,8 @@ export default function MainLayout() {
const [isExploding, setIsExploding] = useState(false);
const games = useSnapshot(gameState).games;
+ const requestGameModal = useDisclosure();
+
useEffect(() => {
userPreferenceService.sync()
.then(() => loadUserTheme().catch(console.error))
@@ -93,7 +106,17 @@ export default function MainLayout() {
}
-
+
+ {auth.state.user &&
+
+
+
+
+
+
+
+ }
{isAdmin(auth) &&
@@ -142,6 +165,10 @@ export default function MainLayout() {
+
+
+
);
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/Role.kt b/app/src/main/kotlin/org/gameyfin/app/core/Role.kt
index 0401300..76b33a4 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/Role.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/Role.kt
@@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue
import org.gameyfin.app.users.RoleService
import java.lang.Enum
+import kotlin.Int
+import kotlin.String
enum class Role(val roleName: String, val powerLevel: Int) {
@@ -21,12 +23,12 @@ enum class Role(val roleName: String, val powerLevel: Int) {
@JsonCreator
@JvmStatic
fun fromValue(value: String): Role? {
- val enumString = value.removePrefix(RoleService.Companion.INTERNAL_ROLE_PREFIX)
+ val enumString = value.removePrefix(RoleService.INTERNAL_ROLE_PREFIX)
return entries.find { it.roleName == enumString }
}
fun safeValueOf(type: String): Role? {
- val enumString = type.removePrefix(RoleService.Companion.INTERNAL_ROLE_PREFIX)
+ val enumString = type.removePrefix(RoleService.INTERNAL_ROLE_PREFIX)
return Enum.valueOf(Role::class.java, enumString)
}
}
@@ -34,9 +36,9 @@ enum class Role(val roleName: String, val powerLevel: Int) {
// necessary for the ability to use the Roles class in the @RolesAllowed annotation
class Names {
companion object {
- const val SUPERADMIN = "${RoleService.Companion.INTERNAL_ROLE_PREFIX}SUPERADMIN"
- const val ADMIN = "${RoleService.Companion.INTERNAL_ROLE_PREFIX}ADMIN"
- const val USER = "${RoleService.Companion.INTERNAL_ROLE_PREFIX}USER"
+ const val SUPERADMIN = "${RoleService.INTERNAL_ROLE_PREFIX}SUPERADMIN"
+ const val ADMIN = "${RoleService.INTERNAL_ROLE_PREFIX}ADMIN"
+ const val USER = "${RoleService.INTERNAL_ROLE_PREFIX}USER"
}
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt b/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt
index 1e30da0..f201ef6 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt
@@ -1,5 +1,6 @@
package org.gameyfin.app.core.events
+import org.gameyfin.app.games.entities.Game
import org.gameyfin.app.shared.token.Token
import org.gameyfin.app.shared.token.TokenType
import org.gameyfin.app.users.entities.User
@@ -23,4 +24,6 @@ class PasswordResetRequestEvent(source: Any, val token: Token userService.getByUsernameNonNull(userDetails.username)
is OidcUser -> userService.getByUsernameNonNull(userDetails.preferredUsername)
- else -> throw IllegalStateException("Unkown user type: ${userDetails::class.java.name}")
+ else -> throw IllegalStateException("Unkown user type: ${userDetails?.javaClass?.name}")
}
// Update only non-null fields
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
index f1f8e53..b400493 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
@@ -3,38 +3,21 @@ package org.gameyfin.app.games.entities
import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate
+import org.gameyfin.app.core.events.GameCreatedEvent
import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.dto.GameAdminEvent
import org.gameyfin.app.games.dto.GameUserEvent
import org.gameyfin.app.games.extensions.toAdminDto
import org.gameyfin.app.games.extensions.toUserDto
-import org.gameyfin.app.requests.GameRequestService
-import org.springframework.context.ApplicationContext
-import org.springframework.context.ApplicationContextAware
-import org.springframework.stereotype.Component
+import org.gameyfin.app.util.EventPublisherHolder
-@Component
-class GameEntityListener : ApplicationContextAware {
-
- companion object {
- private lateinit var applicationContext: ApplicationContext
- }
-
- override fun setApplicationContext(context: ApplicationContext) {
- applicationContext = context
- }
-
- private fun getGameRequestService(): GameRequestService {
- return applicationContext.getBean(GameRequestService::class.java)
- }
+class GameEntityListener {
@PostPersist
fun created(game: Game) {
GameService.emitUser(GameUserEvent.Created(game.toUserDto()))
GameService.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
-
- // After a game is created, mark any matching game requests as FULFILLED
- getGameRequestService().completeMatchingRequests(game)
+ EventPublisherHolder.publish(GameCreatedEvent(this, game))
}
@PostUpdate
diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
index 725df8a..df451b2 100644
--- a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
@@ -60,7 +60,7 @@ class ImageEndpoint(
@PermitAll
@PostMapping("/avatar/upload")
fun uploadAvatar(@RequestParam("file") file: MultipartFile) {
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val image: Image = if (!userService.hasAvatar(auth.name)) {
imageService.createFile(ImageType.AVATAR, file.inputStream, file.contentType!!)
@@ -75,7 +75,7 @@ class ImageEndpoint(
@PermitAll
@PostMapping("/avatar/delete")
fun deleteAvatar() {
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
userService.deleteAvatar(auth.name)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
index a0e48b8..530c879 100644
--- a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
@@ -60,7 +60,7 @@ class MessageService(
}
try {
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val user = userService.getByUsername(auth.name) ?: throw IllegalStateException("User not found")
val template = templateService.getMessageTemplate(templateKey)
sendNotification(user.email, "[Gameyfin] Test Notification", template, placeholders)
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
index 4cc235a..533cfdb 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
@@ -26,6 +26,7 @@ class GameRequestEndpoint(
fun getAll() = gameRequestService.getAll()
+ @PermitAll
fun create(gameRequest: GameRequestCreationDto) {
gameRequestService.createRequest(gameRequest)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
index a8d2a95..12e9f43 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
@@ -1,14 +1,20 @@
package org.gameyfin.app.requests
import org.gameyfin.app.requests.entities.GameRequest
+import org.gameyfin.app.requests.status.GameRequestStatus
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import java.time.Instant
interface GameRequestRepository : JpaRepository {
- fun findByTitleAndRelease(title: String, release: Instant): List
-
- @Query("SELECT g FROM GameRequest g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release)")
- fun findByTitleAndReleaseYear(@Param("title") title: String, @Param("release") release: Instant): List
+ @Query("SELECT g FROM GameRequest g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release) AND g.status NOT IN (:excludedStatuses)")
+ fun findOpenRequestsByTitleAndReleaseYear(
+ @Param("title") title: String,
+ @Param("release") release: Instant?,
+ @Param("excludedStatuses") excludedStatuses: List = listOf(
+ GameRequestStatus.FULFILLED,
+ GameRequestStatus.REJECTED
+ )
+ ): List
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
index 9860d84..0551c1d 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -1,8 +1,8 @@
package org.gameyfin.app.requests
import io.github.oshai.kotlinlogging.KotlinLogging
+import org.gameyfin.app.core.events.GameCreatedEvent
import org.gameyfin.app.core.security.getCurrentAuth
-import org.gameyfin.app.games.entities.Game
import org.gameyfin.app.requests.dto.GameRequestCreationDto
import org.gameyfin.app.requests.dto.GameRequestDto
import org.gameyfin.app.requests.dto.GameRequestEvent
@@ -11,6 +11,8 @@ import org.gameyfin.app.requests.extensions.toDto
import org.gameyfin.app.requests.extensions.toDtos
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.UserService
+import org.springframework.context.event.EventListener
+import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Sinks
@@ -52,13 +54,14 @@ class GameRequestService(
}
fun createRequest(gameRequest: GameRequestCreationDto) {
- val currentUser = userService.getByUsername(getCurrentAuth().name)
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
+ val currentUser =
+ userService.getByUsername(auth.name) ?: throw IllegalStateException("Current user not found")
val gameRequest = GameRequest(
title = gameRequest.title,
release = gameRequest.release,
status = GameRequestStatus.PENDING,
- externalProviderIds = gameRequest.externalProviderIds,
requester = currentUser
)
@@ -79,12 +82,13 @@ class GameRequestService(
}
fun toggleRequestVote(id: Long) {
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val currentUser =
- userService.getByUsername(getCurrentAuth().name) ?: throw IllegalStateException("Current user not found")
+ userService.getByUsername(auth.name) ?: throw IllegalStateException("Current user not found")
val gameRequest = gameRequestRepository.findById(id)
.orElseThrow { NoSuchElementException("No game request found with id $id") }
- if (gameRequest.requester?.id == currentUser.id) {
+ if (gameRequest.requester.id == currentUser.id) {
throw IllegalStateException("You cannot vote for your own request")
}
@@ -97,25 +101,24 @@ class GameRequestService(
gameRequestRepository.save(gameRequest)
}
- fun completeMatchingRequests(game: Game) {
+ @Async
+ @EventListener(GameCreatedEvent::class)
+ fun completeMatchingRequests(gameCreatedEvent: GameCreatedEvent) {
+ val game = gameCreatedEvent.game
val gameTitle = game.title
val gameRelease = game.release
- if (gameTitle == null || gameRelease == null) {
- log.debug { "Game '${game.id}' is missing title and/or release date, cannot complete matching requests" }
+ if (gameTitle == null) {
+ log.debug { "Game '${game.id}' is missing title, cannot complete matching requests" }
return
}
- // First match by exact title and release date, if not result could be found then by title and release year only
- val matchingRequestsByExactRelease = gameRequestRepository.findByTitleAndRelease(gameTitle, gameRelease)
- val matchingRequestsByReleaseYear = matchingRequestsByExactRelease.ifEmpty {
- gameRequestRepository.findByTitleAndReleaseYear(
- gameTitle,
- gameRelease
- )
- }
+ val matchingRequests = gameRequestRepository.findOpenRequestsByTitleAndReleaseYear(
+ gameTitle,
+ gameRelease
+ )
- matchingRequestsByReleaseYear.forEach { request ->
+ matchingRequests.forEach { request ->
request.status = GameRequestStatus.FULFILLED
request.linkedGameId = game.id
val persistedRequest = gameRequestRepository.save(request)
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt
index a31f8ed..8c2e22e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestCreationDto.kt
@@ -5,6 +5,5 @@ import java.time.Instant
class GameRequestCreationDto(
val title: String,
- val release: Instant,
- val externalProviderIds: Map
+ val release: Instant?
)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
index 58f6743..7932c90 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
@@ -1,6 +1,5 @@
package org.gameyfin.app.requests.dto
-import org.gameyfin.app.requests.entities.ExternalProviderIds
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.dto.UserInfoDto
import java.time.Instant
@@ -8,8 +7,7 @@ import java.time.Instant
class GameRequestDto(
val id: Long,
val title: String,
- val release: Instant,
- val externalProviderIds: ExternalProviderIds,
+ val release: Instant?,
val status: GameRequestStatus,
val requester: UserInfoDto?,
val voters: List,
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
index 832322c..0fbd7e9 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
@@ -8,8 +8,6 @@ import org.hibernate.annotations.UpdateTimestamp
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.Instant
-typealias ExternalProviderIds = Map
-
@Entity
@EntityListeners(GameRequestEntityListener::class, AuditingEntityListener::class)
class GameRequest(
@@ -21,18 +19,16 @@ class GameRequest(
val title: String,
@Column(nullable = false)
- val release: Instant,
-
- @ElementCollection
- val externalProviderIds: ExternalProviderIds,
+ val release: Instant?,
@Column(nullable = false)
+ @Enumerated(EnumType.STRING)
var status: GameRequestStatus,
- @ManyToOne(fetch = FetchType.EAGER)
- var requester: User? = null,
+ @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
+ var requester: User,
- @OneToMany
+ @OneToMany(fetch = FetchType.EAGER, cascade = [CascadeType.ALL], orphanRemoval = true)
var voters: MutableList = mutableListOf(),
var linkedGameId: Long? = null,
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
index 7301814..d798387 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
@@ -9,9 +9,8 @@ fun GameRequest.toDto(): GameRequestDto {
id = this.id!!,
title = this.title,
release = this.release,
- externalProviderIds = this.externalProviderIds,
status = this.status,
- requester = this.requester?.toUserInfoDto(),
+ requester = this.requester.toUserInfoDto(),
voters = this.voters.map { it.toUserInfoDto() },
createdAt = this.createdAt,
updatedAt = this.updatedAt
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt b/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt
index 3aa2d6c..4a8fc95 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/SessionService.kt
@@ -12,7 +12,7 @@ class SessionService(private val sessionRegistry: SessionRegistry) {
fun logoutAllSessions() {
val auth = getCurrentAuth()
- val sessions: List = sessionRegistry.getAllSessions(auth.principal, false)
+ val sessions: List = sessionRegistry.getAllSessions(auth?.principal, false)
for (sessionInfo in sessions) {
sessionInfo.expireNow()
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
index d7c5739..b925817 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserEndpoint.kt
@@ -20,13 +20,13 @@ class UserEndpoint(
@AnonymousAllowed
fun getUserInfo(): ExtendedUserInfoDto? {
val auth = getCurrentAuth()
- if (!auth.isAuthenticated || auth.principal == "anonymousUser") return null
+ if (auth?.isAuthenticated == false || auth?.principal == "anonymousUser") return null
return userService.getUserInfo()
}
@PermitAll
fun updateUser(updates: UserUpdateDto) {
- val auth: Authentication = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
userService.updateUser(auth.name, updates)
}
@@ -52,7 +52,7 @@ class UserEndpoint(
@PermitAll
fun deleteUser() {
- val auth: Authentication = getCurrentAuth()
+ val auth: Authentication = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
userService.deleteUser(auth.name)
}
@@ -68,7 +68,7 @@ class UserEndpoint(
@RolesAllowed(Role.Names.ADMIN)
fun getRolesBelow(): List {
- val auth: Authentication = getCurrentAuth()
+ val auth: Authentication = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
return roleService.getRolesBelowAuth(auth).map { it.roleName }
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
index dad6137..b98b41b 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
@@ -94,7 +94,7 @@ class UserService(
}
fun getUserInfo(): ExtendedUserInfoDto {
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val principal = auth.principal
if (principal is OidcUser) {
@@ -238,7 +238,7 @@ class UserService(
return RoleAssignmentResult.NO_ROLES_PROVIDED
}
- val currentUser = getCurrentAuth()
+ val currentUser = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val targetUser = getByUsernameNonNull(username)
if (!canManage(targetUser)) {
@@ -266,7 +266,7 @@ class UserService(
}
fun canManage(targetUser: org.gameyfin.app.users.entities.User): Boolean {
- val currentUser = getCurrentAuth()
+ val currentUser = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val currentUserLevel = roleService.getHighestRoleFromAuthorities(currentUser.authorities).powerLevel
val targetUserLevel = roleService.getHighestRole(targetUser.roles).powerLevel
return currentUserLevel > targetUserLevel
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt
index 4a84f83..0b913a3 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/emailconfirmation/EmailConfirmationEndpoint.kt
@@ -19,7 +19,7 @@ class EmailConfirmationEndpoint(
@PermitAll
fun resendEmailConfirmation() {
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
userService.getByUsername(auth.name)?.let {
emailConfirmationService.resendEmailConfirmation(it)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt b/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt
index 617b5c8..2d88f89 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/preferences/UserPreferencesService.kt
@@ -135,7 +135,7 @@ class UserPreferencesService(
}
private fun id(key: String): UserPreferenceKey {
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val user = userService.getByUsernameNonNull(auth.name)
return UserPreferenceKey(key, user.id!!)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt b/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt
index 81d1974..14db409 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/registration/InvitationService.kt
@@ -29,7 +29,7 @@ class InvitationService(
if (userService.existsByEmail(email))
throw IllegalStateException("User with email ${Utils.Companion.maskEmail(email)} is already registered")
- val auth = getCurrentAuth()
+ val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val user = userService.getByUsername(auth.name) ?: throw IllegalStateException("User not found")
val payload = mapOf(EMAIL_KEY to email)
val token = super.generateWithPayload(user, payload)
From 2f54cb49e665e47ad48da4a3bf4bb9996b87862d Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 3 Sep 2025 00:41:52 +0200
Subject: [PATCH 10/32] Finish implementing game requests
---
app/src/main/frontend/App.tsx | 2 +-
app/src/main/frontend/routes.tsx | 6 +
.../main/frontend/state/GameRequestState.ts | 12 +-
.../main/frontend/views/GameRequestView.tsx | 277 ++++++++++++++++++
app/src/main/frontend/views/MainLayout.tsx | 29 +-
.../org/gameyfin/app/config/ConfigEndpoint.kt | 4 +
.../gameyfin/app/config/ConfigProperties.kt | 26 ++
.../app/core/plugins/PluginEndpoint.kt | 17 +-
.../app/core/security/SecurityConfig.kt | 1 +
.../app/core/security/SecurityUtils.kt | 7 +-
.../org/gameyfin/app/games/GameEndpoint.kt | 9 +-
.../app/requests/GameRequestEndpoint.kt | 15 +-
.../app/requests/GameRequestRepository.kt | 9 +
.../app/requests/GameRequestService.kt | 87 +++++-
.../app/requests/dto/GameRequestDto.kt | 4 +-
.../app/requests/entities/GameRequest.kt | 15 +-
.../extensions/GameRequestExtensions.kt | 6 +-
.../app/requests/status/GameRequestStatus.kt | 4 +-
.../app/users/dto/ExtendedUserInfoDto.kt | 1 +
.../org/gameyfin/app/users/dto/UserInfoDto.kt | 1 +
.../app/users/extensions/UserExtensions.kt | 2 +
21 files changed, 460 insertions(+), 74 deletions(-)
create mode 100644 app/src/main/frontend/views/GameRequestView.tsx
diff --git a/app/src/main/frontend/App.tsx b/app/src/main/frontend/App.tsx
index 1aadb07..4cee138 100644
--- a/app/src/main/frontend/App.tsx
+++ b/app/src/main/frontend/App.tsx
@@ -47,10 +47,10 @@ function ViewWithAuth() {
initializeLibraryState();
initializeGameState();
initializeGameRequestState();
+ initializePluginState();
if (isAdmin(auth)) {
initializeScanState();
- initializePluginState();
}
}, [auth]);
diff --git a/app/src/main/frontend/routes.tsx b/app/src/main/frontend/routes.tsx
index 75a9ccc..db7e443 100644
--- a/app/src/main/frontend/routes.tsx
+++ b/app/src/main/frontend/routes.tsx
@@ -24,6 +24,7 @@ import RecentlyAddedView from "Frontend/views/RecentlyAddedView";
import LibraryView from "Frontend/views/LibraryView";
import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js";
import ErrorView from "Frontend/views/ErrorView";
+import GameRequestView from "Frontend/views/GameRequestView";
export const {router, routes} = new RouterConfigurationBuilder()
.withReactRoutes([
@@ -47,6 +48,11 @@ export const {router, routes} = new RouterConfigurationBuilder()
element: ,
handle: {title: 'Recently Added'}
},
+ {
+ path: '/requests',
+ element: ,
+ handle: {title: 'Game requests'}
+ },
{
path: 'library/:libraryId',
element:
diff --git a/app/src/main/frontend/state/GameRequestState.ts b/app/src/main/frontend/state/GameRequestState.ts
index 00d47a4..bdf0dfa 100644
--- a/app/src/main/frontend/state/GameRequestState.ts
+++ b/app/src/main/frontend/state/GameRequestState.ts
@@ -38,13 +38,19 @@ export async function initializeGameRequestState() {
case "created":
case "updated":
//@ts-ignore
- gameRequestState.state[gameRequestEvent.id] = gameRequestEvent;
+ gameRequestState.state[gameRequestEvent.gameRequest.id] = gameRequestEvent.gameRequest;
break;
case "deleted":
//@ts-ignore
- delete gameRequestState.state[gameRequestEvent.id];
+ delete gameRequestState.state[gameRequestEvent.gameRequestId];
break;
}
})
});
-}
\ No newline at end of file
+}
+
+
+
+
+
+
diff --git a/app/src/main/frontend/views/GameRequestView.tsx b/app/src/main/frontend/views/GameRequestView.tsx
new file mode 100644
index 0000000..4346cbc
--- /dev/null
+++ b/app/src/main/frontend/views/GameRequestView.tsx
@@ -0,0 +1,277 @@
+import {
+ Button,
+ Chip,
+ Input,
+ Pagination,
+ Select,
+ SelectItem,
+ SortDescriptor,
+ Table,
+ TableBody,
+ TableCell,
+ TableColumn,
+ TableHeader,
+ TableRow,
+ Tooltip,
+ useDisclosure
+} from "@heroui/react";
+import RequestGameModal from "Frontend/components/general/modals/RequestGameModal";
+import {ArrowUp, Check, PlusCircle, X} from "@phosphor-icons/react";
+import React, {useMemo, useState} from "react";
+import {useAuth} from "Frontend/util/auth";
+import {GameRequestEndpoint} from "Frontend/generated/endpoints";
+import {gameRequestState} from "Frontend/state/GameRequestState";
+import {useSnapshot} from "valtio/react";
+import GameRequestDto from "Frontend/generated/org/gameyfin/app/requests/dto/GameRequestDto";
+import GameRequestStatus from "Frontend/generated/org/gameyfin/app/requests/status/GameRequestStatus";
+import {isAdmin} from "Frontend/util/utils";
+
+export default function GameRequestView() {
+ const rowsPerPage = 25;
+
+ const auth = useAuth();
+ const requestGameModal = useDisclosure();
+ const gameRequests = useSnapshot(gameRequestState).gameRequests
+
+ const [searchTerm, setSearchTerm] = useState("");
+ const [filters, setFilters] = useState<"all" | GameRequestStatus[]>([GameRequestStatus.PENDING, GameRequestStatus.APPROVED, GameRequestStatus.REJECTED]);
+ const [sortDescriptor, setSortDescriptor] = useState({column: "votes", direction: "descending"});
+
+ const [page, setPage] = useState(1);
+ const pages = useMemo(() => {
+ return Math.ceil(getFilteredRequests().length / rowsPerPage);
+ }, [gameRequests, filters]);
+
+ const filteredItems = useMemo(() => {
+ return getFilteredRequests();
+ }, [gameRequests, filters, searchTerm]);
+
+ const sortedItems = useMemo(() => {
+ return (filteredItems as GameRequestDto[]).slice().sort((a, b) => {
+ let cmp: number;
+
+ switch (sortDescriptor.column) {
+ case "title":
+ cmp = a.title.localeCompare(b.title);
+ break;
+ case "votes":
+ cmp = a.voters.length - b.voters.length;
+ if (cmp === 0) {
+ // If votes are equal, sort by creation date (newest first)
+ cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
+ }
+ break;
+ case "status":
+ const statusOrder = {
+ [GameRequestStatus.PENDING]: 1,
+ [GameRequestStatus.APPROVED]: 2,
+ [GameRequestStatus.REJECTED]: 3,
+ [GameRequestStatus.FULFILLED]: 4
+ };
+ cmp = (statusOrder[a.status] || 99) - (statusOrder[b.status] || 99);
+ break;
+ case "createdAt":
+ cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
+ break;
+ case "updatedAt":
+ cmp = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
+ break;
+ default:
+ return 0; // No sorting if the column is not recognized
+ }
+
+ if (sortDescriptor.direction === "descending") {
+ cmp *= -1; // Reverse the comparison if sorting in descending order
+ }
+
+ return cmp;
+ });
+ }, [filteredItems, sortDescriptor]);
+
+ const pagedItems = useMemo(() => {
+ const start = (page - 1) * rowsPerPage;
+ const end = start + rowsPerPage;
+ return sortedItems.slice(start, end);
+ }, [page, sortedItems]);
+
+
+ function getFilteredRequests() {
+ let filteredRequests = (gameRequests as GameRequestDto[]).filter((gameRequest) => {
+ return gameRequest.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (gameRequest.requester && gameRequest.requester.username.toLowerCase().includes(searchTerm.toLowerCase()));
+ });
+
+ filteredRequests = filteredRequests.filter((gameRequest) => {
+ return filters.includes(gameRequest.status);
+ });
+
+ return filteredRequests;
+ }
+
+ async function toggleVote(gameRequestId: number) {
+ await GameRequestEndpoint.toggleVote(gameRequestId);
+ }
+
+ async function toggleApprove(gameRequest: GameRequestDto) {
+ if (gameRequest.status == GameRequestStatus.FULFILLED) return;
+ const newStatus = gameRequest.status === GameRequestStatus.APPROVED ? GameRequestStatus.PENDING : GameRequestStatus.APPROVED;
+ await GameRequestEndpoint.changeStatus(gameRequest.id, newStatus);
+ }
+
+ async function toggleReject(gameRequest: GameRequestDto) {
+ if (gameRequest.status == GameRequestStatus.FULFILLED) return;
+ const newStatus = gameRequest.status === GameRequestStatus.REJECTED ? GameRequestStatus.PENDING : GameRequestStatus.REJECTED;
+ await GameRequestEndpoint.changeStatus(gameRequest.id, newStatus);
+ }
+
+ function hasUserVotedForRequest(gameRequest: GameRequestDto): boolean {
+ if (!auth.state.user) return false;
+ return gameRequest.voters.map(v => v.id).includes(auth.state.user.id);
+ }
+
+ function statusToBadge(status: GameRequestStatus) {
+ switch (status) {
+ case GameRequestStatus.APPROVED:
+ return Approved ;
+ case GameRequestStatus.FULFILLED:
+ return Fulfilled ;
+ case GameRequestStatus.REJECTED:
+ return Rejected ;
+ case GameRequestStatus.PENDING:
+ default:
+ return Pending ;
+ }
+ }
+
+ return (<>
+
+
Game Requests
+
+ }
+ onPress={requestGameModal.onOpen}>
+ Request a Game
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ onClear={() => setSearchTerm("")}
+ />
+ setFilters(Array.from(keys) as any)}
+ selectionMode="multiple"
+ className="w-64"
+ >
+ Pending
+ Approved
+ Fulfilled
+ Rejected
+
+
+
+
+ {pagedItems.length > 0 &&
+ setPage(page)}
+ />}
+
+ }
+ >
+
+ Title & Release
+ Submitted by
+ Submitted
+ Updated
+ Status
+ {/* width={1} keeps the column as far to the right as possible*/}
+ Votes
+
+
+ {(item) => (
+
+
+ {item.title} ({item.release ? new Date(item.release).getFullYear() : "unknown"})
+
+
+
+ {item.requester ?
+ item.requester.username :
+ "Guest"
+ }
+
+
+
+ {new Date(item.createdAt).toLocaleDateString()}
+
+
+ {new Date(item.updatedAt).toLocaleDateString()}
+
+
+ {statusToBadge(item.status)}
+
+
+
+
+ }
+ onPress={async () => await toggleVote(item.id)}>
+ {item.voters.length}
+
+
+ {isAdmin(auth) &&
+
+ await toggleApprove(item as GameRequestDto)}>
+
+
+
+
+ await toggleReject(item as GameRequestDto)}>
+
+
+
+
}
+
+
+
+ )}
+
+
+
+
+
+ >)
+}
\ No newline at end of file
diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx
index b7c1acc..268454d 100644
--- a/app/src/main/frontend/views/MainLayout.tsx
+++ b/app/src/main/frontend/views/MainLayout.tsx
@@ -1,21 +1,11 @@
import {useEffect, useState} from 'react';
import ProfileMenu from "Frontend/components/ProfileMenu";
-import {
- Button,
- Divider,
- Link,
- Navbar,
- NavbarBrand,
- NavbarContent,
- NavbarItem,
- Tooltip,
- useDisclosure
-} from "@heroui/react";
+import {Button, Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Tooltip} from "@heroui/react";
import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
import * as PackageJson from "../../../../package.json";
import {Outlet, useLocation, useNavigate} from "react-router";
import {useAuth} from "Frontend/util/auth";
-import {ArrowLeft, DiceSix, Heart, House, ListMagnifyingGlass, PlusCircle, SignIn} from "@phosphor-icons/react";
+import {ArrowLeft, DiceSix, Disc, Heart, House, ListMagnifyingGlass, SignIn} from "@phosphor-icons/react";
import Confetti, {ConfettiProps} from "react-confetti-boom";
import {useTheme} from "next-themes";
import {useUserPreferenceService} from "Frontend/util/user-preference-service";
@@ -24,7 +14,6 @@ import {useSnapshot} from "valtio/react";
import {gameState} from "Frontend/state/GameState";
import ScanProgressPopover from "Frontend/components/general/ScanProgressPopover";
import {isAdmin} from "Frontend/util/utils";
-import RequestGameModal from "Frontend/components/general/modals/RequestGameModal";
export default function MainLayout() {
const navigate = useNavigate();
@@ -37,8 +26,6 @@ export default function MainLayout() {
const [isExploding, setIsExploding] = useState(false);
const games = useSnapshot(gameState).games;
- const requestGameModal = useDisclosure();
-
useEffect(() => {
userPreferenceService.sync()
.then(() => loadUserTheme().catch(console.error))
@@ -110,9 +97,11 @@ export default function MainLayout() {
{auth.state.user &&
-
-
+ navigate("/requests")}
+ startContent={ }>
+ Requests
@@ -165,10 +154,6 @@ export default function MainLayout() {
-
-
-
);
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
index 50ddede..395c038 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
@@ -55,4 +55,8 @@ class ConfigEndpoint(
@DynamicPublicAccess
@AnonymousAllowed
fun isPublicAccessEnabled(): Boolean = configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true
+
+ @DynamicPublicAccess
+ @AnonymousAllowed
+ fun areGameRequestsEnabled(): Boolean = configService.get(ConfigProperties.Requests.Games.Enabled) == true
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
index a65cf0a..d8db3b3 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
@@ -103,6 +103,32 @@ sealed class ConfigProperties(
}
}
+ /** Requests */
+ sealed class Requests {
+ sealed class Games {
+ data object Enabled : ConfigProperties(
+ Boolean::class,
+ "requests.games.enabled",
+ "Enable game requests",
+ true
+ )
+
+ data object AllowGuestsToRequestGames : ConfigProperties(
+ Boolean::class,
+ "requests.games.allow-guests-to-request-games",
+ "Allow guests (not logged in) to create game requests",
+ false
+ )
+
+ data object MaxOpenRequestsPerUser : ConfigProperties(
+ Int::class,
+ "requests.games.max-open-requests-per-user",
+ "Maximum number of open (not yet fulfilled or rejected) requests per user. Set to 0 for unlimited.",
+ 10
+ )
+ }
+ }
+
/** User management */
sealed class Users {
sealed class SignUps {
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginEndpoint.kt
index 94be2f4..049db72 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/plugins/PluginEndpoint.kt
@@ -1,41 +1,46 @@
package org.gameyfin.app.core.plugins
+import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
-import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role
+import org.gameyfin.app.core.annotations.DynamicPublicAccess
import org.gameyfin.app.core.plugins.dto.PluginUpdateDto
-import org.gameyfin.app.core.security.isCurrentUserAdmin
import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import reactor.core.publisher.Flux
@Endpoint
-@RolesAllowed(Role.Names.ADMIN)
+@DynamicPublicAccess
+@AnonymousAllowed
class PluginEndpoint(
private val pluginService: PluginService,
) {
- @PermitAll
fun subscribe(): Flux> {
- return if (isCurrentUserAdmin()) PluginService.subscribe()
- else Flux.empty()
+ return PluginService.subscribe()
}
fun getAll() = pluginService.getAll().sortedByDescending { it.priority }
+ @RolesAllowed(Role.Names.ADMIN)
fun enablePlugin(pluginId: String) = pluginService.enablePlugin(pluginId)
+ @RolesAllowed(Role.Names.ADMIN)
fun disablePlugin(pluginId: String) = pluginService.disablePlugin(pluginId)
+ @RolesAllowed(Role.Names.ADMIN)
fun setPluginPriorities(pluginPriorities: Map) =
pluginService.setPluginPriorities(pluginPriorities)
+ @RolesAllowed(Role.Names.ADMIN)
fun validatePluginConfig(pluginId: String): PluginConfigValidationResult =
pluginService.validatePluginConfig(pluginId, true)
+ @RolesAllowed(Role.Names.ADMIN)
fun validateNewConfig(pluginId: String, config: Map): PluginConfigValidationResult =
pluginService.validatePluginConfig(pluginId, config)
+ @RolesAllowed(Role.Names.ADMIN)
fun updateConfig(pluginId: String, updatedConfig: Map) =
pluginService.updateConfig(pluginId, updatedConfig)
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
index 3caaa62..23933d9 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
@@ -53,6 +53,7 @@ class SecurityConfig(
.requestMatchers("/game/**").access(DynamicPublicAccessAuthorizationManager(config))
.requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config))
.requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config))
+ .requestMatchers("/requests/**").access(DynamicPublicAccessAuthorizationManager(config))
.requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config))
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
index 25113b0..385162b 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
@@ -9,6 +9,9 @@ fun getCurrentAuth(): Authentication? {
}
fun isCurrentUserAdmin(): Boolean {
- return getCurrentAuth()?.authorities?.any { it.authority == Role.Names.ADMIN || it.authority == Role.Names.SUPERADMIN }
- ?: false
+ return getCurrentAuth()?.isAdmin() ?: false
+}
+
+fun Authentication.isAdmin(): Boolean {
+ return this.authorities?.any { it.authority == Role.Names.ADMIN || it.authority == Role.Names.SUPERADMIN } ?: false
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
index 9f33c51..e7ec428 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
@@ -34,6 +34,10 @@ class GameEndpoint(
fun getAll(): List = gameService.getAll()
+ fun getPotentialMatches(searchTerm: String): List {
+ return gameService.getPotentialMatches(searchTerm)
+ }
+
@RolesAllowed(Role.Names.ADMIN)
fun updateGame(game: GameUpdateDto) = gameService.edit(game)
@@ -43,11 +47,6 @@ class GameEndpoint(
gameService.delete(gameId)
}
- @RolesAllowed(Role.Names.ADMIN)
- fun getPotentialMatches(searchTerm: String): List {
- return gameService.getPotentialMatches(searchTerm)
- }
-
@RolesAllowed(Role.Names.ADMIN)
fun matchManually(
originalIds: Map,
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
index 533cfdb..37d25a8 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestEndpoint.kt
@@ -4,6 +4,7 @@ import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed
+import org.gameyfin.app.config.ConfigService
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.annotations.DynamicPublicAccess
import org.gameyfin.app.requests.dto.GameRequestCreationDto
@@ -17,7 +18,8 @@ import reactor.core.publisher.Flux
@DynamicPublicAccess
@AnonymousAllowed
class GameRequestEndpoint(
- private val gameRequestService: GameRequestService
+ private val gameRequestService: GameRequestService,
+ private val config: ConfigService
) {
fun subscribe(): Flux> {
@@ -26,7 +28,6 @@ class GameRequestEndpoint(
fun getAll() = gameRequestService.getAll()
- @PermitAll
fun create(gameRequest: GameRequestCreationDto) {
gameRequestService.createRequest(gameRequest)
}
@@ -36,13 +37,13 @@ class GameRequestEndpoint(
gameRequestService.toggleRequestVote(gameRequestId)
}
+ @PermitAll
+ fun delete(gameRequestId: Long) {
+ gameRequestService.deleteRequest(gameRequestId)
+ }
+
@RolesAllowed(Role.Names.ADMIN)
fun changeStatus(gameRequestId: Long, newStatus: GameRequestStatus) {
gameRequestService.changeRequestStatus(gameRequestId, newStatus)
}
-
- @RolesAllowed(Role.Names.ADMIN)
- fun delete(gameRequestId: Long) {
- gameRequestService.deleteRequest(gameRequestId)
- }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
index 12e9f43..433077a 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
@@ -17,4 +17,13 @@ interface GameRequestRepository : JpaRepository {
GameRequestStatus.REJECTED
)
): List
+
+ @Query("SELECT g FROM GameRequest g WHERE g.requester.id = :requesterId AND g.status NOT IN (:excludedStatuses)")
+ fun findOpenRequestsByRequesterId(
+ @Param("requesterId") requesterId: Long?,
+ @Param("excludedStatuses") excludedStatuses: List = listOf(
+ GameRequestStatus.FULFILLED,
+ GameRequestStatus.REJECTED
+ )
+ ): List
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
index 0551c1d..665c5f6 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -1,8 +1,11 @@
package org.gameyfin.app.requests
import io.github.oshai.kotlinlogging.KotlinLogging
+import org.gameyfin.app.config.ConfigProperties
+import org.gameyfin.app.config.ConfigService
import org.gameyfin.app.core.events.GameCreatedEvent
import org.gameyfin.app.core.security.getCurrentAuth
+import org.gameyfin.app.core.security.isAdmin
import org.gameyfin.app.requests.dto.GameRequestCreationDto
import org.gameyfin.app.requests.dto.GameRequestDto
import org.gameyfin.app.requests.dto.GameRequestEvent
@@ -11,9 +14,11 @@ import org.gameyfin.app.requests.extensions.toDto
import org.gameyfin.app.requests.extensions.toDtos
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.UserService
+import org.gameyfin.app.users.entities.User
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
+import org.springframework.transaction.annotation.Transactional
import reactor.core.publisher.Flux
import reactor.core.publisher.Sinks
import kotlin.time.Duration.Companion.milliseconds
@@ -22,7 +27,8 @@ import kotlin.time.toJavaDuration
@Service
class GameRequestService(
private val gameRequestRepository: GameRequestRepository,
- private val userService: UserService
+ private val userService: UserService,
+ private val config: ConfigService
) {
companion object {
@@ -54,33 +60,82 @@ class GameRequestService(
}
fun createRequest(gameRequest: GameRequestCreationDto) {
- val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
- val currentUser =
- userService.getByUsername(auth.name) ?: throw IllegalStateException("Current user not found")
- val gameRequest = GameRequest(
+ // Check if requests are enabled
+ if (config.get(ConfigProperties.Requests.Games.Enabled) != true) {
+ throw IllegalStateException("Game requests are disabled")
+ }
+
+ // Check if a request with the same title and release year already exists
+ val existingRequests = gameRequestRepository.findOpenRequestsByTitleAndReleaseYear(
+ gameRequest.title,
+ gameRequest.release,
+ emptyList()
+ )
+ if (existingRequests.isNotEmpty()) {
+ throw IllegalStateException("A request for this game already exists (ID: ${existingRequests[0].id})")
+ }
+
+ val auth = getCurrentAuth()
+ val currentUser = auth?.let { userService.getByUsername(it.name) }
+
+ // Check if guests are allowed to create requests
+ if (config.get(ConfigProperties.Requests.Games.AllowGuestsToRequestGames) != true && currentUser == null) {
+ throw IllegalStateException("Only registered users can create game requests")
+ }
+
+ // Check if user has too many open requests (0 means no limit per user)
+ // Note: All guests are treated as a single user with null ID and thus share their request limit
+ // Note: Admins are exempt from this limit
+ val openRequestsForUser = gameRequestRepository.findOpenRequestsByRequesterId(currentUser?.id)
+ val maxRequestsPerUser = config.get(ConfigProperties.Requests.Games.MaxOpenRequestsPerUser) ?: 0
+ if (maxRequestsPerUser == 0 || (auth?.isAdmin() != true && openRequestsForUser.size >= maxRequestsPerUser)) {
+ throw IllegalStateException("You have reached the maximum number of open requests (${maxRequestsPerUser})")
+ }
+
+ val newGameRequest = GameRequest(
title = gameRequest.title,
release = gameRequest.release,
status = GameRequestStatus.PENDING,
- requester = currentUser
+ requester = currentUser,
+ voters = mutableSetOf().apply {
+ currentUser?.let { add(it) }
+ }
)
- gameRequestRepository.save(gameRequest)
+ gameRequestRepository.save(newGameRequest)
}
fun deleteRequest(id: Long) {
val gameRequest = gameRequestRepository.findById(id)
.orElseThrow { NoSuchElementException("No game request found with id $id") }
+
+ val auth = getCurrentAuth()
+ val currentUser = auth?.let { userService.getByUsername(it.name) }
+ val requester = gameRequest.requester
+
+ // Check if the current user is the requester or an admin
+ if (auth?.isAdmin() != true || requester == null || requester.id != currentUser?.id) {
+ throw IllegalStateException("Only the requester or an admin can delete a game request")
+ }
+
gameRequestRepository.delete(gameRequest)
}
fun changeRequestStatus(id: Long, status: GameRequestStatus) {
val gameRequest = gameRequestRepository.findById(id)
.orElseThrow { NoSuchElementException("No game request found with id $id") }
+
+ if (gameRequest.status == GameRequestStatus.FULFILLED) {
+ log.debug { "Status of requests with status ${GameRequestStatus.FULFILLED} can't be changed" }
+ return
+ }
+
gameRequest.status = status
gameRequestRepository.save(gameRequest)
}
+ @Transactional
fun toggleRequestVote(id: Long) {
val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val currentUser =
@@ -88,15 +143,17 @@ class GameRequestService(
val gameRequest = gameRequestRepository.findById(id)
.orElseThrow { NoSuchElementException("No game request found with id $id") }
- if (gameRequest.requester.id == currentUser.id) {
- throw IllegalStateException("You cannot vote for your own request")
- }
-
- if (gameRequest.voters.contains(currentUser)) {
- gameRequest.voters.remove(currentUser)
+ // Replace the voters collection to ensure Hibernate detects the change
+ val updatedVoters = gameRequest.voters.toMutableSet()
+ if (updatedVoters.contains(currentUser)) {
+ updatedVoters.remove(currentUser)
} else {
- gameRequest.voters.add(currentUser)
+ updatedVoters.add(currentUser)
}
+ gameRequest.voters = updatedVoters
+
+ // Ensure the entity is marked as dirty
+ gameRequest.status = gameRequest.status
gameRequestRepository.save(gameRequest)
}
@@ -109,7 +166,7 @@ class GameRequestService(
val gameRelease = game.release
if (gameTitle == null) {
- log.debug { "Game '${game.id}' is missing title, cannot complete matching requests" }
+ log.warn { "Game '${game.id}' is missing title, cannot complete matching requests" }
return
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
index 7932c90..e5ba423 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/dto/GameRequestDto.kt
@@ -11,6 +11,6 @@ class GameRequestDto(
val status: GameRequestStatus,
val requester: UserInfoDto?,
val voters: List,
- val createdAt: Instant?,
- val updatedAt: Instant?
+ val createdAt: Instant,
+ val updatedAt: Instant
)
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
index 0fbd7e9..5444092 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/entities/GameRequest.kt
@@ -4,12 +4,13 @@ import jakarta.persistence.*
import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.entities.User
import org.hibernate.annotations.CreationTimestamp
+import org.hibernate.annotations.OnDelete
+import org.hibernate.annotations.OnDeleteAction
import org.hibernate.annotations.UpdateTimestamp
-import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.Instant
@Entity
-@EntityListeners(GameRequestEntityListener::class, AuditingEntityListener::class)
+@EntityListeners(GameRequestEntityListener::class)
class GameRequest(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@@ -25,11 +26,13 @@ class GameRequest(
@Enumerated(EnumType.STRING)
var status: GameRequestStatus,
- @ManyToOne(fetch = FetchType.EAGER, cascade = [CascadeType.ALL])
- var requester: User,
+ @ManyToOne(fetch = FetchType.EAGER)
+ @OnDelete(action = OnDeleteAction.SET_NULL)
+ var requester: User? = null,
- @OneToMany(fetch = FetchType.EAGER, cascade = [CascadeType.ALL], orphanRemoval = true)
- var voters: MutableList = mutableListOf(),
+ @ManyToMany(fetch = FetchType.EAGER)
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ var voters: MutableSet = mutableSetOf(),
var linkedGameId: Long? = null,
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
index d798387..e9e1746 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/extensions/GameRequestExtensions.kt
@@ -10,10 +10,10 @@ fun GameRequest.toDto(): GameRequestDto {
title = this.title,
release = this.release,
status = this.status,
- requester = this.requester.toUserInfoDto(),
+ requester = this.requester?.toUserInfoDto(),
voters = this.voters.map { it.toUserInfoDto() },
- createdAt = this.createdAt,
- updatedAt = this.updatedAt
+ createdAt = this.createdAt!!,
+ updatedAt = this.updatedAt!!
)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt b/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt
index 290c677..ecbe7ea 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/status/GameRequestStatus.kt
@@ -3,6 +3,6 @@ package org.gameyfin.app.requests.status
enum class GameRequestStatus {
PENDING,
APPROVED,
- FULFILLED,
- REJECTED
+ REJECTED,
+ FULFILLED
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt b/app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt
index d125756..3486e62 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/dto/ExtendedUserInfoDto.kt
@@ -3,6 +3,7 @@ package org.gameyfin.app.users.dto
import org.gameyfin.app.core.Role
data class ExtendedUserInfoDto(
+ val id: Long,
val username: String,
val managedBySso: Boolean,
val email: String,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
index d975a85..c1dc8e4 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/dto/UserInfoDto.kt
@@ -1,6 +1,7 @@
package org.gameyfin.app.users.dto
data class UserInfoDto(
+ val id: Long,
val username: String,
val hasAvatar: Boolean,
val avatarId: Long? = null,
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt
index 77e715f..5265063 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/extensions/UserExtensions.kt
@@ -9,6 +9,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority
fun User.toUserInfoDto(): UserInfoDto {
return UserInfoDto(
+ id = this.id!!,
username = this.username,
hasAvatar = this.avatar != null,
avatarId = this.avatar?.id
@@ -17,6 +18,7 @@ fun User.toUserInfoDto(): UserInfoDto {
fun User.toExtendedUserInfoDto(): ExtendedUserInfoDto {
return ExtendedUserInfoDto(
+ id = this.id!!,
username = this.username,
email = this.email,
emailConfirmed = this.emailConfirmed,
From c50c3e43bb6c083b19f257b3b7c9c4b427e230f0 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 3 Sep 2025 10:13:55 +0200
Subject: [PATCH 11/32] Upgrade Hilla version
---
gradle.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index a6935cc..7a18b27 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,7 +5,7 @@ org.gradle.caching=true
# Plugin versions
kotlinVersion=2.2.0
kspVersion=2.2.0-2.0.2
-vaadinVersion=24.8.3
+vaadinVersion=24.8.7
springBootVersion=3.5.3
springCloudVersion=2025.0.0
springDependencyManagementVersion=1.1.7
From 6e86584fd850090b575dc5b3e77b42aca4e0eeac Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 3 Sep 2025 10:26:17 +0200
Subject: [PATCH 12/32] Add permission checks Add UI for config
---
app/package-lock.json | 1922 +++++++----------
app/package.json | 218 +-
.../administration/GameRequestManagement.tsx | 49 +
.../general/modals/RequestGameModal.tsx | 18 +-
app/src/main/frontend/routes.tsx | 6 +
.../frontend/views/AdministrationView.tsx | 7 +-
.../main/frontend/views/GameRequestView.tsx | 40 +-
app/src/main/frontend/views/MainLayout.tsx | 6 +-
.../gameyfin/app/config/ConfigProperties.kt | 4 +-
.../app/games/repositories/GameRepository.kt | 11 +-
.../app/requests/GameRequestRepository.kt | 26 +-
.../app/requests/GameRequestService.kt | 45 +-
12 files changed, 1072 insertions(+), 1280 deletions(-)
create mode 100644 app/src/main/frontend/components/administration/GameRequestManagement.tsx
diff --git a/app/package-lock.json b/app/package-lock.json
index 6bf65fb..0532cf8 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gameyfin",
- "version": "2.0.0.RC1",
+ "version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gameyfin",
- "version": "2.0.0.RC1",
+ "version": "2.0.1",
"dependencies": {
"@heroui/react": "2.7.9",
"@material-tailwind/react": "^2.1.10",
@@ -14,22 +14,22 @@
"@polymer/polymer": "3.5.2",
"@react-stately/data": "^3.12.2",
"@react-types/shared": "^3.28.0",
- "@vaadin/bundles": "24.8.3",
+ "@vaadin/bundles": "24.8.6",
"@vaadin/common-frontend": "0.0.19",
- "@vaadin/hilla-file-router": "24.8.2",
- "@vaadin/hilla-frontend": "24.8.2",
- "@vaadin/hilla-lit-form": "24.8.2",
- "@vaadin/hilla-react-auth": "24.8.2",
- "@vaadin/hilla-react-crud": "24.8.2",
- "@vaadin/hilla-react-form": "24.8.2",
- "@vaadin/hilla-react-i18n": "24.8.2",
- "@vaadin/hilla-react-signals": "24.8.2",
- "@vaadin/polymer-legacy-adapter": "24.8.3",
- "@vaadin/react-components": "24.8.3",
+ "@vaadin/hilla-file-router": "24.8.7",
+ "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-lit-form": "24.8.7",
+ "@vaadin/hilla-react-auth": "24.8.7",
+ "@vaadin/hilla-react-crud": "24.8.7",
+ "@vaadin/hilla-react-form": "24.8.7",
+ "@vaadin/hilla-react-i18n": "24.8.7",
+ "@vaadin/hilla-react-signals": "24.8.7",
+ "@vaadin/polymer-legacy-adapter": "24.8.6",
+ "@vaadin/react-components": "24.8.6",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.3",
- "@vaadin/vaadin-material-styles": "24.8.3",
- "@vaadin/vaadin-themable-mixin": "24.8.3",
+ "@vaadin/vaadin-lumo-styles": "24.8.6",
+ "@vaadin/vaadin-material-styles": "24.8.6",
+ "@vaadin/vaadin-themable-mixin": "24.8.6",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"classnames": "^2.5.1",
"construct-style-sheets-polyfill": "3.1.0",
@@ -66,17 +66,17 @@
"@types/node": "^22.4.0",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
- "@vaadin/hilla-generator-cli": "24.8.2",
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.2",
- "@vaadin/hilla-generator-plugin-barrel": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-plugin-model": "24.8.2",
- "@vaadin/hilla-generator-plugin-push": "24.8.2",
- "@vaadin/hilla-generator-plugin-signals": "24.8.2",
- "@vaadin/hilla-generator-plugin-subtypes": "24.8.2",
- "@vaadin/hilla-generator-plugin-transfertypes": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-cli": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
+ "@vaadin/hilla-generator-plugin-barrel": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-plugin-model": "24.8.7",
+ "@vaadin/hilla-generator-plugin-push": "24.8.7",
+ "@vaadin/hilla-generator-plugin-signals": "24.8.7",
+ "@vaadin/hilla-generator-plugin-subtypes": "24.8.7",
+ "@vaadin/hilla-generator-plugin-transfertypes": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"@vitejs/plugin-react": "4.5.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"async": "3.2.6",
@@ -6699,201 +6699,6 @@
}
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
- "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
- "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
- "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
- "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
- "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
- "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
- "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
- "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
- "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
- "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
- "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
- "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
- "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.38.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz",
@@ -6908,96 +6713,6 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
- "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
- "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
- "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
- "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
- "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
- "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "peer": true
- },
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -7476,168 +7191,168 @@
"license": "ISC"
},
"node_modules/@vaadin/a11y-base": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-24.8.3.tgz",
- "integrity": "sha512-RbNp466eAqR1FFzDeCAtTvZb7ffmys7Wmb9Klk88pC9lPjnMySvPhLO2EtV5soSE1WFQAoNSOCblSqDvjTp1Dg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-24.8.6.tgz",
+ "integrity": "sha512-c8SEpMbgdOXtfHG7HS4FT80UnVQ1TiOx2g7F2YWkJDGLJ+/GjiX7dGZfX3zn92U8Yc3voSCB0JsOu9OCtnGQTw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/accordion": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/accordion/-/accordion-24.8.3.tgz",
- "integrity": "sha512-UAzqnptUjzf/OQc6gDflomXKWrNYllY4vBJwF9vK7lOvDOvB1uUxQMnvgKQCwyJJTj1Hkgj8Pf92z/Mw5u8HIQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/accordion/-/accordion-24.8.6.tgz",
+ "integrity": "sha512-aXvLcxUWFA5Q7W07aTAbnSairTmahQS5pKffswWzawfhBq8XHxRAXuui5lzOcmGyagV0SUP5di3hq8D3p8J9Nw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/details": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/details": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/app-layout": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/app-layout/-/app-layout-24.8.3.tgz",
- "integrity": "sha512-GN+fCGs08lqHoV/t4RGO1umQKYBKySe1rTunQIfS6iidrLS+w3QFAJnQqApl/N+c19AcX6myn+tkbcKvluKZgQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/app-layout/-/app-layout-24.8.6.tgz",
+ "integrity": "sha512-Jl+RciFBnQlLImk3zkl/0TQrQmeuxf4MYqfsQ1oW9xdOEeR04VJ6Cw5BM+gG9I8PfszlyHYbFmnTE3JurRK7cA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/avatar": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/avatar/-/avatar-24.8.3.tgz",
- "integrity": "sha512-IvznGJRgvtPT3i12+9KyhXs5QCD2QPlI8XuSqR73GnYmBaOCJEPf6jGaFUNr68CHB8Lphytc/w9ryii/8nDERg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/avatar/-/avatar-24.8.6.tgz",
+ "integrity": "sha512-ZWe5g5ZorJXyd3d8DqDQkJ6TpGkwTsOGMQQgEWVVs00DyYENFCs4dF+jL9taYXfr7DF8x58XIS9aARQpKWnyIQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/tooltip": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/tooltip": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/avatar-group": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/avatar-group/-/avatar-group-24.8.3.tgz",
- "integrity": "sha512-Ml6DoKGBEd9cnAyaRwKKm8P1i96SaGAUlXXPXNLCSLIXO3bxo5Lezma8WDii6HHIOfsFXhL9x9ATNBDNuYd7fw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/avatar-group/-/avatar-group-24.8.6.tgz",
+ "integrity": "sha512-sGGzMNbsIigD8ijjyMgqmPJ4Qir7IhUSu87pl17Iy3nNVIXk9hsoIPTKoDbPPCzuRHo+bTEEYKeGqGJFKeAFeA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/avatar": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/list-box": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/tooltip": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/avatar": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/list-box": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/tooltip": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/bundles": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/bundles/-/bundles-24.8.3.tgz",
- "integrity": "sha512-7MccSIvAC5L7q2LTumizigUE4rPshQWAq67Id2oH9HJCqBzsn6qBoG5GsQgZjEorp5/K2c0oYCYstI6hamZICw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/bundles/-/bundles-24.8.6.tgz",
+ "integrity": "sha512-/7R6FNJuD78UUT4csMuAEZhf0Qgz7zJ4mIvhG/uUhJauV48S9zi1RFp0NLRIpn41LRjb1hLEyMIlbjA9k2JJkw==",
"license": "(Apache-2.0 OR SEE LICENSE IN https://vaadin.com/license/cvdl-4.0)",
"peerDependencies": {
"@open-wc/dedupe-mixin": "1.4.0",
"@polymer/polymer": "3.5.2",
- "@vaadin/a11y-base": "24.8.3",
- "@vaadin/accordion": "24.8.3",
- "@vaadin/app-layout": "24.8.3",
- "@vaadin/avatar": "24.8.3",
- "@vaadin/avatar-group": "24.8.3",
- "@vaadin/board": "24.8.3",
- "@vaadin/button": "24.8.3",
- "@vaadin/card": "24.8.3",
- "@vaadin/charts": "24.8.3",
- "@vaadin/checkbox": "24.8.3",
- "@vaadin/checkbox-group": "24.8.3",
- "@vaadin/combo-box": "24.8.3",
- "@vaadin/component-base": "24.8.3",
- "@vaadin/confirm-dialog": "24.8.3",
- "@vaadin/context-menu": "24.8.3",
- "@vaadin/cookie-consent": "24.8.3",
- "@vaadin/crud": "24.8.3",
- "@vaadin/custom-field": "24.8.3",
- "@vaadin/dashboard": "24.8.3",
- "@vaadin/date-picker": "24.8.3",
- "@vaadin/date-time-picker": "24.8.3",
- "@vaadin/details": "24.8.3",
- "@vaadin/dialog": "24.8.3",
- "@vaadin/email-field": "24.8.3",
- "@vaadin/field-base": "24.8.3",
- "@vaadin/field-highlighter": "24.8.3",
- "@vaadin/form-layout": "24.8.3",
- "@vaadin/grid": "24.8.3",
- "@vaadin/grid-pro": "24.8.3",
- "@vaadin/horizontal-layout": "24.8.3",
- "@vaadin/icon": "24.8.3",
- "@vaadin/icons": "24.8.3",
- "@vaadin/input-container": "24.8.3",
- "@vaadin/integer-field": "24.8.3",
- "@vaadin/item": "24.8.3",
- "@vaadin/list-box": "24.8.3",
- "@vaadin/lit-renderer": "24.8.3",
- "@vaadin/login": "24.8.3",
- "@vaadin/map": "24.8.3",
- "@vaadin/markdown": "24.8.3",
- "@vaadin/master-detail-layout": "24.8.3",
- "@vaadin/menu-bar": "24.8.3",
- "@vaadin/message-input": "24.8.3",
- "@vaadin/message-list": "24.8.3",
- "@vaadin/multi-select-combo-box": "24.8.3",
- "@vaadin/notification": "24.8.3",
- "@vaadin/number-field": "24.8.3",
- "@vaadin/overlay": "24.8.3",
- "@vaadin/password-field": "24.8.3",
- "@vaadin/polymer-legacy-adapter": "24.8.3",
- "@vaadin/popover": "24.8.3",
- "@vaadin/progress-bar": "24.8.3",
- "@vaadin/radio-group": "24.8.3",
- "@vaadin/rich-text-editor": "24.8.3",
- "@vaadin/scroller": "24.8.3",
- "@vaadin/select": "24.8.3",
- "@vaadin/side-nav": "24.8.3",
- "@vaadin/split-layout": "24.8.3",
- "@vaadin/tabs": "24.8.3",
- "@vaadin/tabsheet": "24.8.3",
- "@vaadin/text-area": "24.8.3",
- "@vaadin/text-field": "24.8.3",
- "@vaadin/time-picker": "24.8.3",
- "@vaadin/tooltip": "24.8.3",
- "@vaadin/upload": "24.8.3",
+ "@vaadin/a11y-base": "24.8.6",
+ "@vaadin/accordion": "24.8.6",
+ "@vaadin/app-layout": "24.8.6",
+ "@vaadin/avatar": "24.8.6",
+ "@vaadin/avatar-group": "24.8.6",
+ "@vaadin/board": "24.8.6",
+ "@vaadin/button": "24.8.6",
+ "@vaadin/card": "24.8.6",
+ "@vaadin/charts": "24.8.6",
+ "@vaadin/checkbox": "24.8.6",
+ "@vaadin/checkbox-group": "24.8.6",
+ "@vaadin/combo-box": "24.8.6",
+ "@vaadin/component-base": "24.8.6",
+ "@vaadin/confirm-dialog": "24.8.6",
+ "@vaadin/context-menu": "24.8.6",
+ "@vaadin/cookie-consent": "24.8.6",
+ "@vaadin/crud": "24.8.6",
+ "@vaadin/custom-field": "24.8.6",
+ "@vaadin/dashboard": "24.8.6",
+ "@vaadin/date-picker": "24.8.6",
+ "@vaadin/date-time-picker": "24.8.6",
+ "@vaadin/details": "24.8.6",
+ "@vaadin/dialog": "24.8.6",
+ "@vaadin/email-field": "24.8.6",
+ "@vaadin/field-base": "24.8.6",
+ "@vaadin/field-highlighter": "24.8.6",
+ "@vaadin/form-layout": "24.8.6",
+ "@vaadin/grid": "24.8.6",
+ "@vaadin/grid-pro": "24.8.6",
+ "@vaadin/horizontal-layout": "24.8.6",
+ "@vaadin/icon": "24.8.6",
+ "@vaadin/icons": "24.8.6",
+ "@vaadin/input-container": "24.8.6",
+ "@vaadin/integer-field": "24.8.6",
+ "@vaadin/item": "24.8.6",
+ "@vaadin/list-box": "24.8.6",
+ "@vaadin/lit-renderer": "24.8.6",
+ "@vaadin/login": "24.8.6",
+ "@vaadin/map": "24.8.6",
+ "@vaadin/markdown": "24.8.6",
+ "@vaadin/master-detail-layout": "24.8.6",
+ "@vaadin/menu-bar": "24.8.6",
+ "@vaadin/message-input": "24.8.6",
+ "@vaadin/message-list": "24.8.6",
+ "@vaadin/multi-select-combo-box": "24.8.6",
+ "@vaadin/notification": "24.8.6",
+ "@vaadin/number-field": "24.8.6",
+ "@vaadin/overlay": "24.8.6",
+ "@vaadin/password-field": "24.8.6",
+ "@vaadin/polymer-legacy-adapter": "24.8.6",
+ "@vaadin/popover": "24.8.6",
+ "@vaadin/progress-bar": "24.8.6",
+ "@vaadin/radio-group": "24.8.6",
+ "@vaadin/rich-text-editor": "24.8.6",
+ "@vaadin/scroller": "24.8.6",
+ "@vaadin/select": "24.8.6",
+ "@vaadin/side-nav": "24.8.6",
+ "@vaadin/split-layout": "24.8.6",
+ "@vaadin/tabs": "24.8.6",
+ "@vaadin/tabsheet": "24.8.6",
+ "@vaadin/text-area": "24.8.6",
+ "@vaadin/text-field": "24.8.6",
+ "@vaadin/time-picker": "24.8.6",
+ "@vaadin/tooltip": "24.8.6",
+ "@vaadin/upload": "24.8.6",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.3",
- "@vaadin/vaadin-themable-mixin": "24.8.3",
+ "@vaadin/vaadin-lumo-styles": "24.8.6",
+ "@vaadin/vaadin-themable-mixin": "24.8.6",
"@vaadin/vaadin-usage-statistics": "2.1.3",
- "@vaadin/vertical-layout": "24.8.3",
- "@vaadin/virtual-list": "24.8.3",
+ "@vaadin/vertical-layout": "24.8.6",
+ "@vaadin/virtual-list": "24.8.6",
"cookieconsent": "3.1.1",
"dompurify": "3.2.6",
"highcharts": "9.2.2",
@@ -7894,87 +7609,87 @@
}
},
"node_modules/@vaadin/button": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/button/-/button-24.8.3.tgz",
- "integrity": "sha512-WqDpZe/Db994Lz5qkp5GO2KxDJOn7h6MetJszOOeHS2BZyQh8uyCmfL88b8NQMFOgU/nFfrKLzL8/3mPkE3v9A==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/button/-/button-24.8.6.tgz",
+ "integrity": "sha512-mXZLt2u3elJ3WMLaw35zLIpZiPPB+hlpKdfTlOF22vDm1OIalVYpQlGcdDDOshtIGaUH1W3eb1MmlQhVAEFmkA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/card": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/card/-/card-24.8.3.tgz",
- "integrity": "sha512-cUFAZKTTnVBjvbXkEPiroPtDHLHCM8+LER7OFMdOX7MCRxFQA5hh4cDUjxnnBWBhCqu/NwxgaumbOimciNx3fg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/card/-/card-24.8.6.tgz",
+ "integrity": "sha512-VgnQUkHY8I06INUSwXuNOrevG7PYQsBKr6k1r42KqEzLESiWV/pk+WpdyGRTEH4MbKc8oj6Ut6Uu6DtUpgK+eg==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/checkbox": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-24.8.3.tgz",
- "integrity": "sha512-FMg6EtgkVFRxwCy1C6LIidt472Ix8VYWE6pkAaDCBpzs/mQaFWPF0jRRxUyxJcYQmNP1/WoKTHAror19t+EbYw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-24.8.6.tgz",
+ "integrity": "sha512-wLaKHniu8MKO4sfKJMiC+F/fMZVi1l2CpRw3RTTFzKYQMeBVwVRv3Lg1yV75LN8KtHU0DBR5l2Xfb4pa+rRGFg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/checkbox-group": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/checkbox-group/-/checkbox-group-24.8.3.tgz",
- "integrity": "sha512-E/6V4H619i6N0B56pp2BoU2gYhbQyS2nQsoeJceyrBAEVri9pKT2NGumqDqHSGetk8x2dXlZWUIUJJN1OY5BrQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/checkbox-group/-/checkbox-group-24.8.6.tgz",
+ "integrity": "sha512-odQ9PnF5SfWAniYnhKb6gEDPkn0ZO+P7gK5AUit3TadeAGc9t2+7BgqcJlg0o6fflgfEwD/RKWV6PXCU30BVEw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/checkbox": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/checkbox": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/combo-box": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/combo-box/-/combo-box-24.8.3.tgz",
- "integrity": "sha512-H3oEpAgzCD6Pi37auXW4HIB/d99Uz5xtV+cjkQ6opMW1nOYSbRfJazxfl4yvoaD9PLok/IyN2i50saSiTlpoCw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/combo-box/-/combo-box-24.8.6.tgz",
+ "integrity": "sha512-baeU/8jn7JdV4R3tqoVJMHc3wsMeo4RMREPAYjiQ1T4q1378cOpIixoPgXBnRj0g2bGwVD0dCHXcfb0BtVo9KQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
@@ -7991,9 +7706,9 @@
}
},
"node_modules/@vaadin/component-base": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-24.8.3.tgz",
- "integrity": "sha512-ZtMnFZdlnEXpOaiatCaOd156oNnJeQO5CH5cUMf8/hQoONMSrsXGTi+nFsrn88VRPbKD0RhnoZq6lt7eOudjKQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-24.8.6.tgz",
+ "integrity": "sha512-EnhFwUXkZ53AASaPMJukI0w/DTSM74IXh+j65GUcFbrlN/S7tdFCQhtIsaLX95pcEtestSXOSufI/kW/B+6hgA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
@@ -8004,223 +7719,223 @@
}
},
"node_modules/@vaadin/confirm-dialog": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/confirm-dialog/-/confirm-dialog-24.8.3.tgz",
- "integrity": "sha512-dSGlmNQh/Cejj7sCmS09v072Pq6noM2XveOp1N3e8lJFf/KHQcl+RORoPrNaDm741yApkJgTfR7tW3qX8qbtIw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/confirm-dialog/-/confirm-dialog-24.8.6.tgz",
+ "integrity": "sha512-Cm0bJFSfd+MU++d6N15XypSzpJ/1wcF8/NmTKMOW3falJOMHNl3F5/pzo1I24PBDjM/moQrXtOjUS5eKD6nf7g==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/dialog": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/dialog": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/context-menu": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/context-menu/-/context-menu-24.8.3.tgz",
- "integrity": "sha512-588VDBfYrUgA1aVInvQpn1cH/hDhNsbaPAPnp1irx0f8tV2tUWKk0pq5MPo8wzTt+xBO1NOiQGXHywnbooSdZw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/context-menu/-/context-menu-24.8.6.tgz",
+ "integrity": "sha512-oxgko1QeexR0ZFXEr0wt/AgX202rDb2VG0k3Rdjl05e4fzKI7Z5sJastkLEx15CG27JuPEC2iiwrIThzdpTm5Q==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/list-box": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/list-box": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/custom-field": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/custom-field/-/custom-field-24.8.3.tgz",
- "integrity": "sha512-s0ALwKtVY3Rf3vFwQKEjne46D9wxxBuI6hXGNcViqYf7JffK23T2lwnRrVgonCG5dpZ74gBHGHwS1oHqWGsUXA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/custom-field/-/custom-field-24.8.6.tgz",
+ "integrity": "sha512-yi8fAOl4rW55qqj2+eBlHdNvRaIvBEqcm1cZLEQufxi+F/8oCcYj52HceKYTrtnL4eIcwCxDm+E4F/Bvft/8jw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/date-picker": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/date-picker/-/date-picker-24.8.3.tgz",
- "integrity": "sha512-LFLpCjMlbJfEhpcWPoPLqeDc//rMdKRD08QP9plCSRf1Kn1HRfPAjW8Lqq1wMKDLTgQnrth/G7a5vYuaAR3zzg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/date-picker/-/date-picker-24.8.6.tgz",
+ "integrity": "sha512-ofuf57/hynJiiMlyWbSpraQtce+htN1SL4J7pUvB315Qekb6Piic+u11/Y9UC22yl0du39KetsXTZ6dgk0oi1g==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.2.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/date-time-picker": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/date-time-picker/-/date-time-picker-24.8.3.tgz",
- "integrity": "sha512-J3IRtZL3upG+JKKTkNE+aFUxSmoGpp0Vw077u+3K7Kj8Lx4MLtsKXny03yZsnzBTkoomfCZHQbonYr5fCQsTIw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/date-time-picker/-/date-time-picker-24.8.6.tgz",
+ "integrity": "sha512-55I5JgZz3GGoGSUvntovjpqDQxn267C1G3kn9zELVCVEN8yNG2q8cdyunHLpKx+fj6y2/PZIkiVPNiUpBw/Www==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/custom-field": "~24.8.3",
- "@vaadin/date-picker": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/time-picker": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/custom-field": "~24.8.6",
+ "@vaadin/date-picker": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/time-picker": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/details": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/details/-/details-24.8.3.tgz",
- "integrity": "sha512-G2aq3F+jT5rzxPZKgiEhjdjJnKgFjry710GS1+YsiEs531fVNAIuNQWZYY1PzNMsh9AJ1HoIIp9v7Vx+RTL53Q==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/details/-/details-24.8.6.tgz",
+ "integrity": "sha512-vJ7ufMXabp5ltZcsxlTwPwwB5LDwbRsQzFts4Z187L8s0E0k/18xHkdggIPqKZV+hoEh+NsqyZwjAOpK/qSqtQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/dialog": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/dialog/-/dialog-24.8.3.tgz",
- "integrity": "sha512-RxGUr5FVXRtIHpy+MOIVKIaOmTIEV1sRe7waGbiWID+E1r5BRdJnGqV5fjujC7ppS4ZHzydgxglkWeTMDC1QuA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/dialog/-/dialog-24.8.6.tgz",
+ "integrity": "sha512-87h1iwoiS/B5+YWnS2XXWTtqzWJvpYK7BOaqrSsQIbPI9jww3TguTNKiHgu4i/yXXavMBoEhvBrJxJ46i9Ftdg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/email-field": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/email-field/-/email-field-24.8.3.tgz",
- "integrity": "sha512-l7k8XKWsjhA+i3exXzrcr0bwbkLjszJ2OR4HvDqe5EcF4fmGWcegnVUrGKmzj1hpNPRuPhbG1ADuHKv263zjHw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/email-field/-/email-field-24.8.6.tgz",
+ "integrity": "sha512-OjT0OCpU+ByTsCO6YxSnbuT8f8m3ALEabf/3r7uPZYVaAxGTaD/rd2oyyq/bYPe8ZsY7rcTHvRyW91zb8DOzOA==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/text-field": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/text-field": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/field-base": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-24.8.3.tgz",
- "integrity": "sha512-ozkzamDbWWYFDvgQrUfWV3amM37+gKJd581MpBLHCc1ER42D+jtu7Fzll73OmjopL+ykaNZUv/RidsQpINP5xA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-24.8.6.tgz",
+ "integrity": "sha512-B3DXzH1wys6airDSNKfBJZE8kH34N52dhW3DBhcwaQ87iw4KdTG3IbQ+3c7oHc4mezOhtfgtst00PIs8RCg6Hg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/field-highlighter": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/field-highlighter/-/field-highlighter-24.8.3.tgz",
- "integrity": "sha512-r+ivpdL55mo1wuYhbwLOBxDijZ/9YRqUteBnQ5JH776ogxibS7KaaMTTMt5x609J0p9Lav6gJqB8v+5HqwesQA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/field-highlighter/-/field-highlighter-24.8.6.tgz",
+ "integrity": "sha512-TJBf2Nib6SW/rRUVsK81p+qdkXw/WjWxceypmE6nJlcZqGquqcz8TMqY+3g6dXcfUrMKi5nResxjkmkd1RNQxQ==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/form-layout": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/form-layout/-/form-layout-24.8.3.tgz",
- "integrity": "sha512-qpOdU5zJDeRuJqb9myaJ/dewxN1MtYERrS2K5bZV/B/xMd9aZZ5jY1eVudy+7wVqdWsFYvJhbBuHq69sp7Pzqg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/form-layout/-/form-layout-24.8.6.tgz",
+ "integrity": "sha512-7rCr8DxK/Wdn8F7bB/JX70vA6fzYbuQbF22odP6auzVRKWoE6JWY8gvkFIHtuAFnNt+Fc56CRbtHXPewqCY8QQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/grid": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-24.8.3.tgz",
- "integrity": "sha512-agGZmF4TFL3UICiJNUXI7aovVJOiqqJmBs+3f9QUxl2nLV3zvm/oG3xmS4yx90xARdZzfcAH+ZWJbEngfFGYfQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-24.8.6.tgz",
+ "integrity": "sha512-RZUhKOunOdfErNakt4xz99gmmqmjWemYZDWtECh03xTPq2rtZXICmaK4/JqwJ/i+I8/TwDyMSUu3boqlFNiMYA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/checkbox": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/text-field": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/checkbox": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/text-field": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/hilla-file-router": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-file-router/-/hilla-file-router-24.8.2.tgz",
- "integrity": "sha512-rBLFUADRQu53/yrYRJxB5iAqoKRbp0EiU1REWSSivEUd7wk7zon3OkblhHVyU7Ohx6BdqDMxlKnbStJijdBcFg==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-file-router/-/hilla-file-router-24.8.7.tgz",
+ "integrity": "sha512-pWSzxpd8NU4rZjKtPNMfMG7Qhnl7A3CyKGArWBvOCfCeyW0cdLt+Gkxl+606dsgoSqdyHMHrX0EdrtWtsO50EA==",
"license": "Apache-2.0",
"dependencies": {
"@ungap/with-resolvers": "0.1.0",
- "@vaadin/hilla-generator-utils": "24.8.2",
- "@vaadin/hilla-react-auth": "24.8.2",
- "@vaadin/hilla-react-signals": "24.8.2",
+ "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-react-auth": "24.8.7",
+ "@vaadin/hilla-react-signals": "24.8.7",
"tsc-template": "0.2.3",
"typescript": "5.8.3"
},
@@ -8231,9 +7946,9 @@
}
},
"node_modules/@vaadin/hilla-frontend": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-frontend/-/hilla-frontend-24.8.2.tgz",
- "integrity": "sha512-YPFb52LghlpHGzoLlaYY5Bz47G/F+YCaOYTH5aS+kageXaQ5kVnZRYZPG59gElk4Bqk3Z/I2q4+V1BrXCdcerg==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-frontend/-/hilla-frontend-24.8.7.tgz",
+ "integrity": "sha512-WTwv8CAEXZrKhdP0mszuptWKBx453X1fTohkcNx3V58vAhzg/cakBQsdXo1OqGBd3ZPbHUgZ1R5lFp1lL4qAgQ==",
"license": "Apache-2.0",
"dependencies": {
"@vaadin/common-frontend": "0.0.19",
@@ -8245,14 +7960,14 @@
}
},
"node_modules/@vaadin/hilla-generator-cli": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-cli/-/hilla-generator-cli-24.8.2.tgz",
- "integrity": "sha512-AKKa9bsCQMqGN8+TVavLCH90uW83PP15XDcSNHNIsTVNmvByOXGPJslvW2o9ne3Or7p3x1bhTLDadNushI0q4Q==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-cli/-/hilla-generator-cli-24.8.7.tgz",
+ "integrity": "sha512-qd+lfRXtJmy59X4IoBEuBuGynx5dI0QftBNBjCRSSANWbv31WWTOqKOUtZiGTbAYqDqDKcw3Tu1RqMV1KXqaCA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2"
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7"
},
"bin": {
"tsgen": "bin/index.js"
@@ -8262,14 +7977,14 @@
}
},
"node_modules/@vaadin/hilla-generator-core": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-core/-/hilla-generator-core-24.8.2.tgz",
- "integrity": "sha512-iZ87vMJ6X2hVZzpo5fP7k4f+WzRO5e/5CnZwmQ0ZQOV/DcyiXWi+VcLEGbsuejUhqcyKrNnfzuQb4qqHb7oYAw==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-core/-/hilla-generator-core-24.8.7.tgz",
+ "integrity": "sha512-XmbruZpmssOjyGfOcL5l5eSACLzHjHq5tlItTBlsfHpYyrT6SImJQQn0r2MX/tpDV4cNALzCd3tAbOsH3oREzw==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.1.1",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"meow": "13.2.0",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8292,15 +8007,15 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-backbone": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-backbone/-/hilla-generator-plugin-backbone-24.8.2.tgz",
- "integrity": "sha512-JLIgfmg2L0dfjSFUEdgb4bv/q7IK3Q+OIOaOGVe5FCeIrd2jihJhQgzdRkufIsfIbYosXNfEoz9EF5U1xqMi7A==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-backbone/-/hilla-generator-plugin-backbone-24.8.7.tgz",
+ "integrity": "sha512-c62JKMCAt4YGfo9BWwDeaLIDYz8mVl/lwMdyW4anJH+1GLbN1PTnLbjUToNkyl/aRHFQ1wsDz4MSuVFrbmkpYA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8310,15 +8025,15 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-barrel": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-barrel/-/hilla-generator-plugin-barrel-24.8.2.tgz",
- "integrity": "sha512-WNF2DVoTDFXXjFxlwfaO0OrYrphfSbtg4CvEcwTU/9pZo7dQKEQXxYk5rVCONYpCdjyS+q/r9s2c1GJFoQasZw==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-barrel/-/hilla-generator-plugin-barrel-24.8.7.tgz",
+ "integrity": "sha512-4ha9b1hWc8qRryoS9xKEe2isy/Mrn6W5VWugbTjCLLr1PJtwT7meLE41W5IMtiUawoc8Y5VqHwvYoHmEQ8SdOw==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"typescript": "5.8.3"
},
"engines": {
@@ -8326,14 +8041,14 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-client": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-client/-/hilla-generator-plugin-client-24.8.2.tgz",
- "integrity": "sha512-EQ+ZeKbGApVZzx4xtNTzueQSBnsM+GPks8tRxFLC8BfczB9LIm+MCv2m88VAQRiznY3Xdtz2aEyCWQ0oV0ZSeQ==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-client/-/hilla-generator-plugin-client-24.8.7.tgz",
+ "integrity": "sha512-w6HjsM8i+whzuvYHDLLttyzYydXJr2JMdcRy0wjwJrpJiDS9btvKN7dbicesfY6yi9BgT/4UtLtvAAAvEkaF0w==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"typescript": "5.8.3"
},
"engines": {
@@ -8341,16 +8056,16 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-model": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-model/-/hilla-generator-plugin-model-24.8.2.tgz",
- "integrity": "sha512-0b/HVEqKKLH0GswPONbNJxadIVBn66sXHqXNfgB2AdBYFrnORgbVnO96qgxdGrRWHlBO4sf6SXgHf4bb4z7KcA==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-model/-/hilla-generator-plugin-model-24.8.7.tgz",
+ "integrity": "sha512-KwAqPqVdhbPOU0Afur59FUB3uuZyr7TvCMCvBcpebaUe+8t7kPekoX/dYbhQP5tYh4VoFusQNe9xjVSaHvP5lQ==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
- "@vaadin/hilla-lit-form": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-lit-form": "24.8.7",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8360,15 +8075,15 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-push": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-push/-/hilla-generator-plugin-push-24.8.2.tgz",
- "integrity": "sha512-bj/zxHzR4mhbUGyyA/FxIJvZEvgcjgKW2E5KsVJOtmOUFQTQ2KO7haofCgTwT3wCzxFk4EMYt8St+njJs2dqlQ==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-push/-/hilla-generator-plugin-push-24.8.7.tgz",
+ "integrity": "sha512-zG8q/a1i+mHHhjTwn0+NHtJGsbdmkT6Bdp26j5lVM+Qs2aUMhHBtIQCkUbgK+Ack4vi5FfE20ELCLYD4QwsSkg==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8378,14 +8093,14 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-signals": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-signals/-/hilla-generator-plugin-signals-24.8.2.tgz",
- "integrity": "sha512-aCMQXQkXzUlIcGsd2Qng2dZxTzKn5ykTP2VtAm64Xl+1dUqmruTrTmqonIFEHrqJsEqWWbk/qhADYNBa6jsjew==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-signals/-/hilla-generator-plugin-signals-24.8.7.tgz",
+ "integrity": "sha512-qAC2+jn8oDPzc2nFNEosLhKysl0hGrbRVBCC9fDDUyo5XdgZnIyCoETRpo12qAqRyP8y/LToeyAm1deNs82MWQ==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"fast-deep-equal": "3.1.3",
"iterator-helpers-polyfill": "3.0.1",
"openapi-types": "12.1.3",
@@ -8397,16 +8112,16 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-subtypes": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-subtypes/-/hilla-generator-plugin-subtypes-24.8.2.tgz",
- "integrity": "sha512-aLDhUjeJq8sBFkin7svULv454WAJ6Olsdqg2iWJU/gfad+9j7XlKy3IWZI+FI/02uzmB0Vtpn34jx0k2jZyxMQ==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-subtypes/-/hilla-generator-plugin-subtypes-24.8.7.tgz",
+ "integrity": "sha512-CTGlPc4MMbuY31pCBFec38XMZ8Z5sKAMZ+hh4+sXrMLD/2Wrxt7qwf+R7OBULdSXnP0cHqv22FMPwYjOdMrR4g==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-plugin-model": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-plugin-model": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"fast-deep-equal": "^3.1.3",
"openapi-types": "^12.1.3",
"typescript": "5.8.3"
@@ -8416,16 +8131,16 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-transfertypes": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-transfertypes/-/hilla-generator-plugin-transfertypes-24.8.2.tgz",
- "integrity": "sha512-Eb8BumgIxc8Hl3GI+q2gIa60liynUHxXv7mMdvTn9CBMDEMRhKJ8ER8TKVi9rdffDAMhR/Xpf6L7z4aH2I5+WA==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-transfertypes/-/hilla-generator-plugin-transfertypes-24.8.7.tgz",
+ "integrity": "sha512-EssIPC+kMCTL985x7JwcqXA1UfX0/BTcUq14Ur2bj4NQeO4LP2JU5F1+IeGNlyyGQMHwP5MO0qnnMPkvviJFew==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-plugin-model": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-plugin-model": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8435,9 +8150,9 @@
}
},
"node_modules/@vaadin/hilla-generator-utils": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-utils/-/hilla-generator-utils-24.8.2.tgz",
- "integrity": "sha512-qcuC0SRWSzf/3LzSqyxdiid9xbPtkx28e8XsD/MSJK4fgylS5Znz/aeRzd1EHMY3t93XWQ09Nk2yLsExocPQzA==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-utils/-/hilla-generator-utils-24.8.7.tgz",
+ "integrity": "sha512-dKFpGw9V9offx4zne92xEPEw6bMhQGs/abbwkrOJgRTXoZIRQlzGUrc50+LRd7zRoaoE0OLKnBepL1O9TgIPKg==",
"license": "Apache 2.0",
"dependencies": {
"pino": "9.6.0",
@@ -8449,12 +8164,12 @@
}
},
"node_modules/@vaadin/hilla-lit-form": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-lit-form/-/hilla-lit-form-24.8.2.tgz",
- "integrity": "sha512-KcBUiZVVfEPKVPxNpAb6WYGOMOxxShu6Q0jHvPdLrWGhRXPFs3SORZGQPsiryjLlp6seb3p2IO9f9gxYZplqJg==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-lit-form/-/hilla-lit-form-24.8.7.tgz",
+ "integrity": "sha512-1+NeXot0YfuVEKeXjgZ6wMEz6VMePioQYlif70xpsw31trDNqIOhb9jrawe5YJVw7qntPDO+g8mkVuwuOBuYNg==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.2",
+ "@vaadin/hilla-frontend": "24.8.7",
"validator": "13.12.0"
},
"peerDependencies": {
@@ -8462,12 +8177,12 @@
}
},
"node_modules/@vaadin/hilla-react-auth": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-auth/-/hilla-react-auth-24.8.2.tgz",
- "integrity": "sha512-yH//4AVBfdX/3X6mJy5Y4IpLzcYMldHUkZh9Pv4vy3anNo0jXQVcNcM3Y8U+Kt5xXL/6VsRvTO6KLZ+mmECiaQ==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-auth/-/hilla-react-auth-24.8.7.tgz",
+ "integrity": "sha512-baE+BbaIyds+6Cxb7ylqLGYc7f4CSBCmbqDpYciGuU2vurNdWrx2vGjgm3yoae0WU8UHMz/mjsf0bp6fkIc2+Q==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.2"
+ "@vaadin/hilla-frontend": "24.8.7"
},
"peerDependencies": {
"react": "18 || 19",
@@ -8476,16 +8191,16 @@
}
},
"node_modules/@vaadin/hilla-react-crud": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-crud/-/hilla-react-crud-24.8.2.tgz",
- "integrity": "sha512-92Jhxlz1n+6aE+rzJYUqacVTrb2dVpyFv650neV8UzCEAyWqJgV+o4UUx2vWn0iEiS2xsMLK8iOJOjUeMQPZpQ==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-crud/-/hilla-react-crud-24.8.7.tgz",
+ "integrity": "sha512-6UPwCHQpNm7Zc4GpjgzFAPLyF32UtTS2Yy/0LxBUKAC6qqfO87SMMMxNy8YHYifxE1T3NVvUwMAFd1yQhYE+EQ==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.2",
- "@vaadin/hilla-lit-form": "24.8.2",
- "@vaadin/hilla-react-form": "24.8.2",
- "@vaadin/react-components": "24.8.3",
- "@vaadin/vaadin-lumo-styles": "24.8.3",
+ "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-lit-form": "24.8.7",
+ "@vaadin/hilla-react-form": "24.8.7",
+ "@vaadin/react-components": "24.8.6",
+ "@vaadin/vaadin-lumo-styles": "24.8.6",
"type-fest": "4.35.0"
},
"peerDependencies": {
@@ -8494,12 +8209,12 @@
}
},
"node_modules/@vaadin/hilla-react-form": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-form/-/hilla-react-form-24.8.2.tgz",
- "integrity": "sha512-yK/PseFMVg43N4pLljWykvYH0HPLL16y6RVNDh0Dv84RziDN/0xstI/XDpicZEjILCTgQNt9bjP//l5C9/wCfw==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-form/-/hilla-react-form-24.8.7.tgz",
+ "integrity": "sha512-bIXY6toMLgiHk3wVkQc/JnhogvB4+SdSmxiTH+AdGSHw1SOAx+F/wPZI1M24tkluBCCg6YRNQwAR/dlf/MkONw==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-lit-form": "24.8.2"
+ "@vaadin/hilla-lit-form": "24.8.7"
},
"peerDependencies": {
"react": "18 || 19",
@@ -8507,13 +8222,13 @@
}
},
"node_modules/@vaadin/hilla-react-i18n": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-i18n/-/hilla-react-i18n-24.8.2.tgz",
- "integrity": "sha512-NXODXGQQJMdjQCNXVhPsoBPklqqnV4jiHkbddbYe+h7SjBmglqi2sQObMLuCNkJFjN1Lt/BzB3s3u0n3Yc2cqA==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-i18n/-/hilla-react-i18n-24.8.7.tgz",
+ "integrity": "sha512-1SU3ndFOTN7KA1aI5bx+2L98Yob+KCHsicX3B62IEbp1ChUl+FhoO5TF8/aZoKVFRDnGcHPtorv+5Js8ABlY1g==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.2",
- "@vaadin/hilla-react-signals": "24.8.2",
+ "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-react-signals": "24.8.7",
"intl-messageformat": "10.7.11"
},
"peerDependencies": {
@@ -8585,13 +8300,13 @@
}
},
"node_modules/@vaadin/hilla-react-signals": {
- "version": "24.8.2",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-signals/-/hilla-react-signals-24.8.2.tgz",
- "integrity": "sha512-2RKOWZ0C2Uy+KxYYeuw55wzPOue3raIUWYi7RGUH2T+wLUrzMqnswmlUZrBHMexHvOWtsQ8mDnNibzvrXsABOg==",
+ "version": "24.8.7",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-signals/-/hilla-react-signals-24.8.7.tgz",
+ "integrity": "sha512-eCQzw3pYkzNCe0eaXjtfYVKjUEC+qx9S8lZ0EE2gC22Ux80scn5LCMISlWj14yvdmn9oy0utnrkJ2p5+H6dQ/w==",
"license": "Apache-2.0",
"dependencies": {
"@preact/signals-react": "3.0.1",
- "@vaadin/hilla-frontend": "24.8.2",
+ "@vaadin/hilla-frontend": "24.8.7",
"nanoid": "5.0.9"
},
"peerDependencies": {
@@ -8600,437 +8315,437 @@
}
},
"node_modules/@vaadin/horizontal-layout": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/horizontal-layout/-/horizontal-layout-24.8.3.tgz",
- "integrity": "sha512-6OxhXUTQhM6bh8Utahr8EjBi72qBwy38jO1Tve1P5p4ChnqSoW8TONXf873GlrCiEXVE3oJiifV1VHb6Y6NRNQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/horizontal-layout/-/horizontal-layout-24.8.6.tgz",
+ "integrity": "sha512-xInsr7wQ92V3a8DNVyIbR3PZKrqVnG1jAMWGxad854j/f0RMZMecF2u/98mPW4GrKGxswCh2AFWR8w9HFuXLUg==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/icon": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-24.8.3.tgz",
- "integrity": "sha512-BYmBFhB61ISEcxb6Q96LObInraEhK3L52TUKfmJubB93vfTQMQA+AyPywK+mFRGMGg/84n86hIjYrlbnSWxNwQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-24.8.6.tgz",
+ "integrity": "sha512-qnZMtMuEpDxEMwNkrtLorIclSzdlYZaFGm1UD25PLl2uu3jFzsTgsHKxKFHm5Kc2b6gKFHpe/7DK52/D28PVow==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/icons": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/icons/-/icons-24.8.3.tgz",
- "integrity": "sha512-efCXaS5fmWAKomOlTaO6qjl6kA88rv7vW1whYpTYS4Ok40M9MdHN15YH5Fl/cQ+L0DeB2Xc15z0VonmWcx3+/w==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/icons/-/icons-24.8.6.tgz",
+ "integrity": "sha512-g5cfbUE1IM1lPbkoAQ5/rUiaseBU5DloywkukWAXmOHswAiW8dWFo78Hs5LINiIG9c2BlPIZfcfrKbn6cgCwnQ==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/icon": "~24.8.3"
+ "@vaadin/icon": "~24.8.6"
}
},
"node_modules/@vaadin/input-container": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-24.8.3.tgz",
- "integrity": "sha512-ok3w8r3lZSYKEYiuNDUXccgQuF4pDanHvm0pZq2ieLAjylaoZSDhiSIKcBYZYSoEDmPTa5gikXLgWrCrNXMyYw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-24.8.6.tgz",
+ "integrity": "sha512-lxnrsQCjYjDC+5K17tSuJWCwx4/bn5KCRoKz5HJcXzyXsFj8iKF7+Pv1+OjKzRRHTF27kwjviMAaP+2/V5dEgA==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/integer-field": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/integer-field/-/integer-field-24.8.3.tgz",
- "integrity": "sha512-CVUwY4eRasywSpyNtpTXV95AK+NR2izCUlYMo4ssrx7apuWqbBrJ/J9DidP5vDOTsSb70EDW7Zo3L8qrque8mA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/integer-field/-/integer-field-24.8.6.tgz",
+ "integrity": "sha512-ZbntjrZaEohzYp1Qn94DVUbFqZdYZy5Xemmspsr4HG6ahobFsMpgdrHjgEjG5q1hah+Cuo5Yr1mYuzxyTxJFug==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/number-field": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3"
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/number-field": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6"
}
},
"node_modules/@vaadin/item": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/item/-/item-24.8.3.tgz",
- "integrity": "sha512-sPC8QcvlpbEZbG2dd3bSP4FTKluPLjpdKrKti687R0pRIZX+edzowbG8xOY3ehTWXo801pLzxu5HhKfRHd0YaQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/item/-/item-24.8.6.tgz",
+ "integrity": "sha512-ZLgUA/s+9rGw34yUxF8XYpb2SR+xDINT48Sd8d7SFZZKFwlucyeChNKM1MEsa9floKqaFx4x4eGUdiveDdloyw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/list-box": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/list-box/-/list-box-24.8.3.tgz",
- "integrity": "sha512-QVmbw6J17hFxBPLcK5E3QjH4qo2t6qmJR2H4q22czfifFPLMxJ5EImLwHpNh434s73NCIKSlZqKSKI1q6r5Lpg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/list-box/-/list-box-24.8.6.tgz",
+ "integrity": "sha512-Pw9RhWt2UPwzMpgOU5gfxPCWwnw25ip5gshhGuUh8IsRlqQAHa9iIEgk8mt85hJvoaKQ19HKMWx2qAB7PMJdlA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/lit-renderer": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-24.8.3.tgz",
- "integrity": "sha512-3753itYgp/TY81Nm5u7OrCUhsTgkxr19BvVo8O00wHWhQThZOS7bD6+N7pSH83XEG9pv8zQiY24kQBOeSzc0DQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-24.8.6.tgz",
+ "integrity": "sha512-mrIq+0EV/XEelTaNOxQ0EcSOp+uYRH2tL+nIF/Ct8qCOinndBbaIOeScNlCABB/0IH3ltPmrmtEHxDsExPy4lw==",
"license": "Apache-2.0",
"dependencies": {
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/login": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/login/-/login-24.8.3.tgz",
- "integrity": "sha512-dBxY0gVU+wU89Nti6VLLm3LBTtcFVnG20CuXUywhBdyij1zVIcqQRoeN4VwRJVApdbfKngeyUrnYijegcVkhmg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/login/-/login-24.8.6.tgz",
+ "integrity": "sha512-D+3VYR6BWSAxozNG9Z+NSfC1V3XmxoxKNj6vkLs26eyuBxMkDAtbIO6+eKAfk+73U8PbO1+V4QnHeR2MM23eLA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/password-field": "~24.8.3",
- "@vaadin/text-field": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/password-field": "~24.8.6",
+ "@vaadin/text-field": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/markdown": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/markdown/-/markdown-24.8.3.tgz",
- "integrity": "sha512-R6VTyM7iunEcG7pfCi07r4uNO0o3fqOZhcE1LpO8/9zQfW+4WCVHr0bXRgrB2Uciu657W4elbz79pMwLa9eRTg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/markdown/-/markdown-24.8.6.tgz",
+ "integrity": "sha512-g+Hf+IkAnSgWpJ83URzbQ0+n6D4Xt0wh6zPuVv2rLAnFL8/krAhDE/mqPnKZ0HJV+72zJtT2qAvi8Gc+Nva4eg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"dompurify": "^3.2.5",
"lit": "^3.0.0",
"marked": "^15.0.11"
}
},
"node_modules/@vaadin/master-detail-layout": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/master-detail-layout/-/master-detail-layout-24.8.3.tgz",
- "integrity": "sha512-o+GXpJYgwyZeZEPsnqK6qRFiu6YfnmHdnGYOo9srjeFHlDAwXSMmoJj1LQIqmZNNe2IaoTF+z2sX4C/pJKgXTQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/master-detail-layout/-/master-detail-layout-24.8.6.tgz",
+ "integrity": "sha512-cpetSApUbY9altW+FhI0O11gxWptjk35zM2YEUy0ZYsi7tb8+eHvY4k5Az1IDGdijoa++PBwwiW4Qh3Rkgf/HA==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/menu-bar": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/menu-bar/-/menu-bar-24.8.3.tgz",
- "integrity": "sha512-RFdcve68TfX/GbBEyhP6c6d/wF57hMS+3nRlyAyGwUMaqprn+ou+V60I5sQeWCfpigwEmRd7feyjtfKz+3o6Tw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/menu-bar/-/menu-bar-24.8.6.tgz",
+ "integrity": "sha512-m5g1w3XJTir8SIBqVnlGIcMHQXl8j8w3PnW+u9C/MC94Hgms10zYIplYeljS4RMq2wnfos+FO3Gcdp177JJMlg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/context-menu": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/list-box": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/context-menu": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/list-box": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/message-input": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/message-input/-/message-input-24.8.3.tgz",
- "integrity": "sha512-dHX8xXobFn+2kmXU972+tICaSkQVtvnDofDrUw9L5RsWv9Fgr5O8iCkAPdj9fq3crDgZ88Xt6/mblG5AI/p9yg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/message-input/-/message-input-24.8.6.tgz",
+ "integrity": "sha512-+e6pnKHcJPvOcO71j71MEY75o4T1EKO7FgD0Vpg2h4fhJSfZJkWGrn3Dxckbf9kyyYBZnIqTX6votS9CbHKiXg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/text-area": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/text-area": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/message-list": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/message-list/-/message-list-24.8.3.tgz",
- "integrity": "sha512-GOHrK+HF7IXfvoK0jIuB4VYW+aa0wk87fiBKvNiREIWYs+WGOYgeXuR2vf/KFwK+K7E/STgtAsHjFgfLuKVnvw==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/message-list/-/message-list-24.8.6.tgz",
+ "integrity": "sha512-pTEQiXiDEzMoeEWCIx03Tr9I8dhiRNrdT0ylyKaKlHxQgYGOA3RbVk4ArXVF+L7zhCyYefax2EjWlaCb2GhpSQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/avatar": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/markdown": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/avatar": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/markdown": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/multi-select-combo-box": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/multi-select-combo-box/-/multi-select-combo-box-24.8.3.tgz",
- "integrity": "sha512-RE94wVnLQ13MSfLYa2ZkIUvKNVWR7Ey5sKMOx3O9sQU8vKQGiHLE1zDcToRv9FzlErRvUwvTcEBa1D4uMXjzbQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/multi-select-combo-box/-/multi-select-combo-box-24.8.6.tgz",
+ "integrity": "sha512-DaH2oco8QTZ2J09AN8k2XKIQefIS1FnHqevik9CffUaPDSx0jaFYcakK7SKrxXOVnDP/NapHLoDFhaZbfREjEw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/combo-box": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/combo-box": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/notification": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/notification/-/notification-24.8.3.tgz",
- "integrity": "sha512-m+hX5dUcdd4zPgDLdP2tZaPNRDvl/IFUyVMiw7c+qO2apWKvD2q2QGLhyDQexqdidjvbVMlV6HEu8gPyYlxn0g==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/notification/-/notification-24.8.6.tgz",
+ "integrity": "sha512-NYjn6EcF6fKaq/ogULqrYh1GkJG7u+y9KeqeA4W4cjd0XI0fGPUVvqPoqdgds/6+DZWvjeCIWZqU4I5YZkBBig==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/number-field": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/number-field/-/number-field-24.8.3.tgz",
- "integrity": "sha512-P9AYCtExCSJoq00ddn75W+7v5nJASH6kgxfzrw0EZo+5JK/w0dRIdqSiubycE0re6DwXo+EK4pcU9cqJ0MxirA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/number-field/-/number-field-24.8.6.tgz",
+ "integrity": "sha512-oCkN8w1OZnSPNBa/YE+50GuAgkNOhA/IDNVBqTtOD0749IsFdFL5VeT0w9a8wW5v29cWplCc09k4PZYXyNBQdw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/overlay": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/overlay/-/overlay-24.8.3.tgz",
- "integrity": "sha512-y7RtDslU7J/wuwWVMZDgpXnOBa8fK92wXF29EVtSgwcNoZZnmyAgDgsIn2oM4U5Hxtta/F8uqtPWhXejczWehg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/overlay/-/overlay-24.8.6.tgz",
+ "integrity": "sha512-2nWoqUtW0/y6Q8mIwWK312oSkbOoUjw6+9u8amqBHZLkd8yWj9bvFwsPCrrUPhIlAtLPjXdLjMqCixEsAnTCbA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/password-field": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/password-field/-/password-field-24.8.3.tgz",
- "integrity": "sha512-XMY7Cp7KBHqBhQkkU065bpvt5GdjKwPaJI1H8I7YuhbbxGVq3hZ1Hl5NMKfS+vcTj121orOoT/fFe/VkWv006w==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/password-field/-/password-field-24.8.6.tgz",
+ "integrity": "sha512-T3hQLeo+Dy6ElZZKpTzRYT35a3E9MBJt1iomLX2+zoi7oeUr2zJJeS34bOEIVXE4Oh6gKSn+q3NgHEXrXqkwBw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/text-field": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/text-field": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/polymer-legacy-adapter": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/polymer-legacy-adapter/-/polymer-legacy-adapter-24.8.3.tgz",
- "integrity": "sha512-GN00ZA5TMEmh5Vf6/TpgEpdCsdR2XEXk0LYIuJwAahss/xpaWVPwE25tEmQPnHOAe0JGtnGljR4P0VzlMcjrZQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/polymer-legacy-adapter/-/polymer-legacy-adapter-24.8.6.tgz",
+ "integrity": "sha512-C17ElnaP0m0ae/ZGEBinj+ArcffPj1QZ4MFW0wW98z3XUdunS4HaUaE82OBH0Jcabmb1SkteJX9l/DpDg+Os8Q==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/popover": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/popover/-/popover-24.8.3.tgz",
- "integrity": "sha512-2CecNUKJwyAf8UW2+PL//m6tysCUKw+pc9KKyMkxg9jVJGVQnnbJbSrV5qVJYgiDJFywSUDkcAiR8KWB9MsOyg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/popover/-/popover-24.8.6.tgz",
+ "integrity": "sha512-xHYbjzX4lodcip5TvF2zSnzoTrrbKgF3snqjaL8puftrG5DywV8uMvBw4pcRNiwBKkvpjCUfDDXngLuvWxPxpQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/progress-bar": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/progress-bar/-/progress-bar-24.8.3.tgz",
- "integrity": "sha512-K1DwXf2Y93mnGLy8b1fqepwF7zP1rtLtTTAkehj4yvXO9s3iFoICFDZUMSUiS7fnHvbFaVznYalrDWajNqwdwA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/progress-bar/-/progress-bar-24.8.6.tgz",
+ "integrity": "sha512-62Fpp3sPc54c2L2EzkERsuUNMys4FOb2NWOal/iX1KrRLaXN+ObZ5JUGFZ7RlOlhSfuM03mvJw4wT0+kwzD75w==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/radio-group": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/radio-group/-/radio-group-24.8.3.tgz",
- "integrity": "sha512-cwgW7lHcpOnoh8ItpXXx4v6MwyN2xbjaFURunUWpQz/4ftkRtDB5qVcn8ObFhZfESN9UogVnHCEmZtnGodUHIg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/radio-group/-/radio-group-24.8.6.tgz",
+ "integrity": "sha512-qD9bd4AwDhyv3v0YZ4js0gUeHDz7D20yYp6MU1JFf2N7mTHkIkMuBJXX0ZUMzpmae0IypYngRJewHAWPCDgpCA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/react-components": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/react-components/-/react-components-24.8.3.tgz",
- "integrity": "sha512-lvU9hdBnsZj+y2dvWTsJtWFZEcVAZtE8E2Hqw6EXvh7ClFjhzxfRoASCb51Bfh676yvaWPNOrd0k1x584cxQxQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/react-components/-/react-components-24.8.6.tgz",
+ "integrity": "sha512-mWFCwKgKTH3DZ73L52itbJyfTmwenctc/c1nheqTiL3KwpyAJr4Ql5NcSSM9DHQgt53KcM1xBTKdohrKE2rGNQ==",
"license": "Apache-2.0",
"dependencies": {
"@lit/react": "^1.0.7",
- "@vaadin/a11y-base": "24.8.3",
- "@vaadin/accordion": "24.8.3",
- "@vaadin/app-layout": "24.8.3",
- "@vaadin/avatar": "24.8.3",
- "@vaadin/avatar-group": "24.8.3",
- "@vaadin/button": "24.8.3",
- "@vaadin/card": "24.8.3",
- "@vaadin/checkbox": "24.8.3",
- "@vaadin/checkbox-group": "24.8.3",
- "@vaadin/combo-box": "24.8.3",
- "@vaadin/component-base": "24.8.3",
- "@vaadin/confirm-dialog": "24.8.3",
- "@vaadin/context-menu": "24.8.3",
- "@vaadin/custom-field": "24.8.3",
- "@vaadin/date-picker": "24.8.3",
- "@vaadin/date-time-picker": "24.8.3",
- "@vaadin/details": "24.8.3",
- "@vaadin/dialog": "24.8.3",
- "@vaadin/email-field": "24.8.3",
- "@vaadin/field-base": "24.8.3",
- "@vaadin/field-highlighter": "24.8.3",
- "@vaadin/form-layout": "24.8.3",
- "@vaadin/grid": "24.8.3",
- "@vaadin/horizontal-layout": "24.8.3",
- "@vaadin/icon": "24.8.3",
- "@vaadin/icons": "24.8.3",
- "@vaadin/input-container": "24.8.3",
- "@vaadin/integer-field": "24.8.3",
- "@vaadin/item": "24.8.3",
- "@vaadin/list-box": "24.8.3",
- "@vaadin/lit-renderer": "24.8.3",
- "@vaadin/login": "24.8.3",
- "@vaadin/markdown": "24.8.3",
- "@vaadin/master-detail-layout": "24.8.3",
- "@vaadin/menu-bar": "24.8.3",
- "@vaadin/message-input": "24.8.3",
- "@vaadin/message-list": "24.8.3",
- "@vaadin/multi-select-combo-box": "24.8.3",
- "@vaadin/notification": "24.8.3",
- "@vaadin/number-field": "24.8.3",
- "@vaadin/overlay": "24.8.3",
- "@vaadin/password-field": "24.8.3",
- "@vaadin/popover": "24.8.3",
- "@vaadin/progress-bar": "24.8.3",
- "@vaadin/radio-group": "24.8.3",
- "@vaadin/scroller": "24.8.3",
- "@vaadin/select": "24.8.3",
- "@vaadin/side-nav": "24.8.3",
- "@vaadin/split-layout": "24.8.3",
- "@vaadin/tabs": "24.8.3",
- "@vaadin/tabsheet": "24.8.3",
- "@vaadin/text-area": "24.8.3",
- "@vaadin/text-field": "24.8.3",
- "@vaadin/time-picker": "24.8.3",
- "@vaadin/tooltip": "24.8.3",
- "@vaadin/upload": "24.8.3",
- "@vaadin/vaadin-lumo-styles": "24.8.3",
- "@vaadin/vaadin-material-styles": "24.8.3",
- "@vaadin/vaadin-themable-mixin": "24.8.3",
- "@vaadin/vertical-layout": "24.8.3",
- "@vaadin/virtual-list": "24.8.3"
+ "@vaadin/a11y-base": "24.8.6",
+ "@vaadin/accordion": "24.8.6",
+ "@vaadin/app-layout": "24.8.6",
+ "@vaadin/avatar": "24.8.6",
+ "@vaadin/avatar-group": "24.8.6",
+ "@vaadin/button": "24.8.6",
+ "@vaadin/card": "24.8.6",
+ "@vaadin/checkbox": "24.8.6",
+ "@vaadin/checkbox-group": "24.8.6",
+ "@vaadin/combo-box": "24.8.6",
+ "@vaadin/component-base": "24.8.6",
+ "@vaadin/confirm-dialog": "24.8.6",
+ "@vaadin/context-menu": "24.8.6",
+ "@vaadin/custom-field": "24.8.6",
+ "@vaadin/date-picker": "24.8.6",
+ "@vaadin/date-time-picker": "24.8.6",
+ "@vaadin/details": "24.8.6",
+ "@vaadin/dialog": "24.8.6",
+ "@vaadin/email-field": "24.8.6",
+ "@vaadin/field-base": "24.8.6",
+ "@vaadin/field-highlighter": "24.8.6",
+ "@vaadin/form-layout": "24.8.6",
+ "@vaadin/grid": "24.8.6",
+ "@vaadin/horizontal-layout": "24.8.6",
+ "@vaadin/icon": "24.8.6",
+ "@vaadin/icons": "24.8.6",
+ "@vaadin/input-container": "24.8.6",
+ "@vaadin/integer-field": "24.8.6",
+ "@vaadin/item": "24.8.6",
+ "@vaadin/list-box": "24.8.6",
+ "@vaadin/lit-renderer": "24.8.6",
+ "@vaadin/login": "24.8.6",
+ "@vaadin/markdown": "24.8.6",
+ "@vaadin/master-detail-layout": "24.8.6",
+ "@vaadin/menu-bar": "24.8.6",
+ "@vaadin/message-input": "24.8.6",
+ "@vaadin/message-list": "24.8.6",
+ "@vaadin/multi-select-combo-box": "24.8.6",
+ "@vaadin/notification": "24.8.6",
+ "@vaadin/number-field": "24.8.6",
+ "@vaadin/overlay": "24.8.6",
+ "@vaadin/password-field": "24.8.6",
+ "@vaadin/popover": "24.8.6",
+ "@vaadin/progress-bar": "24.8.6",
+ "@vaadin/radio-group": "24.8.6",
+ "@vaadin/scroller": "24.8.6",
+ "@vaadin/select": "24.8.6",
+ "@vaadin/side-nav": "24.8.6",
+ "@vaadin/split-layout": "24.8.6",
+ "@vaadin/tabs": "24.8.6",
+ "@vaadin/tabsheet": "24.8.6",
+ "@vaadin/text-area": "24.8.6",
+ "@vaadin/text-field": "24.8.6",
+ "@vaadin/time-picker": "24.8.6",
+ "@vaadin/tooltip": "24.8.6",
+ "@vaadin/upload": "24.8.6",
+ "@vaadin/vaadin-lumo-styles": "24.8.6",
+ "@vaadin/vaadin-material-styles": "24.8.6",
+ "@vaadin/vaadin-themable-mixin": "24.8.6",
+ "@vaadin/vertical-layout": "24.8.6",
+ "@vaadin/virtual-list": "24.8.6"
},
"peerDependencies": {
"@types/react": "^18.2.37 || ^19",
@@ -9048,197 +8763,197 @@
}
},
"node_modules/@vaadin/scroller": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/scroller/-/scroller-24.8.3.tgz",
- "integrity": "sha512-vUaDX+SvhWreLpGybjnsqgQa/rwrUqcyHNlsM4R+1Ki2h6zmL/iEtk9bDNuswx0XzXYq3rrept7NlkSrUXeQ9w==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/scroller/-/scroller-24.8.6.tgz",
+ "integrity": "sha512-skSvbsjUSY7DD565ygTFliLCjCL+5WRXUE6ErZy0zi0rbMaenfoVSD0yGz5Ryjxk2y4pZqq63Dn8keJDsb9B1w==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/select": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/select/-/select-24.8.3.tgz",
- "integrity": "sha512-Y1AHbzwDMZ+F2X593tjKMOm/JFjcye3IZcE75360eL0GyoQZ9Oy/Y4qqyVfttd/r2urcHoHplsCRoq/fTDOuyQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/select/-/select-24.8.6.tgz",
+ "integrity": "sha512-68CmCfEyx5bN9e63qLxshKcCB6++mNT3fo/IANAU6HyC97i5tTUZXcChO3hILqXJ7pVeR+h8NHudzd1IFPmtoQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.2.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/list-box": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/list-box": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/side-nav": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/side-nav/-/side-nav-24.8.3.tgz",
- "integrity": "sha512-ztqLqItNtDGFohMgzw1LkEcSlUpb7arZ1UmWuLSt608XphvArTNPgzqbuqohwF8ZulofzieNvoIjYWkii0sH/w==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/side-nav/-/side-nav-24.8.6.tgz",
+ "integrity": "sha512-RD4gI4I8r9FNJZ2FqrpBkbnXVs5fiHOFwOF4fxgfD/lajac8yr/0Nwwktf6CPrCRRafHh78C2uEEk/YUfog2FA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/split-layout": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/split-layout/-/split-layout-24.8.3.tgz",
- "integrity": "sha512-PFnvLH+UEQF0ph0B/Ejt7Awn/c1TdBxbzyxLXC9wk8S4LY+wGD5XdmVZW017sMvVSs5jh9oaleSGe0vf0FUV0w==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/split-layout/-/split-layout-24.8.6.tgz",
+ "integrity": "sha512-zTm/vCS1xfbmIB2XRiChiREgEGsIAUIJ7w4qM2nZzeGXSePK0ZQm6/2AP3Qsekd6WJJu1kqO/6iTQ2a2DN7TLQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/tabs": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/tabs/-/tabs-24.8.3.tgz",
- "integrity": "sha512-TGLAnEhjbPduIghR9euIUBQgkBd9TuY3piGU/7jvOr9gcoWm1r2DD85uSa/b43XCyCuNZwgQREWOKKbmzKn+Zg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/tabs/-/tabs-24.8.6.tgz",
+ "integrity": "sha512-F/Aa4o+bvI01Gibw38WgrxJodCc6vSLNs6CMaE72KbD7hmCgjjiDbhY2HHS99/q5sUtALWM0VP54HbqWqCuEkw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/tabsheet": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/tabsheet/-/tabsheet-24.8.3.tgz",
- "integrity": "sha512-r8Gnmys+06nK6GdgbP9Ym+N4uqGqHEJ/FXdmLlpeOhCO6jpOF+ShpvVjqDu9VZ1fDyvg9VP1y/BuRBPDTGT2yA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/tabsheet/-/tabsheet-24.8.6.tgz",
+ "integrity": "sha512-oNO3T4Gxai3BwUWAveq5MNxAgM+bCu6Z8BwEwps3Hn08ps0GEPM5uv3RZHPW+GrrYP58tlk+OREHDgT1qyk4rw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/scroller": "~24.8.3",
- "@vaadin/tabs": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/scroller": "~24.8.6",
+ "@vaadin/tabs": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/text-area": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/text-area/-/text-area-24.8.3.tgz",
- "integrity": "sha512-MiQTGtTNL2Akbn6e1bM7MFaCwxESVTExZG3C/u2bc5/wc+YntUxGSk2hxU5G2BqX662ZfwVmt4skFmZAmofFZA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/text-area/-/text-area-24.8.6.tgz",
+ "integrity": "sha512-GFKGmG1mkF6VrzUk8XT1u3wJexM40BlO9REsDHGQw64y8UBVL9nKyCGVUv0SjJc/y5ECrZKHiKhVpho+QRb+2g==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/text-field": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-24.8.3.tgz",
- "integrity": "sha512-mJGjLFbbNgC4uhjQ9N1l3pfFBL4C4qRiRQWC/0SgDhwhOV1cyAy+wZ0p6sXpQ8jtgUnrGMg29ryv2bPS3vTtrA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-24.8.6.tgz",
+ "integrity": "sha512-nLw3Oi0hjHWFhqT0RUDOx6D7aT8drW+PM0vgcCa6Q1I0zZnYUnevtGzyZBOlD+XmveEiXNx3jHhXMwHeu2fOaA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/time-picker": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/time-picker/-/time-picker-24.8.3.tgz",
- "integrity": "sha512-ehzyBkDdXexHOymPMSko4FA5WG+V1mybcGlic/i20EidXkh4V1xzEnRWM299mqEP/F9NgHejo8M7wKJrRJrybQ==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/time-picker/-/time-picker-24.8.6.tgz",
+ "integrity": "sha512-cxRVPZjprCwx+6+P6b2Ad9m6BFzZpsNAuQO8aL0fcdkIiG8jmKl3/KPNviB7LUlORMhsWr1dJIPU7GiyIKxrLA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/combo-box": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/field-base": "~24.8.3",
- "@vaadin/input-container": "~24.8.3",
- "@vaadin/item": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/combo-box": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/field-base": "~24.8.6",
+ "@vaadin/input-container": "~24.8.6",
+ "@vaadin/item": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/tooltip": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/tooltip/-/tooltip-24.8.3.tgz",
- "integrity": "sha512-X0AEz3/+ZsxK2lpPTv4+3tuLpuE0sC5jkaDjkPXBDt0E/ad1i0AUOa8FgCpBEczYAPhA3WUVkjh/T0/k0K9S8g==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/tooltip/-/tooltip-24.8.6.tgz",
+ "integrity": "sha512-1xSQw5Wq0psKnl+HehmCWa/jKyofQyHGmTcJ3rtWDrqFr/dviIwx1n3TZu9FDeCSIRQxmIAdkErDYmouSdlOwA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/overlay": "~24.8.3",
- "@vaadin/popover": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/overlay": "~24.8.6",
+ "@vaadin/popover": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/upload": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/upload/-/upload-24.8.3.tgz",
- "integrity": "sha512-zDoXWDn1zYRivSL3Rs6kfVSaZuj2aLVvbH5tlxXTzj4ewfTxv5A3S6iZdIDQTlkh0Nw1JoXBX1LqjlwiD383dg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/upload/-/upload-24.8.6.tgz",
+ "integrity": "sha512-/1y8UT0UUdrypnyOPTVIejDpk6pPNPbqcPhqRGtCHKUHAAdZKVjCFMe6NT4kzTFqvEICdKCWOtC57yiwPk8WvA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.3",
- "@vaadin/button": "~24.8.3",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/progress-bar": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/a11y-base": "~24.8.6",
+ "@vaadin/button": "~24.8.6",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/progress-bar": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
@@ -9249,32 +8964,32 @@
"license": "Apache-2.0"
},
"node_modules/@vaadin/vaadin-lumo-styles": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-24.8.3.tgz",
- "integrity": "sha512-vvcxwwtsrJgQeSeKGgTuMZ6VR8c/vIp7tBwcGg33PKdij83UKZwxMnnZjynvfQHzAxbE+TOa+uoYQJiI096JNg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-24.8.6.tgz",
+ "integrity": "sha512-b1db8UKEtzXPu9FWFwPZcydOkZvW9ApbeImWGBd/kn/gy1eeqwijF7slXdlwM7KrgoJJ/r+Rpok2nRYrbzp3xQ==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/icon": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3"
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/icon": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6"
}
},
"node_modules/@vaadin/vaadin-material-styles": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/vaadin-material-styles/-/vaadin-material-styles-24.8.3.tgz",
- "integrity": "sha512-shg4u8qnKh9k6QhakTRrMNvxpaQSipWxIha3GFpckDi937j3mQNd8Fi6Fe9r+4OL362fjYK0679HeaTwC+GtKA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/vaadin-material-styles/-/vaadin-material-styles-24.8.6.tgz",
+ "integrity": "sha512-69MwODqJ6ksiocutIG3dCTbPwrYKrZGN/To9YtYQzTuDt/3JsjmIRG2VLdvUVicKbdxnr6ZRRlPHoKGF5RS0RA==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3"
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6"
}
},
"node_modules/@vaadin/vaadin-themable-mixin": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-24.8.3.tgz",
- "integrity": "sha512-aYyMzxDF3OsDKmlxpuJNJIVPn8Yi4f/JJBmSOUUBrLmE4Z3+Ncr6F0aTMGVeeRbGDM7KAdJTYO5mM/ahJIxU+A==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-24.8.6.tgz",
+ "integrity": "sha512-CSJYyExRRk4o7ehjtlnGlhjXJgLAHqRTvXL3891STIB4syAgGuOFiHqWyyKr7CV6VFqa9DIHHkAXeZ9OeeIEVg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
@@ -9296,32 +9011,32 @@
}
},
"node_modules/@vaadin/vertical-layout": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/vertical-layout/-/vertical-layout-24.8.3.tgz",
- "integrity": "sha512-MjN36TbsjuKkc0X1B0bzXpMn8OaJiSaTss/KtK2iuuP9VZ/Ef8WWCzcQR165pwmFBRmsyJLoc+r2cH2z4XXAWg==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/vertical-layout/-/vertical-layout-24.8.6.tgz",
+ "integrity": "sha512-o5z9QYGaXghnspuzNWt20+4Xa5Fbdzlhs4Xuwj7CriS8+sXIi8ELuDpvBc0w3RPbWV7jm80PZciVAQWXFoTipQ==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/virtual-list": {
- "version": "24.8.3",
- "resolved": "https://registry.npmjs.org/@vaadin/virtual-list/-/virtual-list-24.8.3.tgz",
- "integrity": "sha512-ooXCM7a3VK31nmw28k9Fezbobk/qrL8+6L1ZMDRk7FD4aNKcyVUlU3oNU8uNaKl41Wvn+aEURnf9nLEl/pJnpA==",
+ "version": "24.8.6",
+ "resolved": "https://registry.npmjs.org/@vaadin/virtual-list/-/virtual-list-24.8.6.tgz",
+ "integrity": "sha512-H/5781WZunm3nGdfJ6PO1IWUUnRvdpKoJA+tmO6Ifdhe0j9weLSj5bczFrYcr/LMMGjXvWJdTN6oW3sp+jJN8A==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.3",
- "@vaadin/lit-renderer": "~24.8.3",
- "@vaadin/vaadin-lumo-styles": "~24.8.3",
- "@vaadin/vaadin-material-styles": "~24.8.3",
- "@vaadin/vaadin-themable-mixin": "~24.8.3",
+ "@vaadin/component-base": "~24.8.6",
+ "@vaadin/lit-renderer": "~24.8.6",
+ "@vaadin/vaadin-lumo-styles": "~24.8.6",
+ "@vaadin/vaadin-material-styles": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.8.6",
"lit": "^3.0.0"
}
},
@@ -14735,47 +14450,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/rollup": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
- "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@types/estree": "1.0.6"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.28.1",
- "@rollup/rollup-android-arm64": "4.28.1",
- "@rollup/rollup-darwin-arm64": "4.28.1",
- "@rollup/rollup-darwin-x64": "4.28.1",
- "@rollup/rollup-freebsd-arm64": "4.28.1",
- "@rollup/rollup-freebsd-x64": "4.28.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
- "@rollup/rollup-linux-arm64-gnu": "4.28.1",
- "@rollup/rollup-linux-arm64-musl": "4.28.1",
- "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
- "@rollup/rollup-linux-s390x-gnu": "4.28.1",
- "@rollup/rollup-linux-x64-gnu": "4.28.1",
- "@rollup/rollup-linux-x64-musl": "4.28.1",
- "@rollup/rollup-win32-arm64-msvc": "4.28.1",
- "@rollup/rollup-win32-ia32-msvc": "4.28.1",
- "@rollup/rollup-win32-x64-msvc": "4.28.1",
- "fsevents": "~2.3.2"
- }
- },
"node_modules/rollup-plugin-brotli": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-brotli/-/rollup-plugin-brotli-3.1.0.tgz",
diff --git a/app/package.json b/app/package.json
index c7e446a..083306a 100644
--- a/app/package.json
+++ b/app/package.json
@@ -9,22 +9,22 @@
"@polymer/polymer": "3.5.2",
"@react-stately/data": "^3.12.2",
"@react-types/shared": "^3.28.0",
- "@vaadin/bundles": "24.8.3",
+ "@vaadin/bundles": "24.8.6",
"@vaadin/common-frontend": "0.0.19",
- "@vaadin/hilla-file-router": "24.8.2",
- "@vaadin/hilla-frontend": "24.8.2",
- "@vaadin/hilla-lit-form": "24.8.2",
- "@vaadin/hilla-react-auth": "24.8.2",
- "@vaadin/hilla-react-crud": "24.8.2",
- "@vaadin/hilla-react-form": "24.8.2",
- "@vaadin/hilla-react-i18n": "24.8.2",
- "@vaadin/hilla-react-signals": "24.8.2",
- "@vaadin/polymer-legacy-adapter": "24.8.3",
- "@vaadin/react-components": "24.8.3",
+ "@vaadin/hilla-file-router": "24.8.7",
+ "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-lit-form": "24.8.7",
+ "@vaadin/hilla-react-auth": "24.8.7",
+ "@vaadin/hilla-react-crud": "24.8.7",
+ "@vaadin/hilla-react-form": "24.8.7",
+ "@vaadin/hilla-react-i18n": "24.8.7",
+ "@vaadin/hilla-react-signals": "24.8.7",
+ "@vaadin/polymer-legacy-adapter": "24.8.6",
+ "@vaadin/react-components": "24.8.6",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.3",
- "@vaadin/vaadin-material-styles": "24.8.3",
- "@vaadin/vaadin-themable-mixin": "24.8.3",
+ "@vaadin/vaadin-lumo-styles": "24.8.6",
+ "@vaadin/vaadin-material-styles": "24.8.6",
+ "@vaadin/vaadin-themable-mixin": "24.8.6",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"classnames": "^2.5.1",
"construct-style-sheets-polyfill": "3.1.0",
@@ -61,17 +61,17 @@
"@types/node": "^22.4.0",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
- "@vaadin/hilla-generator-cli": "24.8.2",
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.2",
- "@vaadin/hilla-generator-plugin-barrel": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-plugin-model": "24.8.2",
- "@vaadin/hilla-generator-plugin-push": "24.8.2",
- "@vaadin/hilla-generator-plugin-signals": "24.8.2",
- "@vaadin/hilla-generator-plugin-subtypes": "24.8.2",
- "@vaadin/hilla-generator-plugin-transfertypes": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-cli": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
+ "@vaadin/hilla-generator-plugin-barrel": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-plugin-model": "24.8.7",
+ "@vaadin/hilla-generator-plugin-push": "24.8.7",
+ "@vaadin/hilla-generator-plugin-signals": "24.8.7",
+ "@vaadin/hilla-generator-plugin-subtypes": "24.8.7",
+ "@vaadin/hilla-generator-plugin-transfertypes": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"@vitejs/plugin-react": "4.5.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"async": "3.2.6",
@@ -142,85 +142,85 @@
"valtio": "$valtio",
"valtio-reactive": "$valtio-reactive",
"fzf": "$fzf",
- "@vaadin/a11y-base": "24.8.3",
- "@vaadin/accordion": "24.8.3",
- "@vaadin/app-layout": "24.8.3",
- "@vaadin/avatar": "24.8.3",
- "@vaadin/avatar-group": "24.8.3",
- "@vaadin/button": "24.8.3",
- "@vaadin/card": "24.8.3",
- "@vaadin/checkbox": "24.8.3",
- "@vaadin/checkbox-group": "24.8.3",
- "@vaadin/combo-box": "24.8.3",
- "@vaadin/component-base": "24.8.3",
- "@vaadin/confirm-dialog": "24.8.3",
- "@vaadin/context-menu": "24.8.3",
- "@vaadin/custom-field": "24.8.3",
- "@vaadin/date-picker": "24.8.3",
- "@vaadin/date-time-picker": "24.8.3",
- "@vaadin/details": "24.8.3",
- "@vaadin/dialog": "24.8.3",
- "@vaadin/email-field": "24.8.3",
- "@vaadin/field-base": "24.8.3",
- "@vaadin/field-highlighter": "24.8.3",
- "@vaadin/form-layout": "24.8.3",
- "@vaadin/grid": "24.8.3",
- "@vaadin/horizontal-layout": "24.8.3",
- "@vaadin/icon": "24.8.3",
- "@vaadin/icons": "24.8.3",
- "@vaadin/input-container": "24.8.3",
- "@vaadin/integer-field": "24.8.3",
- "@vaadin/item": "24.8.3",
- "@vaadin/list-box": "24.8.3",
- "@vaadin/lit-renderer": "24.8.3",
- "@vaadin/login": "24.8.3",
- "@vaadin/markdown": "24.8.3",
- "@vaadin/master-detail-layout": "24.8.3",
- "@vaadin/menu-bar": "24.8.3",
- "@vaadin/message-input": "24.8.3",
- "@vaadin/message-list": "24.8.3",
- "@vaadin/multi-select-combo-box": "24.8.3",
- "@vaadin/notification": "24.8.3",
- "@vaadin/number-field": "24.8.3",
- "@vaadin/overlay": "24.8.3",
- "@vaadin/password-field": "24.8.3",
- "@vaadin/popover": "24.8.3",
- "@vaadin/progress-bar": "24.8.3",
- "@vaadin/radio-group": "24.8.3",
- "@vaadin/scroller": "24.8.3",
- "@vaadin/select": "24.8.3",
- "@vaadin/side-nav": "24.8.3",
- "@vaadin/split-layout": "24.8.3",
- "@vaadin/tabs": "24.8.3",
- "@vaadin/tabsheet": "24.8.3",
- "@vaadin/text-area": "24.8.3",
- "@vaadin/text-field": "24.8.3",
- "@vaadin/time-picker": "24.8.3",
- "@vaadin/tooltip": "24.8.3",
- "@vaadin/upload": "24.8.3",
+ "@vaadin/a11y-base": "24.8.6",
+ "@vaadin/accordion": "24.8.6",
+ "@vaadin/app-layout": "24.8.6",
+ "@vaadin/avatar": "24.8.6",
+ "@vaadin/avatar-group": "24.8.6",
+ "@vaadin/button": "24.8.6",
+ "@vaadin/card": "24.8.6",
+ "@vaadin/checkbox": "24.8.6",
+ "@vaadin/checkbox-group": "24.8.6",
+ "@vaadin/combo-box": "24.8.6",
+ "@vaadin/component-base": "24.8.6",
+ "@vaadin/confirm-dialog": "24.8.6",
+ "@vaadin/context-menu": "24.8.6",
+ "@vaadin/custom-field": "24.8.6",
+ "@vaadin/date-picker": "24.8.6",
+ "@vaadin/date-time-picker": "24.8.6",
+ "@vaadin/details": "24.8.6",
+ "@vaadin/dialog": "24.8.6",
+ "@vaadin/email-field": "24.8.6",
+ "@vaadin/field-base": "24.8.6",
+ "@vaadin/field-highlighter": "24.8.6",
+ "@vaadin/form-layout": "24.8.6",
+ "@vaadin/grid": "24.8.6",
+ "@vaadin/horizontal-layout": "24.8.6",
+ "@vaadin/icon": "24.8.6",
+ "@vaadin/icons": "24.8.6",
+ "@vaadin/input-container": "24.8.6",
+ "@vaadin/integer-field": "24.8.6",
+ "@vaadin/item": "24.8.6",
+ "@vaadin/list-box": "24.8.6",
+ "@vaadin/lit-renderer": "24.8.6",
+ "@vaadin/login": "24.8.6",
+ "@vaadin/markdown": "24.8.6",
+ "@vaadin/master-detail-layout": "24.8.6",
+ "@vaadin/menu-bar": "24.8.6",
+ "@vaadin/message-input": "24.8.6",
+ "@vaadin/message-list": "24.8.6",
+ "@vaadin/multi-select-combo-box": "24.8.6",
+ "@vaadin/notification": "24.8.6",
+ "@vaadin/number-field": "24.8.6",
+ "@vaadin/overlay": "24.8.6",
+ "@vaadin/password-field": "24.8.6",
+ "@vaadin/popover": "24.8.6",
+ "@vaadin/progress-bar": "24.8.6",
+ "@vaadin/radio-group": "24.8.6",
+ "@vaadin/scroller": "24.8.6",
+ "@vaadin/select": "24.8.6",
+ "@vaadin/side-nav": "24.8.6",
+ "@vaadin/split-layout": "24.8.6",
+ "@vaadin/tabs": "24.8.6",
+ "@vaadin/tabsheet": "24.8.6",
+ "@vaadin/text-area": "24.8.6",
+ "@vaadin/text-field": "24.8.6",
+ "@vaadin/time-picker": "24.8.6",
+ "@vaadin/tooltip": "24.8.6",
+ "@vaadin/upload": "24.8.6",
"@vaadin/router": "2.0.0",
- "@vaadin/vertical-layout": "24.8.3",
- "@vaadin/virtual-list": "24.8.3"
+ "@vaadin/vertical-layout": "24.8.6",
+ "@vaadin/virtual-list": "24.8.6"
},
"vaadin": {
"dependencies": {
"@polymer/polymer": "3.5.2",
- "@vaadin/bundles": "24.8.3",
+ "@vaadin/bundles": "24.8.6",
"@vaadin/common-frontend": "0.0.19",
- "@vaadin/hilla-file-router": "24.8.2",
- "@vaadin/hilla-frontend": "24.8.2",
- "@vaadin/hilla-lit-form": "24.8.2",
- "@vaadin/hilla-react-auth": "24.8.2",
- "@vaadin/hilla-react-crud": "24.8.2",
- "@vaadin/hilla-react-form": "24.8.2",
- "@vaadin/hilla-react-i18n": "24.8.2",
- "@vaadin/hilla-react-signals": "24.8.2",
- "@vaadin/polymer-legacy-adapter": "24.8.3",
- "@vaadin/react-components": "24.8.3",
+ "@vaadin/hilla-file-router": "24.8.7",
+ "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-lit-form": "24.8.7",
+ "@vaadin/hilla-react-auth": "24.8.7",
+ "@vaadin/hilla-react-crud": "24.8.7",
+ "@vaadin/hilla-react-form": "24.8.7",
+ "@vaadin/hilla-react-i18n": "24.8.7",
+ "@vaadin/hilla-react-signals": "24.8.7",
+ "@vaadin/polymer-legacy-adapter": "24.8.6",
+ "@vaadin/react-components": "24.8.6",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.3",
- "@vaadin/vaadin-material-styles": "24.8.3",
- "@vaadin/vaadin-themable-mixin": "24.8.3",
+ "@vaadin/vaadin-lumo-styles": "24.8.6",
+ "@vaadin/vaadin-material-styles": "24.8.6",
+ "@vaadin/vaadin-themable-mixin": "24.8.6",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3",
@@ -236,17 +236,17 @@
"@rollup/pluginutils": "5.1.4",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
- "@vaadin/hilla-generator-cli": "24.8.2",
- "@vaadin/hilla-generator-core": "24.8.2",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.2",
- "@vaadin/hilla-generator-plugin-barrel": "24.8.2",
- "@vaadin/hilla-generator-plugin-client": "24.8.2",
- "@vaadin/hilla-generator-plugin-model": "24.8.2",
- "@vaadin/hilla-generator-plugin-push": "24.8.2",
- "@vaadin/hilla-generator-plugin-signals": "24.8.2",
- "@vaadin/hilla-generator-plugin-subtypes": "24.8.2",
- "@vaadin/hilla-generator-plugin-transfertypes": "24.8.2",
- "@vaadin/hilla-generator-utils": "24.8.2",
+ "@vaadin/hilla-generator-cli": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.8.7",
+ "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
+ "@vaadin/hilla-generator-plugin-barrel": "24.8.7",
+ "@vaadin/hilla-generator-plugin-client": "24.8.7",
+ "@vaadin/hilla-generator-plugin-model": "24.8.7",
+ "@vaadin/hilla-generator-plugin-push": "24.8.7",
+ "@vaadin/hilla-generator-plugin-signals": "24.8.7",
+ "@vaadin/hilla-generator-plugin-subtypes": "24.8.7",
+ "@vaadin/hilla-generator-plugin-transfertypes": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.8.7",
"@vitejs/plugin-react": "4.5.0",
"async": "3.2.6",
"glob": "11.0.2",
@@ -263,6 +263,6 @@
"workbox-precaching": "7.3.0"
},
"disableUsageStatistics": true,
- "hash": "962eccc3fa0735d5234901be4f9e384096113c45bec22564a53688096d62aef4"
+ "hash": "e499c8893c397649c698f302e100ee1e48833c88e57bd0829fbf86dc5a14cfd8"
}
}
\ No newline at end of file
diff --git a/app/src/main/frontend/components/administration/GameRequestManagement.tsx b/app/src/main/frontend/components/administration/GameRequestManagement.tsx
new file mode 100644
index 0000000..5e87a32
--- /dev/null
+++ b/app/src/main/frontend/components/administration/GameRequestManagement.tsx
@@ -0,0 +1,49 @@
+import React from "react";
+import withConfigPage from "Frontend/components/administration/withConfigPage";
+import * as Yup from 'yup';
+import ConfigFormField from "Frontend/components/administration/ConfigFormField";
+import Section from "Frontend/components/general/Section";
+import {Button} from "@heroui/react";
+import {useNavigate} from "react-router";
+
+function GameRequestManagementLayout({getConfig, formik}: any) {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ navigate("/requests")}>
+ Manage game requests
+
+
+
+
+ );
+}
+
+const validationSchema = Yup.object({
+ requests: Yup.object({
+ games: Yup.object({
+ enabled: Yup.boolean().required("Required"),
+ "allow-guests-to-request-games": Yup.boolean().required("Required"),
+ "max-open-requests-per-user": Yup.number()
+ .min(0, "Must be at least 0")
+ .max(Number.MAX_SAFE_INTEGER, `Must be lower than ${Number.MAX_SAFE_INTEGER}`)
+ .required("Required"),
+ }).required("Required"),
+ }).required("Required"),
+});
+
+export const GameRequestManagement = withConfigPage(GameRequestManagementLayout, "Game Requests", validationSchema);
\ No newline at end of file
diff --git a/app/src/main/frontend/components/general/modals/RequestGameModal.tsx b/app/src/main/frontend/components/general/modals/RequestGameModal.tsx
index 453e66b..19d09f0 100644
--- a/app/src/main/frontend/components/general/modals/RequestGameModal.tsx
+++ b/app/src/main/frontend/components/general/modals/RequestGameModal.tsx
@@ -49,13 +49,19 @@ export default function RequestGameModal({
title: game.title,
release: game.release
}
- await GameRequestEndpoint.create(request);
- addToast({
- title: "Request submitted",
- description: `Your request for "${game.title}" has been submitted.`,
- color: "success"
- })
+ try {
+ await GameRequestEndpoint.create(request);
+
+ addToast({
+ title: "Request submitted",
+ description: `Your request for "${game.title}" has been submitted.`,
+ color: "success"
+ });
+ } catch (e) {
+ setIsSearching(false);
+ setIsRequesting(null);
+ }
}
async function search() {
diff --git a/app/src/main/frontend/routes.tsx b/app/src/main/frontend/routes.tsx
index db7e443..d06ef14 100644
--- a/app/src/main/frontend/routes.tsx
+++ b/app/src/main/frontend/routes.tsx
@@ -25,6 +25,7 @@ import LibraryView from "Frontend/views/LibraryView";
import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js";
import ErrorView from "Frontend/views/ErrorView";
import GameRequestView from "Frontend/views/GameRequestView";
+import {GameRequestManagement} from "Frontend/components/administration/GameRequestManagement";
export const {router, routes} = new RouterConfigurationBuilder()
.withReactRoutes([
@@ -93,6 +94,11 @@ export const {router, routes} = new RouterConfigurationBuilder()
element: ,
handle: {title: 'Administration - Library'}
},
+ {
+ path: 'requests',
+ element: ,
+ handle: {title: 'Administration - Game Requests'}
+ },
{
path: 'users',
element: ,
diff --git a/app/src/main/frontend/views/AdministrationView.tsx b/app/src/main/frontend/views/AdministrationView.tsx
index 456bdb5..b20d14d 100644
--- a/app/src/main/frontend/views/AdministrationView.tsx
+++ b/app/src/main/frontend/views/AdministrationView.tsx
@@ -1,4 +1,4 @@
-import {Envelope, GameController, LockKey, Log, Plug, Users, Wrench} from "@phosphor-icons/react";
+import {Disc, Envelope, GameController, LockKey, Log, Plug, Users, Wrench} from "@phosphor-icons/react";
import withSideMenu, {MenuItem} from "Frontend/components/general/withSideMenu";
const menuItems: MenuItem[] = [
@@ -7,6 +7,11 @@ const menuItems: MenuItem[] = [
url: "libraries",
icon:
},
+ {
+ title: "Game Requests",
+ url: "requests",
+ icon:
+ },
{
title: "Users",
url: "users",
diff --git a/app/src/main/frontend/views/GameRequestView.tsx b/app/src/main/frontend/views/GameRequestView.tsx
index 4346cbc..03a784a 100644
--- a/app/src/main/frontend/views/GameRequestView.tsx
+++ b/app/src/main/frontend/views/GameRequestView.tsx
@@ -16,22 +16,29 @@ import {
useDisclosure
} from "@heroui/react";
import RequestGameModal from "Frontend/components/general/modals/RequestGameModal";
-import {ArrowUp, Check, PlusCircle, X} from "@phosphor-icons/react";
-import React, {useMemo, useState} from "react";
+import {ArrowUp, Check, Info, PlusCircle, Trash, X} from "@phosphor-icons/react";
+import React, {useEffect, useMemo, useState} from "react";
import {useAuth} from "Frontend/util/auth";
-import {GameRequestEndpoint} from "Frontend/generated/endpoints";
+import {ConfigEndpoint, GameRequestEndpoint} from "Frontend/generated/endpoints";
import {gameRequestState} from "Frontend/state/GameRequestState";
import {useSnapshot} from "valtio/react";
import GameRequestDto from "Frontend/generated/org/gameyfin/app/requests/dto/GameRequestDto";
import GameRequestStatus from "Frontend/generated/org/gameyfin/app/requests/status/GameRequestStatus";
import {isAdmin} from "Frontend/util/utils";
+import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
export default function GameRequestView() {
const rowsPerPage = 25;
const auth = useAuth();
const requestGameModal = useDisclosure();
- const gameRequests = useSnapshot(gameRequestState).gameRequests
+ const gameRequests = useSnapshot(gameRequestState).gameRequests;
+
+ const [areGameRequestsEnabled, setAreGameRequestsEnabled] = useState(false);
+
+ useEffect(() => {
+ ConfigEndpoint.areGameRequestsEnabled().then(setAreGameRequestsEnabled);
+ }, []);
const [searchTerm, setSearchTerm] = useState("");
const [filters, setFilters] = useState<"all" | GameRequestStatus[]>([GameRequestStatus.PENDING, GameRequestStatus.APPROVED, GameRequestStatus.REJECTED]);
@@ -94,7 +101,6 @@ export default function GameRequestView() {
return sortedItems.slice(start, end);
}, [page, sortedItems]);
-
function getFilteredRequests() {
let filteredRequests = (gameRequests as GameRequestDto[]).filter((gameRequest) => {
return gameRequest.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -124,6 +130,10 @@ export default function GameRequestView() {
await GameRequestEndpoint.changeStatus(gameRequest.id, newStatus);
}
+ async function deleteRequest(gameRequestId: number) {
+ await GameRequestEndpoint.delete(gameRequestId);
+ }
+
function hasUserVotedForRequest(gameRequest: GameRequestDto): boolean {
if (!auth.state.user) return false;
return gameRequest.voters.map(v => v.id).includes(auth.state.user.id);
@@ -149,10 +159,16 @@ export default function GameRequestView() {
Game Requests
+ {!areGameRequestsEnabled &&
+
+ }
}
- onPress={requestGameModal.onOpen}>
+ onPress={requestGameModal.onOpen}
+ isDisabled={!areGameRequestsEnabled}>
Request a Game
@@ -176,8 +192,8 @@ export default function GameRequestView() {
>
Pending
Approved
-
Fulfilled
Rejected
+
Fulfilled
@@ -263,6 +279,15 @@ export default function GameRequestView() {
}
+ {(isAdmin(auth) || (auth.state.user && item.requester && auth.state.user.id === item.requester.id)) &&
+
+ await deleteRequest(item.id)}>
+
+
+
+ }
@@ -270,7 +295,6 @@ export default function GameRequestView() {
-
>)
diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx
index 268454d..4a00641 100644
--- a/app/src/main/frontend/views/MainLayout.tsx
+++ b/app/src/main/frontend/views/MainLayout.tsx
@@ -108,7 +108,11 @@ export default function MainLayout() {
}
{isAdmin(auth) &&
-
+
+
+
+
+
}
{auth.state.user &&
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
index d8db3b3..aed0e99 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigProperties.kt
@@ -109,7 +109,7 @@ sealed class ConfigProperties(
data object Enabled : ConfigProperties(
Boolean::class,
"requests.games.enabled",
- "Enable game requests",
+ "Enable submission of game requests",
true
)
@@ -123,7 +123,7 @@ sealed class ConfigProperties(
data object MaxOpenRequestsPerUser : ConfigProperties(
Int::class,
"requests.games.max-open-requests-per-user",
- "Maximum number of open (not yet fulfilled or rejected) requests per user. Set to 0 for unlimited.",
+ "Maximum number of pending requests per user. Set to 0 for unlimited.",
10
)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt b/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt
index bbea808..0713ba3 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt
@@ -2,5 +2,14 @@ package org.gameyfin.app.games.repositories
import org.gameyfin.app.games.entities.Game
import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+import org.springframework.data.repository.query.Param
+import java.time.Instant
-interface GameRepository : JpaRepository
\ No newline at end of file
+interface GameRepository : JpaRepository {
+ @Query("SELECT g FROM Game g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release)")
+ fun findByTitleAndReleaseYear(
+ @Param("title") title: String,
+ @Param("release") release: Instant?
+ ): List
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
index 433077a..d0d9468 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestRepository.kt
@@ -8,22 +8,22 @@ import org.springframework.data.repository.query.Param
import java.time.Instant
interface GameRequestRepository : JpaRepository {
- @Query("SELECT g FROM GameRequest g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release) AND g.status NOT IN (:excludedStatuses)")
- fun findOpenRequestsByTitleAndReleaseYear(
+ @Query("SELECT g FROM GameRequest g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release)")
+ fun findByTitleAndReleaseYear(
@Param("title") title: String,
- @Param("release") release: Instant?,
- @Param("excludedStatuses") excludedStatuses: List = listOf(
- GameRequestStatus.FULFILLED,
- GameRequestStatus.REJECTED
- )
+ @Param("release") release: Instant?
): List
- @Query("SELECT g FROM GameRequest g WHERE g.requester.id = :requesterId AND g.status NOT IN (:excludedStatuses)")
- fun findOpenRequestsByRequesterId(
+ @Query("SELECT g FROM GameRequest g WHERE g.title = :title AND YEAR(g.release) = YEAR(:release) AND g.status NOT IN (:excludedStatuses)")
+ fun findRequestsByTitleAndReleaseYearAndStatusNotIn(
+ @Param("title") title: String,
+ @Param("release") release: Instant?,
+ @Param("excludedStatuses") excludedStatuses: List
+ ): List
+
+ @Query("SELECT g FROM GameRequest g WHERE g.requester.id = :requesterId AND g.status IN (:statuses)")
+ fun findRequestsByRequesterIdAndStatusIn(
@Param("requesterId") requesterId: Long?,
- @Param("excludedStatuses") excludedStatuses: List = listOf(
- GameRequestStatus.FULFILLED,
- GameRequestStatus.REJECTED
- )
+ @Param("statuses") statuses: List
): List
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
index 665c5f6..c56ee14 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -1,11 +1,13 @@
package org.gameyfin.app.requests
+import com.vaadin.hilla.exception.EndpointException
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.config.ConfigProperties
import org.gameyfin.app.config.ConfigService
import org.gameyfin.app.core.events.GameCreatedEvent
import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.core.security.isAdmin
+import org.gameyfin.app.games.repositories.GameRepository
import org.gameyfin.app.requests.dto.GameRequestCreationDto
import org.gameyfin.app.requests.dto.GameRequestDto
import org.gameyfin.app.requests.dto.GameRequestEvent
@@ -26,9 +28,10 @@ import kotlin.time.toJavaDuration
@Service
class GameRequestService(
- private val gameRequestRepository: GameRequestRepository,
+ private val config: ConfigService,
private val userService: UserService,
- private val config: ConfigService
+ private val gameRequestRepository: GameRequestRepository,
+ private val gameRepository: GameRepository
) {
companion object {
@@ -63,17 +66,24 @@ class GameRequestService(
// Check if requests are enabled
if (config.get(ConfigProperties.Requests.Games.Enabled) != true) {
- throw IllegalStateException("Game requests are disabled")
+ throw EndpointException("Game requests are disabled")
+ }
+
+ // Check if game is already available
+ val existingGames = gameRepository.findByTitleAndReleaseYear(gameRequest.title, gameRequest.release)
+ if (existingGames.isNotEmpty()) {
+ throw EndpointException(
+ "This game is already available (ID: ${existingGames[0].id})"
+ )
}
// Check if a request with the same title and release year already exists
- val existingRequests = gameRequestRepository.findOpenRequestsByTitleAndReleaseYear(
+ val existingRequests = gameRequestRepository.findByTitleAndReleaseYear(
gameRequest.title,
- gameRequest.release,
- emptyList()
+ gameRequest.release
)
if (existingRequests.isNotEmpty()) {
- throw IllegalStateException("A request for this game already exists (ID: ${existingRequests[0].id})")
+ throw EndpointException("A request for this game already exists (ID: ${existingRequests[0].id})")
}
val auth = getCurrentAuth()
@@ -81,16 +91,19 @@ class GameRequestService(
// Check if guests are allowed to create requests
if (config.get(ConfigProperties.Requests.Games.AllowGuestsToRequestGames) != true && currentUser == null) {
- throw IllegalStateException("Only registered users can create game requests")
+ throw EndpointException("Only registered users can create game requests")
}
// Check if user has too many open requests (0 means no limit per user)
// Note: All guests are treated as a single user with null ID and thus share their request limit
// Note: Admins are exempt from this limit
- val openRequestsForUser = gameRequestRepository.findOpenRequestsByRequesterId(currentUser?.id)
+ val pendingRequestsForUser = gameRequestRepository.findRequestsByRequesterIdAndStatusIn(
+ currentUser?.id,
+ listOf(GameRequestStatus.PENDING)
+ )
val maxRequestsPerUser = config.get(ConfigProperties.Requests.Games.MaxOpenRequestsPerUser) ?: 0
- if (maxRequestsPerUser == 0 || (auth?.isAdmin() != true && openRequestsForUser.size >= maxRequestsPerUser)) {
- throw IllegalStateException("You have reached the maximum number of open requests (${maxRequestsPerUser})")
+ if (maxRequestsPerUser == 0 || (auth?.isAdmin() != true && pendingRequestsForUser.size >= maxRequestsPerUser)) {
+ throw EndpointException("You have reached the maximum number of pending requests (${maxRequestsPerUser})")
}
val newGameRequest = GameRequest(
@@ -115,8 +128,9 @@ class GameRequestService(
val requester = gameRequest.requester
// Check if the current user is the requester or an admin
- if (auth?.isAdmin() != true || requester == null || requester.id != currentUser?.id) {
- throw IllegalStateException("Only the requester or an admin can delete a game request")
+ // Note: Requests submitted by guests (request is null) can only be deleted by an admin
+ if (auth?.isAdmin() != true && (requester == null || requester.id != currentUser?.id)) {
+ throw EndpointException("Only the requester or an admin can delete a game request")
}
gameRequestRepository.delete(gameRequest)
@@ -170,9 +184,10 @@ class GameRequestService(
return
}
- val matchingRequests = gameRequestRepository.findOpenRequestsByTitleAndReleaseYear(
+ val matchingRequests = gameRequestRepository.findRequestsByTitleAndReleaseYearAndStatusNotIn(
gameTitle,
- gameRelease
+ gameRelease,
+ listOf(GameRequestStatus.FULFILLED)
)
matchingRequests.forEach { request ->
From 98991baefa1b04939dcd11ef2aed89854822c855 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 3 Sep 2025 16:42:58 +0200
Subject: [PATCH 13/32] Implement metadata completeness indicator Implement
sorting by completeness
---
.../general/MetadataCompletenessIndicator.tsx | 27 +++++++++++++++++++
.../library/LibraryManagementGames.tsx | 9 +++++++
app/src/main/frontend/util/utils.ts | 20 ++++++++++++++
.../org/gameyfin/app/games/dto/GameDto.kt | 4 +--
4 files changed, 58 insertions(+), 2 deletions(-)
create mode 100644 app/src/main/frontend/components/general/MetadataCompletenessIndicator.tsx
diff --git a/app/src/main/frontend/components/general/MetadataCompletenessIndicator.tsx b/app/src/main/frontend/components/general/MetadataCompletenessIndicator.tsx
new file mode 100644
index 0000000..bc2eaff
--- /dev/null
+++ b/app/src/main/frontend/components/general/MetadataCompletenessIndicator.tsx
@@ -0,0 +1,27 @@
+import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
+import {useMemo} from "react";
+import {CircularProgress} from "@heroui/react";
+import {metadataCompleteness} from "Frontend/util/utils";
+
+interface MetadataCompletenessIndicatorProps {
+ game: GameDto;
+}
+
+export default function MetadataCompletenessIndicator({game}: MetadataCompletenessIndicatorProps) {
+ const completeness = useMemo(() => metadataCompleteness(game), [game]);
+
+ const color = useMemo(() => {
+ return completeness > 80 ? "success" : completeness > 50 ? "warning" : "danger";
+ }, [completeness]);
+
+ return ;
+}
\ No newline at end of file
diff --git a/app/src/main/frontend/components/general/library/LibraryManagementGames.tsx b/app/src/main/frontend/components/general/library/LibraryManagementGames.tsx
index a6579c0..e211946 100644
--- a/app/src/main/frontend/components/general/library/LibraryManagementGames.tsx
+++ b/app/src/main/frontend/components/general/library/LibraryManagementGames.tsx
@@ -26,6 +26,8 @@ import {useMemo, useState} from "react";
import EditGameMetadataModal from "Frontend/components/general/modals/EditGameMetadataModal";
import MatchGameModal from "Frontend/components/general/modals/MatchGameModal";
import {GameAdminDto} from "Frontend/dtos/GameDtos";
+import MetadataCompletenessIndicator from "Frontend/components/general/MetadataCompletenessIndicator";
+import {metadataCompleteness} from "Frontend/util/utils";
interface LibraryManagementGamesProps {
library: LibraryDto;
@@ -67,6 +69,9 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
case "downloadCount":
cmp = a.metadata.downloadCount - b.metadata.downloadCount;
break;
+ case "completeness":
+ cmp = metadataCompleteness(a) - metadataCompleteness(b);
+ break;
default:
return 0; // No sorting if the column is not recognized
}
@@ -160,6 +165,7 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
Added to library
Download count
Path
+ Completeness
{/* width={1} keeps the column as far to the right as possible*/}
Actions
@@ -182,6 +188,9 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
{item.metadata.path}
+
+
+
toggleMatchConfirmed(item)}>
diff --git a/app/src/main/frontend/util/utils.ts b/app/src/main/frontend/util/utils.ts
index 5aac492..0511b61 100644
--- a/app/src/main/frontend/util/utils.ts
+++ b/app/src/main/frontend/util/utils.ts
@@ -1,5 +1,6 @@
import {getCsrfToken} from "Frontend/util/auth";
import moment from 'moment-timezone';
+import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
export function isAdmin(auth: any): boolean {
return auth.state.user?.roles?.some((a: string) => a?.includes("ADMIN"));
@@ -207,4 +208,23 @@ export function fileNameFromPath(path: string, includeExtension: boolean = true)
}
const dotIndex = fileName.lastIndexOf('.');
return dotIndex < 0 ? fileName : fileName.substring(0, dotIndex);
+}
+
+/** Calculate the completeness of a GameDto
+ * @param game
+ * @returns completeness percentage (0-100)
+ */
+export function metadataCompleteness(game: GameDto) {
+ // Total number of fields considered for completeness
+ // Includes all fields except "comment"
+ const totalFields = 21;
+
+ const filledFields = Object.values(game).filter(value => {
+ if (value === null || value === undefined) return false;
+ if (Array.isArray(value)) return value.length > 0;
+ if (typeof value === "string") return value.trim().length > 0;
+ return true;
+ }).length;
+
+ return Math.round((filledFields / totalFields) * 100);
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameDto.kt b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameDto.kt
index 8e0fa66..b92a5d6 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameDto.kt
@@ -29,7 +29,7 @@ sealed interface GameDto {
val metadata: GameMetadataDto
}
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.ALWAYS)
data class GameUserDto(
override val id: Long,
override val createdAt: Instant,
@@ -55,7 +55,7 @@ data class GameUserDto(
override val metadata: GameMetadataUserDto
) : GameDto
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.ALWAYS)
data class GameAdminDto(
override val id: Long,
override val createdAt: Instant,
From 153001fdfea408603257dcf17b9e63eea2b2edd7 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 3 Sep 2025 18:20:14 +0200
Subject: [PATCH 14/32] Bump version to 2.1.0-preview
---
app/package.json | 2 +-
build.gradle.kts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/package.json b/app/package.json
index 083306a..2cdd2dd 100644
--- a/app/package.json
+++ b/app/package.json
@@ -1,6 +1,6 @@
{
"name": "gameyfin",
- "version": "2.0.1",
+ "version": "2.1.0-preview",
"type": "module",
"dependencies": {
"@heroui/react": "2.7.9",
diff --git a/build.gradle.kts b/build.gradle.kts
index fd5349d..6e8407f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -6,7 +6,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import java.nio.file.Files
group = "org.gameyfin"
-version = "2.0.1"
+version = "2.1.0-preview"
allprojects {
repositories {
From 4ae78b01a85ec94e5ac372132a56a5e02e41af05 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 3 Sep 2025 20:26:44 +0200
Subject: [PATCH 15/32] Fixed various smaller issues
---
.../main/frontend/views/GameRequestView.tsx | 4 ++-
app/src/main/frontend/views/MainLayout.tsx | 22 ++++++++--------
.../org/gameyfin/app/config/ConfigEndpoint.kt | 5 ++++
.../app/requests/GameRequestService.kt | 2 +-
.../org/gameyfin/app/users/UserService.kt | 4 ++-
.../org/gameyfin/app/users/entities/User.kt | 3 ++-
.../app/users/entities/UserEntityListener.kt | 25 +++++++++++++++++++
.../gameyfin/app/util/EntityManagerHolder.kt | 19 ++++++++++++++
8 files changed, 68 insertions(+), 16 deletions(-)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt
diff --git a/app/src/main/frontend/views/GameRequestView.tsx b/app/src/main/frontend/views/GameRequestView.tsx
index 03a784a..427229e 100644
--- a/app/src/main/frontend/views/GameRequestView.tsx
+++ b/app/src/main/frontend/views/GameRequestView.tsx
@@ -35,9 +35,11 @@ export default function GameRequestView() {
const gameRequests = useSnapshot(gameRequestState).gameRequests;
const [areGameRequestsEnabled, setAreGameRequestsEnabled] = useState(false);
+ const [areGuestsAllowedToRequestGames, setAreGuestsAllowedToRequestGames] = useState(false);
useEffect(() => {
ConfigEndpoint.areGameRequestsEnabled().then(setAreGameRequestsEnabled);
+ ConfigEndpoint.areGuestsAllowedToRequestGames().then(setAreGuestsAllowedToRequestGames);
}, []);
const [searchTerm, setSearchTerm] = useState("");
@@ -168,7 +170,7 @@ export default function GameRequestView() {
color="primary"
startContent={ }
onPress={requestGameModal.onOpen}
- isDisabled={!areGameRequestsEnabled}>
+ isDisabled={!areGameRequestsEnabled || (!auth.state.user && !areGuestsAllowedToRequestGames)}>
Request a Game
diff --git a/app/src/main/frontend/views/MainLayout.tsx b/app/src/main/frontend/views/MainLayout.tsx
index 4a00641..a8aa302 100644
--- a/app/src/main/frontend/views/MainLayout.tsx
+++ b/app/src/main/frontend/views/MainLayout.tsx
@@ -94,18 +94,16 @@ export default function MainLayout() {
}
- {auth.state.user &&
-
-
- navigate("/requests")}
- startContent={ }>
- Requests
-
-
-
- }
+
+
+ navigate("/requests")}
+ startContent={ }>
+ Requests
+
+
+
{isAdmin(auth) &&
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
index 395c038..67b11ff 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
@@ -59,4 +59,9 @@ class ConfigEndpoint(
@DynamicPublicAccess
@AnonymousAllowed
fun areGameRequestsEnabled(): Boolean = configService.get(ConfigProperties.Requests.Games.Enabled) == true
+
+ @DynamicPublicAccess
+ @AnonymousAllowed
+ fun areGuestsAllowedToRequestGames(): Boolean =
+ configService.get(ConfigProperties.Requests.Games.AllowGuestsToRequestGames) == true
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
index c56ee14..3f81d65 100644
--- a/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/requests/GameRequestService.kt
@@ -91,7 +91,7 @@ class GameRequestService(
// Check if guests are allowed to create requests
if (config.get(ConfigProperties.Requests.Games.AllowGuestsToRequestGames) != true && currentUser == null) {
- throw EndpointException("Only registered users can create game requests")
+ throw EndpointException("Only registered users can submit game requests")
}
// Check if user has too many open requests (0 means no limit per user)
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
index b98b41b..c3d58a4 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
@@ -99,7 +99,9 @@ class UserService(
if (principal is OidcUser) {
val oidcUser = org.gameyfin.app.users.entities.User(principal)
- val userInfoDto = oidcUser.toExtendedUserInfoDto()
+ val user = userRepository.findByOidcProviderId(oidcUser.oidcProviderId!!)
+ ?: throw UsernameNotFoundException("Unknown OIDC user with provider ID '${oidcUser.oidcProviderId}'")
+ val userInfoDto = user.toExtendedUserInfoDto()
userInfoDto.roles = roleService.extractGrantedAuthorities(principal.authorities)
.mapNotNull { Role.safeValueOf(it.authority) }
return userInfoDto
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/entities/User.kt b/app/src/main/kotlin/org/gameyfin/app/users/entities/User.kt
index f170bb0..9c5994f 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/entities/User.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/entities/User.kt
@@ -1,14 +1,15 @@
package org.gameyfin.app.users.entities
-import org.gameyfin.app.games.entities.Image
import jakarta.persistence.*
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.security.EncryptionConverter
+import org.gameyfin.app.games.entities.Image
import org.springframework.security.oauth2.core.oidc.user.OidcUser
@Entity
@Table(name = "users")
+@EntityListeners(UserEntityListener::class)
class User(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt
new file mode 100644
index 0000000..9779485
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt
@@ -0,0 +1,25 @@
+package org.gameyfin.app.users.entities
+
+import jakarta.persistence.EntityManager
+import jakarta.persistence.PreRemove
+import org.gameyfin.app.requests.entities.GameRequest
+import org.gameyfin.app.util.EntityManagerHolder
+
+class UserEntityListener {
+
+ @PreRemove
+ fun preRemove(user: User) {
+ val entityManager: EntityManager = EntityManagerHolder.getEntityManager()
+
+ // Remove user from all GameRequest voters and requester fields
+ val gameRequests = entityManager.createQuery(
+ "SELECT gr FROM GameRequest gr WHERE :user MEMBER OF gr.voters OR gr.requester = :user",
+ GameRequest::class.java
+ ).setParameter("user", user).resultList
+ for (gr in gameRequests) {
+ gr.voters.remove(user)
+ if (gr.requester == user) gr.requester = null
+ entityManager.merge(gr)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt b/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt
new file mode 100644
index 0000000..08059b7
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/util/EntityManagerHolder.kt
@@ -0,0 +1,19 @@
+package org.gameyfin.app.util
+
+import jakarta.persistence.EntityManager
+import org.springframework.context.ApplicationContext
+import org.springframework.context.ApplicationContextAware
+import org.springframework.stereotype.Component
+
+@Component
+object EntityManagerHolder : ApplicationContextAware {
+ private var entityManager: EntityManager? = null
+
+ override fun setApplicationContext(context: ApplicationContext) {
+ entityManager = context.getBean(EntityManager::class.java)
+ }
+
+ fun getEntityManager(): EntityManager {
+ return entityManager ?: throw IllegalStateException("EntityManager not set")
+ }
+}
\ No newline at end of file
From cc53a6faaa35cd77338793dc45efa783fb22de97 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Thu, 4 Sep 2025 12:44:15 +0200
Subject: [PATCH 16/32] Implement sorting in SearchView
---
.../main/frontend/views/GameRequestView.tsx | 22 +--
app/src/main/frontend/views/SearchView.tsx | 147 ++++++++++++++----
2 files changed, 134 insertions(+), 35 deletions(-)
diff --git a/app/src/main/frontend/views/GameRequestView.tsx b/app/src/main/frontend/views/GameRequestView.tsx
index 427229e..c734979 100644
--- a/app/src/main/frontend/views/GameRequestView.tsx
+++ b/app/src/main/frontend/views/GameRequestView.tsx
@@ -251,15 +251,19 @@ export default function GameRequestView() {
-
- }
- onPress={async () => await toggleVote(item.id)}>
- {item.voters.length}
-
+
+
+ }
+ onPress={async () => await toggleVote(item.id)}>
+ {item.voters.length}
+
+
{isAdmin(auth) &&
diff --git a/app/src/main/frontend/views/SearchView.tsx b/app/src/main/frontend/views/SearchView.tsx
index a7c7074..1aa83c7 100644
--- a/app/src/main/frontend/views/SearchView.tsx
+++ b/app/src/main/frontend/views/SearchView.tsx
@@ -1,10 +1,10 @@
-import {Input, Select, SelectItem} from "@heroui/react";
-import {MagnifyingGlass} from "@phosphor-icons/react";
+import {Button, Input, Select, SelectItem, Tooltip} from "@heroui/react";
+import {FunnelSimple, FunnelSimpleX, MagnifyingGlass, SortAscending} from "@phosphor-icons/react";
import {useSnapshot} from "valtio/react";
import {gameState} from "Frontend/state/GameState";
import {libraryState} from "Frontend/state/LibraryState";
import {useSearchParams} from "react-router";
-import {useEffect, useMemo, useState} from "react";
+import React, {useEffect, useMemo, useState} from "react";
import {Fzf} from "fzf";
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
import LibraryDto from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryDto";
@@ -24,6 +24,9 @@ export default function SearchView() {
const [searchParams, setSearchParams] = useSearchParams();
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
+ const [showFilters, setShowFilters] = useState(false);
+ const [sortBy, setSortBy] = useState("title_asc");
+
// State to track selected filter values
const [searchTerm, setSearchTerm] = useState("");
const [selectedLibraries, setSelectedLibraries] = useState>(new Set());
@@ -45,6 +48,7 @@ export default function SearchView() {
const features = searchParams.getAll("feature");
const perspectives = searchParams.getAll("perspective");
const keywords = searchParams.getAll("keyword");
+ const sort = searchParams.get("sort") || "title_asc";
setSearchTerm(term);
setSelectedLibraries(new Set(libs));
@@ -54,11 +58,12 @@ export default function SearchView() {
setSelectedFeatures(new Set(features));
setSelectedPerspectives(new Set(perspectives));
setSelectedKeywords(new Set(keywords));
+ setSortBy(sort);
setInitialLoadComplete(true);
}, []);
- // Update search parameters whenever the filters change
+ // Update search parameters whenever the filters or sort change
useEffect(() => {
if (!initialLoadComplete) return;
@@ -112,15 +117,55 @@ export default function SearchView() {
});
}
+ // Add sort param
+ if (sortBy && sortBy !== "title_asc") {
+ newParams.set("sort", sortBy);
+ }
+
setSearchParams(newParams, {replace: true});
}, [searchTerm, selectedLibraries, selectedDevelopers, selectedGenres,
- selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords]);
+ selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords, sortBy]);
- const filteredGames = useMemo(() => filterGames(), [
+ // Sorting function (refactored to use sortKey and sortDirection)
+ function sortGames(games: GameDto[]): GameDto[] {
+ if (!sortBy) return games;
+
+ const [sortKey, sortDirection] = sortBy.split("_");
+
+ return [...games].sort((a, b) => {
+ let cmp: number;
+
+ switch (sortKey) {
+ case "title":
+ cmp = a.title.localeCompare(b.title);
+ break;
+ case "release":
+ cmp = (a.release || "").localeCompare(b.release || "");
+ break;
+ case "rating":
+ cmp = (a.criticRating ?? 0) - (b.criticRating ?? 0);
+ break;
+ case "added":
+ cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
+ break;
+ case "updated":
+ cmp = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
+ break;
+ default:
+ cmp = 0;
+ }
+ if (sortDirection === "desc") {
+ cmp *= -1; // Reverse the comparison if sorting in descending order
+ }
+ return cmp;
+ });
+ }
+
+ const filteredAndSortedGames = useMemo(() => sortGames(filterGames()), [
games, searchTerm,
selectedLibraries, selectedDevelopers,
selectedGenres, selectedThemes,
- selectedFeatures, selectedPerspectives, selectedKeywords
+ selectedFeatures, selectedPerspectives, selectedKeywords, sortBy
]);
function filterGames(): GameDto[] {
@@ -185,22 +230,71 @@ export default function SearchView() {
}
return
-
}
- type="search"
- value={searchTerm}
- isClearable
- onChange={(event) => setSearchTerm(event.target.value)}
- onClear={() => setSearchTerm("")}
- />
-
+
}
+ type="search"
+ value={searchTerm}
+ isClearable
+ onChange={(event) => setSearchTerm(event.target.value)}
+ onClear={() => setSearchTerm("")}
+ />
+
+ }
+ selectedKeys={[sortBy]}
+ disallowEmptySelection
+ selectionMode="single"
+ onSelectionChange={keys => setSortBy(Array.from(keys)[0] as any)}
+ className="w-full lg:w-64"
+ >
+ Title (A-Z)
+ Title (Z-A)
+ Release Date (Newest)
+ Release Date (Oldest)
+ Rating (Highest)
+ Rating (Lowest)
+ Date Added (Newest)
+ Date Added (Oldest)
+ Last Updated (Newest)
+ Last Updated (Oldest)
+
+
+ setShowFilters(!showFilters)}
+ aria-label="Toggle Filters"
+ >
+
+
+
+
+ {
+ setSelectedLibraries(new Set());
+ setSelectedDevelopers(new Set());
+ setSelectedGenres(new Set());
+ setSelectedThemes(new Set());
+ setSelectedFeatures(new Set());
+ setSelectedPerspectives(new Set());
+ setSelectedKeywords(new Set());
+ }}
+ aria-label="Clear All Filters"
+ >
+
+
+
+
+
+ {showFilters &&
-
-
- {filteredGames.length === 0 && (
+ }
+
+
+ {filteredAndSortedGames.length === 0 && (
No games found matching your filters
From ab0f28e94f5a79aaccbb4fa587b91b16f35e9722 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Thu, 4 Sep 2025 17:20:13 +0200
Subject: [PATCH 17/32] Show rating in GameView Implement filter by rating
---
.../administration/ProfileManagement.tsx | 2 +-
.../general/modals/GameCoverPickerModal.tsx | 2 +-
.../general/modals/GameHeaderPickerModal.tsx | 2 +-
app/src/main/frontend/util/utils.ts | 35 +++++++++-
.../main/frontend/views/GameRequestView.tsx | 4 +-
app/src/main/frontend/views/GameView.tsx | 19 ++++--
app/src/main/frontend/views/SearchView.tsx | 65 +++++++++++++++----
7 files changed, 105 insertions(+), 24 deletions(-)
diff --git a/app/src/main/frontend/components/administration/ProfileManagement.tsx b/app/src/main/frontend/components/administration/ProfileManagement.tsx
index c965cc1..246c275 100644
--- a/app/src/main/frontend/components/administration/ProfileManagement.tsx
+++ b/app/src/main/frontend/components/administration/ProfileManagement.tsx
@@ -91,7 +91,7 @@ export default function ProfileManagement() {
{formik.values.newPassword.length > 0 &&
}
No results found.
}
{searchResults.length === 0 && isSearching &&
- Searching...
+ Searching...
}
diff --git a/app/src/main/frontend/components/general/modals/GameHeaderPickerModal.tsx b/app/src/main/frontend/components/general/modals/GameHeaderPickerModal.tsx
index 9bd95b0..31c00b3 100644
--- a/app/src/main/frontend/components/general/modals/GameHeaderPickerModal.tsx
+++ b/app/src/main/frontend/components/general/modals/GameHeaderPickerModal.tsx
@@ -81,7 +81,7 @@ export function GameHeaderPickerModal({game, isOpen, onOpenChange, setHeaderUrl}
No results found.
}
{searchResults.length === 0 && isSearching &&
- Searching...
+ Searching...
}
diff --git a/app/src/main/frontend/util/utils.ts b/app/src/main/frontend/util/utils.ts
index 0511b61..6b5dd9f 100644
--- a/app/src/main/frontend/util/utils.ts
+++ b/app/src/main/frontend/util/utils.ts
@@ -210,7 +210,8 @@ export function fileNameFromPath(path: string, includeExtension: boolean = true)
return dotIndex < 0 ? fileName : fileName.substring(0, dotIndex);
}
-/** Calculate the completeness of a GameDto
+/**
+ * Calculate the completeness of a GameDto
* @param game
* @returns completeness percentage (0-100)
*/
@@ -227,4 +228,36 @@ export function metadataCompleteness(game: GameDto) {
}).length;
return Math.round((filledFields / totalFields) * 100);
+}
+
+/**
+ * Scale a number from one range to another
+ * @param value The number to scale
+ * @param originalRange The original range [min, max]
+ * @param targetRange The target range [min, max]
+ * @returns The scaled number
+ */
+function convertRange(value: number, originalRange: number[], targetRange: number[]): number {
+ return (value - originalRange[0]) * (targetRange[1] - targetRange[0]) / (originalRange[1] - originalRange[0]) + targetRange[0];
+}
+
+/**
+ * Convert a GameDto's ratings to a star rating out of 5.
+ * If both criticRating and userRating are present, their average is taken.
+ * If neither is present, "N/A" is returned.
+ * @param game The GameDto object containing the ratings.
+ * @returns A string representing the star rating out of 5, or "N/A" if no ratings are available.
+ */
+export function gameRatingInStars(game: GameDto) {
+ if (!game.criticRating && !game.userRating) return "N/A";
+
+ const originalRange = [0, 100];
+ const starRange = [1, 5];
+ const ratings = [];
+
+ if (game.criticRating) ratings.push(game.criticRating);
+ if (game.userRating) ratings.push(game.userRating);
+ const avgRating = ratings.reduce((a, b) => a + b, 0) / ratings.length;
+
+ return convertRange(avgRating, originalRange, starRange).toFixed(1);
}
\ No newline at end of file
diff --git a/app/src/main/frontend/views/GameRequestView.tsx b/app/src/main/frontend/views/GameRequestView.tsx
index c734979..3510975 100644
--- a/app/src/main/frontend/views/GameRequestView.tsx
+++ b/app/src/main/frontend/views/GameRequestView.tsx
@@ -164,7 +164,7 @@ export default function GameRequestView() {
{!areGameRequestsEnabled &&
+ className="text-default-500"/>
}
-
+
{item.requester ?
item.requester.username :
"Guest"
diff --git a/app/src/main/frontend/views/GameView.tsx b/app/src/main/frontend/views/GameView.tsx
index 44ac19e..92cc8f4 100644
--- a/app/src/main/frontend/views/GameView.tsx
+++ b/app/src/main/frontend/views/GameView.tsx
@@ -5,11 +5,11 @@ import {GameCover} from "Frontend/components/general/covers/GameCover";
import ComboButton, {ComboButtonOption} from "Frontend/components/general/input/ComboButton";
import ImageCarousel from "Frontend/components/general/covers/ImageCarousel";
import {Accordion, AccordionItem, addToast, Button, Chip, Link, Tooltip, useDisclosure} from "@heroui/react";
-import {humanFileSize, isAdmin, toTitleCase} from "Frontend/util/utils";
+import {gameRatingInStars, humanFileSize, isAdmin, toTitleCase} from "Frontend/util/utils";
import {DownloadEndpoint} from "Frontend/endpoints/endpoints";
import {gameState} from "Frontend/state/GameState";
import {useSnapshot} from "valtio/react";
-import {CheckCircle, Info, MagnifyingGlass, Pencil, Trash, TriangleDashed} from "@phosphor-icons/react";
+import {CheckCircle, Info, MagnifyingGlass, Pencil, Star, Trash, TriangleDashed} from "@phosphor-icons/react";
import {useAuth} from "Frontend/util/auth";
import MatchGameModal from "Frontend/components/general/modals/MatchGameModal";
import EditGameMetadataModal from "Frontend/components/general/modals/EditGameMetadataModal";
@@ -102,14 +102,23 @@ export default function GameView() {
-
{game.title}
+
+
+ {game.title}
+
+
+
+ {gameRatingInStars(game)}
+
+
{game.release !== undefined ? new Date(game.release).getFullYear() :
no data
}
-
+
diff --git a/app/src/main/frontend/views/SearchView.tsx b/app/src/main/frontend/views/SearchView.tsx
index 1aa83c7..520e1c1 100644
--- a/app/src/main/frontend/views/SearchView.tsx
+++ b/app/src/main/frontend/views/SearchView.tsx
@@ -1,5 +1,5 @@
-import {Button, Input, Select, SelectItem, Tooltip} from "@heroui/react";
-import {FunnelSimple, FunnelSimpleX, MagnifyingGlass, SortAscending} from "@phosphor-icons/react";
+import {Button, Input, Select, SelectedItems, SelectItem, Tooltip} from "@heroui/react";
+import {FunnelSimple, FunnelSimpleX, MagnifyingGlass, SortAscending, Star} from "@phosphor-icons/react";
import {useSnapshot} from "valtio/react";
import {gameState} from "Frontend/state/GameState";
import {libraryState} from "Frontend/state/LibraryState";
@@ -9,7 +9,7 @@ import {Fzf} from "fzf";
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
import LibraryDto from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryDto";
import CoverGrid from "Frontend/components/general/covers/CoverGrid";
-import {toTitleCase} from "Frontend/util/utils";
+import {gameRatingInStars, toTitleCase} from "Frontend/util/utils";
export default function SearchView() {
const games = useSnapshot(gameState).sortedAlphabetically as GameDto[];
@@ -36,6 +36,7 @@ export default function SearchView() {
const [selectedFeatures, setSelectedFeatures] = useState
>(new Set());
const [selectedPerspectives, setSelectedPerspectives] = useState>(new Set());
const [selectedKeywords, setSelectedKeywords] = useState>(new Set());
+ const [minRating, setMinRating] = useState(1); // Minimum rating filter
// Load initial filter values from URL parameters on component mount
useEffect(() => {
@@ -49,6 +50,7 @@ export default function SearchView() {
const perspectives = searchParams.getAll("perspective");
const keywords = searchParams.getAll("keyword");
const sort = searchParams.get("sort") || "title_asc";
+ const minRatingParam = parseInt(searchParams.get("minRating") || "1", 10);
setSearchTerm(term);
setSelectedLibraries(new Set(libs));
@@ -59,6 +61,7 @@ export default function SearchView() {
setSelectedPerspectives(new Set(perspectives));
setSelectedKeywords(new Set(keywords));
setSortBy(sort);
+ setMinRating(isNaN(minRatingParam) ? 1 : minRatingParam);
setInitialLoadComplete(true);
}, []);
@@ -80,43 +83,40 @@ export default function SearchView() {
newParams.append("lib", lib.toString());
});
}
-
if (selectedDevelopers.size > 0) {
selectedDevelopers.forEach(dev => {
newParams.append("dev", dev);
});
}
-
if (selectedGenres.size > 0) {
selectedGenres.forEach(genre => {
newParams.append("genre", genre);
});
}
-
if (selectedThemes.size > 0) {
selectedThemes.forEach(theme => {
newParams.append("theme", theme);
});
}
-
if (selectedFeatures.size > 0) {
selectedFeatures.forEach(feature => {
newParams.append("feature", feature);
});
}
-
if (selectedPerspectives.size > 0) {
selectedPerspectives.forEach(perspective => {
newParams.append("perspective", perspective);
});
}
-
if (selectedKeywords.size > 0) {
selectedKeywords.forEach(keyword => {
newParams.append("keyword", keyword);
});
}
-
+ // Add minRating param if not default
+ if (minRating > 1) {
+ newParams.set("minRating", minRating.toString());
+ }
// Add sort param
if (sortBy && sortBy !== "title_asc") {
newParams.set("sort", sortBy);
@@ -124,7 +124,7 @@ export default function SearchView() {
setSearchParams(newParams, {replace: true});
}, [searchTerm, selectedLibraries, selectedDevelopers, selectedGenres,
- selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords, sortBy]);
+ selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords, sortBy, minRating]);
// Sorting function (refactored to use sortKey and sortDirection)
function sortGames(games: GameDto[]): GameDto[] {
@@ -165,7 +165,7 @@ export default function SearchView() {
games, searchTerm,
selectedLibraries, selectedDevelopers,
selectedGenres, selectedThemes,
- selectedFeatures, selectedPerspectives, selectedKeywords, sortBy
+ selectedFeatures, selectedPerspectives, selectedKeywords, sortBy, minRating
]);
function filterGames(): GameDto[] {
@@ -226,9 +226,30 @@ export default function SearchView() {
);
}
+ // Apply minimum rating filter
+ if (minRating > 1) {
+ filtered = filtered.filter(game => {
+ const ratingStr = gameRatingInStars(game);
+ if (ratingStr === "N/A") return false;
+ const ratingNum = parseFloat(ratingStr);
+ return ratingNum >= minRating;
+ });
+ }
return filtered;
}
+ function stars(filled: number, total: number = 5) {
+ const stars = [];
+ for (let i = 0; i < total; i++) {
+ stars.push(
+
+ );
+ }
+ return
+ {stars}
+
;
+ }
+
return
{showFilters && {library.name}
))}
+
setMinRating(parseInt(Array.from(keys)[0] as string, 10))}
+ renderValue={(items: SelectedItems) => {
+ return items.map((item) => stars(parseInt(item.key as string)));
+ }}
+ >
+ {stars(1)}
+ {stars(2)}
+ {stars(3)}
+ {stars(4)}
+ {stars(5)}
+
Date: Thu, 4 Sep 2025 17:41:58 +0200
Subject: [PATCH 18/32] Sync showFilter with URL Make 5 stars filter actually
"higher than 4.5"
---
app/src/main/frontend/views/SearchView.tsx | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/app/src/main/frontend/views/SearchView.tsx b/app/src/main/frontend/views/SearchView.tsx
index 520e1c1..0125b0d 100644
--- a/app/src/main/frontend/views/SearchView.tsx
+++ b/app/src/main/frontend/views/SearchView.tsx
@@ -51,6 +51,7 @@ export default function SearchView() {
const keywords = searchParams.getAll("keyword");
const sort = searchParams.get("sort") || "title_asc";
const minRatingParam = parseInt(searchParams.get("minRating") || "1", 10);
+ const filtersParam = searchParams.get("filters");
setSearchTerm(term);
setSelectedLibraries(new Set(libs));
@@ -62,6 +63,7 @@ export default function SearchView() {
setSelectedKeywords(new Set(keywords));
setSortBy(sort);
setMinRating(isNaN(minRatingParam) ? 1 : minRatingParam);
+ setShowFilters(filtersParam === "1");
setInitialLoadComplete(true);
}, []);
@@ -121,10 +123,14 @@ export default function SearchView() {
if (sortBy && sortBy !== "title_asc") {
newParams.set("sort", sortBy);
}
+ // Add showFilters param
+ if (showFilters) {
+ newParams.set("filters", "1");
+ }
setSearchParams(newParams, {replace: true});
}, [searchTerm, selectedLibraries, selectedDevelopers, selectedGenres,
- selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords, sortBy, minRating]);
+ selectedThemes, selectedFeatures, selectedPerspectives, selectedKeywords, sortBy, minRating, showFilters]);
// Sorting function (refactored to use sortKey and sortDirection)
function sortGames(games: GameDto[]): GameDto[] {
@@ -232,6 +238,9 @@ export default function SearchView() {
const ratingStr = gameRatingInStars(game);
if (ratingStr === "N/A") return false;
const ratingNum = parseFloat(ratingStr);
+ if (minRating === 5) {
+ return ratingNum > 4.5;
+ }
return ratingNum >= minRating;
});
}
@@ -343,6 +352,7 @@ export default function SearchView() {
selectionMode="single"
label="Minimum Rating"
placeholder="Minimum rating"
+ disallowEmptySelection
selectedKeys={[minRating.toString()]}
onSelectionChange={keys => setMinRating(parseInt(Array.from(keys)[0] as string, 10))}
renderValue={(items: SelectedItems) => {
From 24eacc019677412f05f884d26067d78cded65878 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Thu, 4 Sep 2025 18:19:22 +0200
Subject: [PATCH 19/32] Update rating algorithm
---
app/src/main/frontend/util/utils.ts | 34 +++++++++++++++++-----
app/src/main/frontend/views/SearchView.tsx | 14 ++++-----
2 files changed, 33 insertions(+), 15 deletions(-)
diff --git a/app/src/main/frontend/util/utils.ts b/app/src/main/frontend/util/utils.ts
index 6b5dd9f..8c4cffb 100644
--- a/app/src/main/frontend/util/utils.ts
+++ b/app/src/main/frontend/util/utils.ts
@@ -241,6 +241,30 @@ function convertRange(value: number, originalRange: number[], targetRange: numbe
return (value - originalRange[0]) * (targetRange[1] - targetRange[0]) / (originalRange[1] - originalRange[0]) + targetRange[0];
}
+/**
+ * Calculate a compound rating for a GameDto based on its criticRating and userRating.
+ * If both ratings are present, a weighted average is calculated (40% critic, 60% user).
+ * If only one rating is present, that rating is returned.
+ * If neither rating is present, 0 is returned.
+ * @param game The GameDto object containing the ratings.
+ * @returns The compound rating as a number between 0 and 100.
+ */
+export function compoundRating(game: GameDto): number {
+ const weights = {
+ critic: 0.4,
+ user: 0.6
+ };
+
+ const criticRating = game.criticRating ?? 0;
+ const userRating = game.userRating ?? 0;
+
+ if (criticRating === 0 && userRating === 0) return 0;
+ if (criticRating === 0) return userRating;
+ if (userRating === 0) return criticRating;
+
+ return Math.round((criticRating * weights.critic + userRating * weights.user) * 10) / 10;
+}
+
/**
* Convert a GameDto's ratings to a star rating out of 5.
* If both criticRating and userRating are present, their average is taken.
@@ -249,15 +273,11 @@ function convertRange(value: number, originalRange: number[], targetRange: numbe
* @returns A string representing the star rating out of 5, or "N/A" if no ratings are available.
*/
export function gameRatingInStars(game: GameDto) {
- if (!game.criticRating && !game.userRating) return "N/A";
-
const originalRange = [0, 100];
const starRange = [1, 5];
- const ratings = [];
- if (game.criticRating) ratings.push(game.criticRating);
- if (game.userRating) ratings.push(game.userRating);
- const avgRating = ratings.reduce((a, b) => a + b, 0) / ratings.length;
+ const rating = compoundRating(game);
+ if (rating === 0) return "N/A";
- return convertRange(avgRating, originalRange, starRange).toFixed(1);
+ return convertRange(rating, originalRange, starRange).toFixed(1);
}
\ No newline at end of file
diff --git a/app/src/main/frontend/views/SearchView.tsx b/app/src/main/frontend/views/SearchView.tsx
index 0125b0d..e6ed454 100644
--- a/app/src/main/frontend/views/SearchView.tsx
+++ b/app/src/main/frontend/views/SearchView.tsx
@@ -9,7 +9,7 @@ import {Fzf} from "fzf";
import GameDto from "Frontend/generated/org/gameyfin/app/games/dto/GameDto";
import LibraryDto from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryDto";
import CoverGrid from "Frontend/components/general/covers/CoverGrid";
-import {gameRatingInStars, toTitleCase} from "Frontend/util/utils";
+import {compoundRating, toTitleCase} from "Frontend/util/utils";
export default function SearchView() {
const games = useSnapshot(gameState).sortedAlphabetically as GameDto[];
@@ -138,7 +138,7 @@ export default function SearchView() {
const [sortKey, sortDirection] = sortBy.split("_");
- return [...games].sort((a, b) => {
+ return games.slice().sort((a, b) => {
let cmp: number;
switch (sortKey) {
@@ -149,7 +149,7 @@ export default function SearchView() {
cmp = (a.release || "").localeCompare(b.release || "");
break;
case "rating":
- cmp = (a.criticRating ?? 0) - (b.criticRating ?? 0);
+ cmp = compoundRating(a) - compoundRating(b);
break;
case "added":
cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
@@ -235,13 +235,11 @@ export default function SearchView() {
// Apply minimum rating filter
if (minRating > 1) {
filtered = filtered.filter(game => {
- const ratingStr = gameRatingInStars(game);
- if (ratingStr === "N/A") return false;
- const ratingNum = parseFloat(ratingStr);
+ const rating = compoundRating(game);
if (minRating === 5) {
- return ratingNum > 4.5;
+ return rating > 4.5;
}
- return ratingNum >= minRating;
+ return rating >= minRating;
});
}
return filtered;
From 6c7bf4399eb35da3d473eff9abc6ea05b6252eea Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Thu, 4 Sep 2025 18:58:07 +0200
Subject: [PATCH 20/32] Fix rating calculation
---
app/src/main/frontend/util/utils.ts | 21 ++++++++++++---------
app/src/main/frontend/views/GameView.tsx | 4 ++--
app/src/main/frontend/views/SearchView.tsx | 6 +++---
3 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/app/src/main/frontend/util/utils.ts b/app/src/main/frontend/util/utils.ts
index 8c4cffb..23743fe 100644
--- a/app/src/main/frontend/util/utils.ts
+++ b/app/src/main/frontend/util/utils.ts
@@ -238,6 +238,7 @@ export function metadataCompleteness(game: GameDto) {
* @returns The scaled number
*/
function convertRange(value: number, originalRange: number[], targetRange: number[]): number {
+ if (originalRange[0] === targetRange[0] && originalRange[1] === targetRange[1]) return value;
return (value - originalRange[0]) * (targetRange[1] - targetRange[0]) / (originalRange[1] - originalRange[0]) + targetRange[0];
}
@@ -247,22 +248,25 @@ function convertRange(value: number, originalRange: number[], targetRange: numbe
* If only one rating is present, that rating is returned.
* If neither rating is present, 0 is returned.
* @param game The GameDto object containing the ratings.
- * @returns The compound rating as a number between 0 and 100.
+ * @param scale The scale to convert the rating to (default is [0, 100]).
+ * @returns The compound rating.
*/
-export function compoundRating(game: GameDto): number {
+export function compoundRating(game: GameDto, scale = [0, 100]): number {
const weights = {
critic: 0.4,
user: 0.6
};
+ const originalRange = [0, 100];
const criticRating = game.criticRating ?? 0;
const userRating = game.userRating ?? 0;
if (criticRating === 0 && userRating === 0) return 0;
- if (criticRating === 0) return userRating;
- if (userRating === 0) return criticRating;
+ if (criticRating === 0) return convertRange(userRating, originalRange, scale);
+ if (userRating === 0) return convertRange(criticRating, originalRange, scale);
- return Math.round((criticRating * weights.critic + userRating * weights.user) * 10) / 10;
+ const avgRating = Math.round((criticRating * weights.critic + userRating * weights.user) * 10) / 10;
+ return convertRange(avgRating, originalRange, scale);
}
/**
@@ -272,12 +276,11 @@ export function compoundRating(game: GameDto): number {
* @param game The GameDto object containing the ratings.
* @returns A string representing the star rating out of 5, or "N/A" if no ratings are available.
*/
-export function gameRatingInStars(game: GameDto) {
- const originalRange = [0, 100];
+export function starRatingAsString(game: GameDto) {
const starRange = [1, 5];
- const rating = compoundRating(game);
+ const rating = compoundRating(game, starRange);
if (rating === 0) return "N/A";
- return convertRange(rating, originalRange, starRange).toFixed(1);
+ return rating.toFixed(1);
}
\ No newline at end of file
diff --git a/app/src/main/frontend/views/GameView.tsx b/app/src/main/frontend/views/GameView.tsx
index 92cc8f4..ae5dea8 100644
--- a/app/src/main/frontend/views/GameView.tsx
+++ b/app/src/main/frontend/views/GameView.tsx
@@ -5,7 +5,7 @@ import {GameCover} from "Frontend/components/general/covers/GameCover";
import ComboButton, {ComboButtonOption} from "Frontend/components/general/input/ComboButton";
import ImageCarousel from "Frontend/components/general/covers/ImageCarousel";
import {Accordion, AccordionItem, addToast, Button, Chip, Link, Tooltip, useDisclosure} from "@heroui/react";
-import {gameRatingInStars, humanFileSize, isAdmin, toTitleCase} from "Frontend/util/utils";
+import {humanFileSize, isAdmin, starRatingAsString, toTitleCase} from "Frontend/util/utils";
import {DownloadEndpoint} from "Frontend/endpoints/endpoints";
import {gameState} from "Frontend/state/GameState";
import {useSnapshot} from "valtio/react";
@@ -108,7 +108,7 @@ export default function GameView() {
- {gameRatingInStars(game)}
+ {starRatingAsString(game)}
diff --git a/app/src/main/frontend/views/SearchView.tsx b/app/src/main/frontend/views/SearchView.tsx
index e6ed454..28084cb 100644
--- a/app/src/main/frontend/views/SearchView.tsx
+++ b/app/src/main/frontend/views/SearchView.tsx
@@ -235,11 +235,11 @@ export default function SearchView() {
// Apply minimum rating filter
if (minRating > 1) {
filtered = filtered.filter(game => {
- const rating = compoundRating(game);
+ const starRating = compoundRating(game, [1, 5]);
if (minRating === 5) {
- return rating > 4.5;
+ return starRating > 4.5;
}
- return rating >= minRating;
+ return starRating >= minRating;
});
}
return filtered;
From 479259adc71cec34c5282b2f580fa15d5381fb05 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 9 Sep 2025 19:10:26 +0200
Subject: [PATCH 21/32] Fix image handling and cleanup
---
.../app/core/config/JpaConfiguration.kt | 18 +++
.../org/gameyfin/app/core/events/Events.kt | 10 +-
.../interceptors/EntityUpdateInterceptor.kt | 107 ++++++++++++++++++
.../org/gameyfin/app/games/GameService.kt | 47 +++++---
.../org/gameyfin/app/games/entities/Game.kt | 20 ++--
.../app/games/entities/GameEntityListener.kt | 3 +
.../org/gameyfin/app/games/entities/Image.kt | 23 +---
.../app/games/entities/ImageEntityListener.kt | 27 -----
.../app/games/repositories/GameRepository.kt | 3 +
.../app/games/repositories/ImageRepository.kt | 3 +-
.../app/libraries/LibraryScanService.kt | 33 +++---
.../org/gameyfin/app/media/ImageEndpoint.kt | 2 +-
.../org/gameyfin/app/media/ImageService.kt | 106 +++++++++++++++--
.../gameyfin/app/messages/MessageService.kt | 4 +-
.../org/gameyfin/app/users/UserService.kt | 8 +-
.../app/users/entities/UserEntityListener.kt | 11 ++
.../app/users/persistence/UserRepository.kt | 5 +
17 files changed, 317 insertions(+), 113 deletions(-)
create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt
create mode 100644 app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt
delete mode 100644 app/src/main/kotlin/org/gameyfin/app/games/entities/ImageEntityListener.kt
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt b/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt
new file mode 100644
index 0000000..a6f86e4
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/core/config/JpaConfiguration.kt
@@ -0,0 +1,18 @@
+package org.gameyfin.app.core.config
+
+import org.gameyfin.app.core.interceptors.EntityUpdateInterceptor
+import org.hibernate.cfg.AvailableSettings
+import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+class JpaConfiguration {
+
+ @Bean
+ fun hibernatePropertiesCustomizer(entityUpdateInterceptor: EntityUpdateInterceptor): HibernatePropertiesCustomizer {
+ return HibernatePropertiesCustomizer { hibernateProperties ->
+ hibernateProperties[AvailableSettings.INTERCEPTOR] = entityUpdateInterceptor
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt b/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt
index f201ef6..d30eaa7 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/events/Events.kt
@@ -22,8 +22,12 @@ class RegistrationAttemptWithExistingEmailEvent(source: Any, val existingUser: U
class PasswordResetRequestEvent(source: Any, val token: Token
, val baseUrl: String) :
ApplicationEvent(source)
-class AccountDeletedEvent(source: Any, val user: User, val baseUrl: String) : ApplicationEvent(source)
-
class LibraryScanScheduleUpdatedEvent(source: Any) : ApplicationEvent(source)
-class GameCreatedEvent(source: Any, val game: Game) : ApplicationEvent(source)
\ No newline at end of file
+class UserDeletedEvent(source: Any, val user: User, val baseUrl: String) : ApplicationEvent(source)
+class UserUpdatedEvent(source: Any, val previousState: User, val currentState: User) : ApplicationEvent(source)
+
+class GameCreatedEvent(source: Any, val game: Game) : ApplicationEvent(source)
+class GameUpdatedEvent(source: Any, val previousState: Game, val currentState: Game) : ApplicationEvent(source)
+class GameDeletedEvent(source: Any, val game: Game) : ApplicationEvent(source)
+
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt b/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt
new file mode 100644
index 0000000..3497d80
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt
@@ -0,0 +1,107 @@
+package org.gameyfin.app.core.interceptors
+
+import org.gameyfin.app.core.events.GameUpdatedEvent
+import org.gameyfin.app.core.events.UserUpdatedEvent
+import org.gameyfin.app.games.entities.Game
+import org.gameyfin.app.games.entities.Image
+import org.gameyfin.app.users.entities.User
+import org.gameyfin.app.util.EventPublisherHolder
+import org.hibernate.Interceptor
+import org.hibernate.type.Type
+import org.springframework.stereotype.Component
+
+@Component
+class EntityUpdateInterceptor() : Interceptor {
+
+ override fun onFlushDirty(
+ entity: Any?,
+ id: Any?,
+ currentState: Array?,
+ previousState: Array?,
+ propertyNames: Array?,
+ types: Array?
+ ): Boolean {
+ if (entity == null || currentState == null || previousState == null || propertyNames == null) {
+ return false
+ }
+
+ when (entity) {
+ is Game -> {
+ val previousGame = reconstructGame(entity, previousState, propertyNames)
+ val currentGame = reconstructGame(entity, currentState, propertyNames)
+ EventPublisherHolder.publish(GameUpdatedEvent(this, previousGame, currentGame))
+ }
+
+ is User -> {
+ val previousUser = reconstructUser(entity, previousState, propertyNames)
+ val currentUser = reconstructUser(entity, currentState, propertyNames)
+ EventPublisherHolder.publish(UserUpdatedEvent(this, previousUser, currentUser))
+ }
+ }
+
+ return false
+ }
+
+ private fun reconstructGame(originalGame: Game, state: Array, propertyNames: Array): Game {
+ val reconstructed = Game(
+ library = originalGame.library,
+ metadata = originalGame.metadata
+ )
+
+ for (i in propertyNames.indices) {
+ when (propertyNames[i]) {
+ "id" -> reconstructed.id = state[i] as? Long
+ "createdAt" -> reconstructed.createdAt = state[i] as? java.time.Instant
+ "updatedAt" -> reconstructed.updatedAt = state[i] as? java.time.Instant
+ "title" -> reconstructed.title = state[i] as? String
+ "coverImage" -> reconstructed.coverImage = state[i] as? Image
+ "headerImage" -> reconstructed.headerImage = state[i] as? Image
+ "comment" -> reconstructed.comment = state[i] as? String
+ "summary" -> reconstructed.summary = state[i] as? String
+ "release" -> reconstructed.release = state[i] as? java.time.Instant
+ "userRating" -> reconstructed.userRating = state[i] as? Int
+ "criticRating" -> reconstructed.criticRating = state[i] as? Int
+ "images" -> {
+ @Suppress("UNCHECKED_CAST")
+ (state[i] as? MutableList)?.let { reconstructed.images = it }
+ }
+ }
+ }
+
+ return reconstructed
+ }
+
+ private fun reconstructUser(originalUser: User, state: Array, propertyNames: Array): User {
+ val reconstructed = User(
+ username = originalUser.username,
+ email = originalUser.email
+ )
+
+ for (i in propertyNames.indices) {
+ when (propertyNames[i]) {
+ "id" -> reconstructed.id = state[i] as? Long
+ "password" -> reconstructed.password = state[i] as? String
+ "oidcProviderId" -> reconstructed.oidcProviderId = state[i] as? String
+ "emailConfirmed" -> reconstructed.emailConfirmed = state[i] as? Boolean ?: false
+ "enabled" -> reconstructed.enabled = state[i] as? Boolean ?: false
+ "avatar" -> reconstructed.avatar = state[i] as? Image
+ "roles" -> {
+ @Suppress("UNCHECKED_CAST")
+ (state[i] as? List)?.let { reconstructed.roles = it }
+ }
+ }
+ }
+
+ return reconstructed
+ }
+
+ private fun extractGameImages(game: Game): List {
+ val images = mutableListOf()
+
+ game.coverImage?.let { images.add(it) }
+ game.headerImage?.let { images.add(it) }
+ images.addAll(game.images)
+
+ return images
+ }
+}
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
index b6d3d97..b5b830c 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
@@ -33,7 +33,6 @@ import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import reactor.core.publisher.Flux
import reactor.core.publisher.Sinks
-import java.net.URI
import java.nio.file.Path
import java.time.ZoneId
import java.time.ZoneOffset
@@ -107,8 +106,8 @@ class GameService(
@Transactional
fun create(game: Game): Game? {
- game.publishers = game.publishers.map { companyService.createOrGet(it) }
- game.developers = game.developers.map { companyService.createOrGet(it) }
+ game.publishers = game.publishers.map { companyService.createOrGet(it) }.toMutableList()
+ game.developers = game.developers.map { companyService.createOrGet(it) }.toMutableList()
try {
game.coverImage?.let {
@@ -138,9 +137,11 @@ class GameService(
val gamesToBePersisted = games.filter { it.id == null }
gamesToBePersisted.forEach { game ->
- game.publishers = game.publishers.map { companyService.createOrGet(it) }
- game.developers = game.developers.map { companyService.createOrGet(it) }
- game
+ game.publishers = game.publishers.map { companyService.createOrGet(it) }.toMutableList()
+ game.developers = game.developers.map { companyService.createOrGet(it) }.toMutableList()
+ game.coverImage?.let { game.coverImage = imageService.createOrGet(it) }
+ game.headerImage?.let { game.headerImage = imageService.createOrGet(it) }
+ game.images = game.images.map { imageService.createOrGet(it) }.toMutableList()
}
return gameRepository.saveAll(gamesToBePersisted)
@@ -166,14 +167,18 @@ class GameService(
existingGame.metadata.fields["release"]?.source = GameFieldUserSource(user = user)
}
gameUpdateDto.coverUrl?.let {
- val newCoverImage = Image(originalUrl = URI.create(it).toURL(), type = ImageType.COVER)
+ val newCoverImage = imageService.createOrGet(
+ Image(originalUrl = it, type = ImageType.COVER)
+ )
imageService.downloadIfNew(newCoverImage)
existingGame.coverImage = newCoverImage
existingGame.metadata.fields["coverImage"]?.source = GameFieldUserSource(user = user)
}
gameUpdateDto.headerUrl?.let {
- val newHeaderImage = Image(originalUrl = URI.create(it).toURL(), type = ImageType.HEADER)
+ val newHeaderImage = imageService.createOrGet(
+ Image(originalUrl = it, type = ImageType.HEADER)
+ )
imageService.downloadIfNew(newHeaderImage)
existingGame.headerImage = newHeaderImage
@@ -190,11 +195,13 @@ class GameService(
gameUpdateDto.developers?.let {
existingGame.developers =
it.map { name -> companyService.createOrGet(Company(name = name, type = CompanyType.DEVELOPER)) }
+ .toMutableList()
existingGame.metadata.fields["developers"]?.source = GameFieldUserSource(user = user)
}
gameUpdateDto.publishers?.let {
existingGame.publishers =
it.map { name -> companyService.createOrGet(Company(name = name, type = CompanyType.PUBLISHER)) }
+ .toMutableList()
existingGame.metadata.fields["publishers"]?.source = GameFieldUserSource(user = user)
}
gameUpdateDto.genres?.let {
@@ -378,7 +385,7 @@ class GameService(
"publishers",
game.publishers,
updatedGame.publishers,
- { game.publishers = it ?: emptyList() },
+ { game.publishers = it ?: mutableListOf() },
updatedGame.metadata.fields["publishers"]
)
@@ -387,7 +394,7 @@ class GameService(
"developers",
game.developers,
updatedGame.developers,
- { game.developers = it ?: emptyList() },
+ { game.developers = it ?: mutableListOf() },
updatedGame.metadata.fields["developers"]
)
@@ -441,7 +448,7 @@ class GameService(
"images",
game.images,
updatedGame.images,
- { game.images = it ?: emptyList() },
+ { game.images = it ?: mutableListOf() },
updatedGame.metadata.fields["images"]
)
@@ -758,14 +765,18 @@ class GameService(
}
metadata.coverUrls?.firstOrNull()?.let { coverUrl ->
if (!metadataMap.containsKey("coverImage")) {
- mergedGame.coverImage = Image(originalUrl = coverUrl.toURL(), type = ImageType.COVER)
+ mergedGame.coverImage = imageService.createOrGet(
+ Image(originalUrl = coverUrl.toString(), type = ImageType.COVER)
+ )
metadataMap["coverImage"] =
GameFieldMetadata(source = GameFieldPluginSource(plugin = sourcePlugin))
}
}
metadata.headerUrls?.firstOrNull()?.let { headerUrl ->
if (!metadataMap.containsKey("headerImage")) {
- mergedGame.headerImage = Image(originalUrl = headerUrl.toURL(), type = ImageType.HEADER)
+ mergedGame.headerImage = imageService.createOrGet(
+ Image(originalUrl = headerUrl.toString(), type = ImageType.HEADER)
+ )
metadataMap["headerImage"] =
GameFieldMetadata(source = GameFieldPluginSource(plugin = sourcePlugin))
}
@@ -794,7 +805,7 @@ class GameService(
metadata.publishedBy?.takeIf { it.isNotEmpty() }?.let { publishedBy ->
if (!metadataMap.containsKey("publishers")) {
mergedGame.publishers =
- publishedBy.map { Company(name = it, type = CompanyType.PUBLISHER) }
+ publishedBy.map { Company(name = it, type = CompanyType.PUBLISHER) }.toMutableList()
metadataMap["publishers"] =
GameFieldMetadata(source = GameFieldPluginSource(plugin = sourcePlugin))
}
@@ -802,7 +813,7 @@ class GameService(
metadata.developedBy?.takeIf { it.isNotEmpty() }?.let { developedBy ->
if (!metadataMap.containsKey("developers")) {
mergedGame.developers =
- developedBy.map { Company(name = it, type = CompanyType.DEVELOPER) }
+ developedBy.map { Company(name = it, type = CompanyType.DEVELOPER) }.toMutableList()
metadataMap["developers"] =
GameFieldMetadata(source = GameFieldPluginSource(plugin = sourcePlugin))
}
@@ -843,7 +854,11 @@ class GameService(
metadata.screenshotUrls?.takeIf { it.isNotEmpty() }?.let { screenshotUrls ->
if (!metadataMap.containsKey("images")) {
mergedGame.images = runBlocking {
- screenshotUrls.map { Image(originalUrl = it.toURL(), type = ImageType.SCREENSHOT) }
+ screenshotUrls.map {
+ imageService.createOrGet(
+ Image(originalUrl = it.toString(), type = ImageType.SCREENSHOT)
+ )
+ }.toMutableList()
}
metadataMap["images"] = GameFieldMetadata(source = GameFieldPluginSource(plugin = sourcePlugin))
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt
index 2cdbcdd..04c52ef 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/Game.kt
@@ -1,6 +1,7 @@
package org.gameyfin.app.games.entities
import jakarta.persistence.*
+import jakarta.persistence.CascadeType.*
import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.pluginapi.gamemetadata.GameFeature
import org.gameyfin.pluginapi.gamemetadata.Genre
@@ -28,15 +29,14 @@ class Game(
var updatedAt: Instant? = null,
@ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "library_id")
val library: Library,
var title: String? = null,
- @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
+ @ManyToOne(cascade = [PERSIST, MERGE, REFRESH], fetch = FetchType.EAGER)
var coverImage: Image? = null,
- @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
+ @ManyToOne(cascade = [PERSIST, MERGE, REFRESH], fetch = FetchType.EAGER)
var headerImage: Image? = null,
@Lob
@@ -53,11 +53,11 @@ class Game(
var criticRating: Int? = null,
- @ManyToMany(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
- var publishers: List = emptyList(),
+ @ManyToMany(cascade = [PERSIST, MERGE, REFRESH], fetch = FetchType.EAGER)
+ var publishers: MutableList = mutableListOf(),
- @ManyToMany(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
- var developers: List = emptyList(),
+ @ManyToMany(cascade = [PERSIST, MERGE, REFRESH], fetch = FetchType.EAGER)
+ var developers: MutableList = mutableListOf(),
@ElementCollection(targetClass = Genre::class)
var genres: List = emptyList(),
@@ -74,16 +74,14 @@ class Game(
@ElementCollection(targetClass = PlayerPerspective::class)
var perspectives: List = emptyList(),
- @OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
- var images: List = emptyList(),
+ @ManyToMany(cascade = [PERSIST, MERGE, REFRESH], fetch = FetchType.EAGER)
+ var images: MutableList = mutableListOf(),
@ElementCollection
var videoUrls: List = emptyList(),
@Embedded
var metadata: GameMetadata
-
-
) {
constructor(path: Path, library: Library) : this(library = library, metadata = GameMetadata(path = path.toString()))
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
index b400493..93fbf10 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/GameEntityListener.kt
@@ -4,6 +4,7 @@ import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate
import org.gameyfin.app.core.events.GameCreatedEvent
+import org.gameyfin.app.core.events.GameDeletedEvent
import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.dto.GameAdminEvent
import org.gameyfin.app.games.dto.GameUserEvent
@@ -24,11 +25,13 @@ class GameEntityListener {
fun updated(game: Game) {
GameService.emitUser(GameUserEvent.Updated(game.toUserDto()))
GameService.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
+ // GameUpdateEvent triggered via {@link org.gameyfin.app.core.interceptors.EntityUpdateInterceptor#onFlushDirty}
}
@PostRemove
fun deleted(game: Game) {
GameService.emitUser(GameUserEvent.Deleted(game.id!!))
GameService.emitAdmin(GameAdminEvent.Deleted(game.id!!))
+ EventPublisherHolder.publish(GameDeletedEvent(this, game))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/Image.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/Image.kt
index 3bc44cf..cb407d9 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/Image.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/Image.kt
@@ -1,19 +1,20 @@
package org.gameyfin.app.games.entities
-import jakarta.persistence.*
+import jakarta.persistence.Entity
+import jakarta.persistence.GeneratedValue
+import jakarta.persistence.GenerationType
+import jakarta.persistence.Id
import org.springframework.content.commons.annotations.ContentId
import org.springframework.content.commons.annotations.ContentLength
import org.springframework.content.commons.annotations.MimeType
-import java.net.URL
@Entity
-@EntityListeners(ImageEntityListener::class)
class Image(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null,
- val originalUrl: URL? = null,
+ val originalUrl: String? = null,
val type: ImageType,
@@ -25,19 +26,7 @@ class Image(
@MimeType
var mimeType: String? = null
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Image) return false
- return originalUrl.toString() == other.originalUrl.toString()
- }
-
- override fun hashCode(): Int {
- var result = id?.hashCode() ?: 0
- result = 31 * result + originalUrl?.toString().hashCode()
- return result
- }
-}
+)
enum class ImageType {
COVER,
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/ImageEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/ImageEntityListener.kt
deleted file mode 100644
index 0486bfc..0000000
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/ImageEntityListener.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.gameyfin.app.games.entities
-
-import jakarta.persistence.PostRemove
-import org.gameyfin.app.media.ImageService
-import org.springframework.context.ApplicationContext
-import org.springframework.context.ApplicationContextAware
-import org.springframework.stereotype.Component
-
-@Component
-class ImageEntityListener : ApplicationContextAware {
- companion object {
- private lateinit var applicationContext: ApplicationContext
- }
-
- override fun setApplicationContext(context: ApplicationContext) {
- applicationContext = context
- }
-
- private fun getImageService(): ImageService {
- return applicationContext.getBean(ImageService::class.java)
- }
-
- @PostRemove
- fun deleted(image: Image) {
- getImageService().deleteFile(image)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt b/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt
index 0713ba3..c2a3f8c 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/repositories/GameRepository.kt
@@ -12,4 +12,7 @@ interface GameRepository : JpaRepository {
@Param("title") title: String,
@Param("release") release: Instant?
): List
+
+ @Query("SELECT CASE WHEN COUNT(g) > 0 THEN true ELSE false END FROM Game g WHERE g.coverImage.id = :imageId OR g.headerImage.id = :imageId OR :imageId IN (SELECT i.id FROM g.images i)")
+ fun existsByImage(@Param("imageId") imageId: Long): Boolean
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageRepository.kt b/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageRepository.kt
index 942c2ff..ea211bc 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/repositories/ImageRepository.kt
@@ -2,8 +2,7 @@ package org.gameyfin.app.games.repositories
import org.gameyfin.app.games.entities.Image
import org.springframework.data.jpa.repository.JpaRepository
-import java.net.URL
interface ImageRepository : JpaRepository {
- fun findByOriginalUrl(originalUrl: URL): Image?
+ fun findByOriginalUrl(originalUrl: String): Image?
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
index 62a53c7..0bb6e12 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
@@ -13,7 +13,6 @@ import org.gameyfin.app.media.ImageService
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Sinks
-import java.net.URL
import java.nio.file.Path
import java.time.Instant
import java.util.concurrent.Callable
@@ -344,19 +343,13 @@ class LibraryScanService(
// Deduplicate by originalUrl
val uniqueImages = allImages
.filter { it.originalUrl != null }
- .distinctBy { it.originalUrl }
-
- // Map to track which Image entity was used for download per originalUrl
- val downloadedImageMap = ConcurrentHashMap()
+ .distinctBy { it.originalUrl.toString() }
// Download each unique image in parallel
val imageDownloadTasks = uniqueImages.map { image ->
Callable {
try {
imageService.downloadIfNew(image)
- image.originalUrl?.let { url ->
- downloadedImageMap[url] = image
- }
} catch (e: Exception) {
log.error { "Error downloading image '${image.originalUrl}': ${e.message}" }
log.debug(e) {}
@@ -368,18 +361,20 @@ class LibraryScanService(
}
executor.invokeAll(imageDownloadTasks)
- // After downloads, associate the contentId with all other Image entities in the batch with the same originalUrl
- for ((url, downloadedImage) in downloadedImageMap) {
- val contentId = downloadedImage.contentId
- if (contentId != null) {
- allImages.filter { it.originalUrl.toString() == url.toString() && it !== downloadedImage }
- .forEach { image ->
- imageService.downloadIfNew(image)
- progress.currentStep.current = completedImageDownload.incrementAndGet()
- emit(progress)
- }
+ // For remaining duplicate images, just copy the content metadata from the downloaded unique image
+ val uniqueImagesByUrl = uniqueImages.associateBy { it.originalUrl.toString() }
+
+ allImages.filter { it.originalUrl != null && it !in uniqueImages }
+ .forEach { duplicateImage ->
+ val downloadedImage = uniqueImagesByUrl[duplicateImage.originalUrl.toString()]
+ if (downloadedImage != null && downloadedImage.contentId != null) {
+ duplicateImage.contentId = downloadedImage.contentId
+ duplicateImage.contentLength = downloadedImage.contentLength
+ duplicateImage.mimeType = downloadedImage.mimeType
+ }
+ progress.currentStep.current = completedImageDownload.incrementAndGet()
+ emit(progress)
}
- }
return DownloadImagesResult(gamesWithImages = games)
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
index df451b2..850dbd8 100644
--- a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
@@ -63,7 +63,7 @@ class ImageEndpoint(
val auth = getCurrentAuth() ?: throw IllegalStateException("No authentication found")
val image: Image = if (!userService.hasAvatar(auth.name)) {
- imageService.createFile(ImageType.AVATAR, file.inputStream, file.contentType!!)
+ imageService.createFromInputStream(ImageType.AVATAR, file.inputStream, file.contentType!!)
} else {
val existingAvatar = userService.getAvatar(auth.name)!!
imageService.updateFileContent(existingAvatar, file.inputStream, file.contentType!!)
diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt
index 2ff041d..06e1c6e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt
@@ -1,41 +1,122 @@
package org.gameyfin.app.media
+import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.tika.Tika
import org.apache.tika.io.TikaInputStream
+import org.gameyfin.app.core.events.GameDeletedEvent
+import org.gameyfin.app.core.events.GameUpdatedEvent
+import org.gameyfin.app.core.events.UserDeletedEvent
+import org.gameyfin.app.core.events.UserUpdatedEvent
import org.gameyfin.app.games.entities.Image
import org.gameyfin.app.games.entities.ImageType
+import org.gameyfin.app.games.repositories.GameRepository
import org.gameyfin.app.games.repositories.ImageContentStore
import org.gameyfin.app.games.repositories.ImageRepository
+import org.gameyfin.app.users.persistence.UserRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
+import org.springframework.transaction.event.TransactionPhase
+import org.springframework.transaction.event.TransactionalEventListener
import java.io.InputStream
+import java.net.URI
@Service
class ImageService(
private val imageRepository: ImageRepository,
- private val imageContentStore: ImageContentStore
+ private val imageContentStore: ImageContentStore,
+ private val gameRepository: GameRepository,
+ private val userRepository: UserRepository
) {
companion object {
- private val tika = Tika();
+ private val tika = Tika()
+ private val log = KotlinLogging.logger {}
+ }
+
+ @TransactionalEventListener(
+ classes = [GameDeletedEvent::class],
+ phase = TransactionPhase.AFTER_COMPLETION
+ )
+ fun onGameDeleted(event: GameDeletedEvent) {
+ val imagesToDelete = listOfNotNull(event.game.coverImage, event.game.headerImage)
+ .toMutableList()
+ .apply { addAll(event.game.images) }
+
+ imagesToDelete.forEach { deleteImageIfUnused(it) }
+ }
+
+ @TransactionalEventListener(
+ classes = [GameUpdatedEvent::class],
+ phase = TransactionPhase.AFTER_COMPLETION
+ )
+ fun onGameUpdated(event: GameUpdatedEvent) {
+ val imagesBeforeUpdate = listOfNotNull(event.previousState.coverImage, event.previousState.headerImage)
+ .toMutableList()
+ .apply { addAll(event.previousState.images) }
+ .toSet()
+
+ val imagesStillInUse = listOfNotNull(event.currentState.coverImage, event.currentState.headerImage)
+ .toMutableList()
+ .apply { addAll(event.currentState.images) }
+ .toSet()
+
+ imagesBeforeUpdate.minus(imagesStillInUse).forEach { deleteImageIfUnused(it) }
+ }
+
+ @TransactionalEventListener(
+ classes = [UserDeletedEvent::class],
+ phase = TransactionPhase.AFTER_COMPLETION
+ )
+ fun onAccountDeleted(event: UserDeletedEvent) {
+ event.user.avatar?.let { deleteImageIfUnused(it) }
+ }
+
+ @TransactionalEventListener(
+ classes = [UserUpdatedEvent::class],
+ phase = TransactionPhase.AFTER_COMPLETION
+ )
+ fun onUserUpdated(event: UserUpdatedEvent) {
+ event.previousState.avatar?.let { previousAvatar ->
+ if (previousAvatar != event.currentState.avatar) {
+ deleteImageIfUnused(previousAvatar)
+ }
+ }
+ }
+
+ fun createOrGet(image: Image): Image {
+ if (image.originalUrl != null) {
+ imageRepository.findByOriginalUrl(image.originalUrl)?.let { return it }
+ }
+
+ return imageRepository.save(image)
}
fun downloadIfNew(image: Image) {
if (image.originalUrl == null) throw IllegalArgumentException("Image must have an original URL")
+ // Always try to get existing image first to avoid detached entity issues
val existingImage = imageRepository.findByOriginalUrl(image.originalUrl)
if (existingImage != null && existingImage.contentId != null) {
+ // If we have an existing image with content, associate it with the current image
imageContentStore.associate(image, existingImage.contentId)
+ // Update the current image's content metadata
+ image.contentId = existingImage.contentId
+ image.contentLength = existingImage.contentLength
+ image.mimeType = existingImage.mimeType
return
}
- TikaInputStream.get { image.originalUrl.openStream() }.use { input ->
+ // If no existing image or existing image has no content, download it
+ TikaInputStream.get { URI.create(image.originalUrl).toURL().openStream() }.use { input ->
image.mimeType = tika.detect(input)
imageContentStore.setContent(image, input)
}
+
+ // Save the image to ensure it's persisted
+ imageRepository.save(image)
}
- fun createFile(type: ImageType, content: InputStream, mimeType: String): Image {
+ fun createFromInputStream(type: ImageType, content: InputStream, mimeType: String): Image {
val image = Image(type = type, mimeType = mimeType)
imageRepository.save(image)
return imageContentStore.setContent(image, content)
@@ -45,19 +126,20 @@ class ImageService(
return imageRepository.findByIdOrNull(id)
}
- fun getFileContent(id: Long): InputStream? {
- val image = getImage(id) ?: return null
- return getFileContent(image)
- }
-
fun getFileContent(image: Image): InputStream? {
return imageContentStore.getContent(image)
}
- fun deleteFile(image: Image) {
- imageContentStore.unsetContent(image)
- imageRepository.delete(image)
+ fun deleteImageIfUnused(image: Image) {
+ val imageId = image.id ?: return
+
+ val isImageStillInUse = gameRepository.existsByImage(imageId) || userRepository.existsByAvatar(imageId)
+
+ if (!isImageStillInUse) {
+ imageContentStore.unsetContent(image)
+ imageRepository.delete(image)
+ }
}
fun updateFileContent(image: Image, content: InputStream, mimeType: String? = null): Image {
diff --git a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
index 530c879..bf277ea 100644
--- a/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/messages/MessageService.kt
@@ -206,8 +206,8 @@ class MessageService(
}
@Async
- @EventListener(AccountDeletedEvent::class)
- fun onAccountDeletion(event: AccountDeletedEvent) {
+ @EventListener(UserDeletedEvent::class)
+ fun onAccountDeletion(event: UserDeletedEvent) {
if (!enabled) {
log.error { "No message provider available, can't send account deletion message" }
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
index c3d58a4..a062aca 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
@@ -5,7 +5,10 @@ import org.gameyfin.app.config.ConfigProperties
import org.gameyfin.app.config.ConfigService
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.Utils
-import org.gameyfin.app.core.events.*
+import org.gameyfin.app.core.events.AccountStatusChangedEvent
+import org.gameyfin.app.core.events.EmailNeedsConfirmationEvent
+import org.gameyfin.app.core.events.RegistrationAttemptWithExistingEmailEvent
+import org.gameyfin.app.core.events.UserRegistrationWaitingForApprovalEvent
import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.games.entities.Image
import org.gameyfin.app.media.ImageService
@@ -126,7 +129,7 @@ class UserService(
val user = getByUsernameNonNull(username)
if (user.avatar == null) return
- imageService.deleteFile(user.avatar!!)
+ imageService.deleteImageIfUnused(user.avatar!!)
user.avatar = null
userRepository.save(user)
@@ -284,6 +287,5 @@ class UserService(
fun deleteUser(username: String) {
val user = getByUsernameNonNull(username)
userRepository.delete(user)
- eventPublisher.publishEvent(AccountDeletedEvent(this, user, Utils.getBaseUrl()))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt
index 9779485..5fbba23 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/entities/UserEntityListener.kt
@@ -1,9 +1,13 @@
package org.gameyfin.app.users.entities
import jakarta.persistence.EntityManager
+import jakarta.persistence.PostRemove
import jakarta.persistence.PreRemove
+import org.gameyfin.app.core.Utils
+import org.gameyfin.app.core.events.UserDeletedEvent
import org.gameyfin.app.requests.entities.GameRequest
import org.gameyfin.app.util.EntityManagerHolder
+import org.gameyfin.app.util.EventPublisherHolder
class UserEntityListener {
@@ -22,4 +26,11 @@ class UserEntityListener {
entityManager.merge(gr)
}
}
+
+ // UserUpdateEvent triggered via {@link org.gameyfin.app.core.interceptors.EntityUpdateInterceptor#onFlushDirty}
+
+ @PostRemove
+ fun postRemove(user: User) {
+ EventPublisherHolder.publish(UserDeletedEvent(this, user, Utils.getBaseUrl()))
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/persistence/UserRepository.kt b/app/src/main/kotlin/org/gameyfin/app/users/persistence/UserRepository.kt
index 847aa8d..8b9d730 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/persistence/UserRepository.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/persistence/UserRepository.kt
@@ -3,6 +3,8 @@ package org.gameyfin.app.users.persistence
import org.gameyfin.app.core.Role
import org.gameyfin.app.users.entities.User
import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+import org.springframework.data.repository.query.Param
interface UserRepository : JpaRepository {
fun existsByUsername(userName: String): Boolean
@@ -11,4 +13,7 @@ interface UserRepository : JpaRepository {
fun findByEmail(email: String): User?
fun findByOidcProviderId(oidcProviderId: String): User?
fun countUserByRolesContains(role: Role): Int
+
+ @Query("SELECT CASE WHEN COUNT(u) > 0 THEN true ELSE false END FROM User u WHERE u.avatar.id = :imageId")
+ fun existsByAvatar(@Param("imageId") imageId: Long): Boolean
}
\ No newline at end of file
From e4d216473f38b95dc4624bf06b895bbdd1756d83 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Tue, 9 Sep 2025 20:02:50 +0200
Subject: [PATCH 22/32] Code cleanup
---
.../app/core/interceptors/EntityUpdateInterceptor.kt | 10 ----------
.../main/kotlin/org/gameyfin/app/games/GameService.kt | 4 +---
.../kotlin/org/gameyfin/app/media/ImageEndpoint.kt | 4 ++--
.../main/kotlin/org/gameyfin/app/media/ImageService.kt | 2 --
4 files changed, 3 insertions(+), 17 deletions(-)
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt b/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt
index 3497d80..fbb3034 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/interceptors/EntityUpdateInterceptor.kt
@@ -94,14 +94,4 @@ class EntityUpdateInterceptor() : Interceptor {
return reconstructed
}
-
- private fun extractGameImages(game: Game): List {
- val images = mutableListOf()
-
- game.coverImage?.let { images.add(it) }
- game.headerImage?.let { images.add(it) }
- images.addAll(game.images)
-
- return images
- }
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
index b5b830c..027c83e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
@@ -104,8 +104,7 @@ class GameService(
return entities.toDtos()
}
- @Transactional
- fun create(game: Game): Game? {
+ private fun create(game: Game): Game {
game.publishers = game.publishers.map { companyService.createOrGet(it) }.toMutableList()
game.developers = game.developers.map { companyService.createOrGet(it) }.toMutableList()
@@ -124,7 +123,6 @@ class GameService(
} catch (e: Exception) {
log.error { "Error downloading images for game '${game.title}' (${game.id}): ${e.message}" }
log.debug(e) {}
- null
}
game.metadata.fileSize = filesystemService.calculateFileSize(game.metadata.path)
diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
index 850dbd8..8b7fa57 100644
--- a/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageEndpoint.kt
@@ -47,7 +47,7 @@ class ImageEndpoint(
@GetMapping("/plugins/{id}/logo")
fun getPluginLogo(@PathVariable("id") pluginId: String): ResponseEntity? {
val logo = pluginService.getLogo(pluginId)
- return Utils.Companion.inputStreamToResponseEntity(logo)
+ return Utils.inputStreamToResponseEntity(logo)
}
@GetMapping("/avatar")
@@ -85,7 +85,7 @@ class ImageEndpoint(
userService.deleteAvatar(name)
}
- private fun getImageContent(id: Long): ResponseEntity? {
+ private fun getImageContent(id: Long): ResponseEntity {
val image = imageService.getImage(id) ?: return ResponseEntity.notFound().build()
val file = image.let { imageService.getFileContent(it) }
diff --git a/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt b/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt
index 06e1c6e..2a21715 100644
--- a/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/media/ImageService.kt
@@ -1,6 +1,5 @@
package org.gameyfin.app.media
-import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.tika.Tika
import org.apache.tika.io.TikaInputStream
import org.gameyfin.app.core.events.GameDeletedEvent
@@ -29,7 +28,6 @@ class ImageService(
) {
companion object {
private val tika = Tika()
- private val log = KotlinLogging.logger {}
}
@TransactionalEventListener(
From f523187971d9ce8b34c4c252eba402e3d2c3f89c Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Wed, 24 Sep 2025 11:46:04 +0200
Subject: [PATCH 23/32] Update dependencies
---
gradle.properties | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 7a18b27..e20e1b8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,12 +3,12 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true
org.gradle.caching=true
# Plugin versions
-kotlinVersion=2.2.0
-kspVersion=2.2.0-2.0.2
-vaadinVersion=24.8.7
-springBootVersion=3.5.3
+kotlinVersion=2.2.20
+kspVersion=2.2.20-2.0.3
+vaadinVersion=24.9.0
+springBootVersion=3.5.6
springCloudVersion=2025.0.0
springDependencyManagementVersion=1.1.7
# Dependency versions
pf4jVersion=3.13.0
-pf4jKspVersion=2.2.0-1.0.3
\ No newline at end of file
+pf4jKspVersion=2.2.20-1.0.3
\ No newline at end of file
From f43140eb2a9f661636087388d33922624210eace Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Thu, 25 Sep 2025 13:18:36 +0200
Subject: [PATCH 24/32] Update SecurityConfig to use SecurityFilterChain
---
.../app/core/security/SecurityConfig.kt | 75 ++++++++++---------
1 file changed, 41 insertions(+), 34 deletions(-)
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
index 23933d9..9edcba0 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
@@ -1,60 +1,69 @@
package org.gameyfin.app.core.security
-import com.vaadin.flow.spring.security.VaadinWebSecurity
+import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration
+import com.vaadin.flow.spring.security.VaadinSecurityConfigurer
+import com.vaadin.hilla.route.RouteUtil
import org.gameyfin.app.config.ConfigProperties
import org.gameyfin.app.config.ConfigService
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Conditional
import org.springframework.context.annotation.Configuration
+import org.springframework.context.annotation.Import
import org.springframework.core.env.Environment
import org.springframework.http.HttpStatus
import org.springframework.security.config.annotation.web.builders.HttpSecurity
-import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
-import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.core.AuthorizationGrantType
+import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler
@Configuration
@EnableWebSecurity
+@Import(
+ VaadinAwareSecurityContextHolderStrategyConfiguration::class
+)
class SecurityConfig(
private val environment: Environment,
private val config: ConfigService,
private val ssoAuthenticationSuccessHandler: SsoAuthenticationSuccessHandler,
private val sessionRegistry: SessionRegistry
-) : VaadinWebSecurity() {
+) {
companion object {
const val SSO_PROVIDER_KEY = "oidc"
}
- @Throws(Exception::class)
- override fun configure(http: HttpSecurity) {
-
- // Configure your static resources with public access before calling super.configure(HttpSecurity) as it adds final anyRequest matcher
- http.authorizeHttpRequests { auth: AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry ->
- auth.requestMatchers("/login").permitAll()
- .requestMatchers("/setup").permitAll()
- .requestMatchers("/reset-password").permitAll()
- .requestMatchers("/accept-invitation").permitAll()
- .requestMatchers("/public/**").permitAll()
- .requestMatchers("/images/**").permitAll()
- .requestMatchers("/favicon.ico").permitAll()
- .requestMatchers("/favicon.svg").permitAll()
-
- // Dynamic public access for certain endpoints
- auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config))
- .requestMatchers("/game/**").access(DynamicPublicAccessAuthorizationManager(config))
- .requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config))
- .requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config))
- .requestMatchers("/requests/**").access(DynamicPublicAccessAuthorizationManager(config))
- .requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config))
+ @Bean
+ fun filterChain(http: HttpSecurity, routeUtil: RouteUtil): SecurityFilterChain {
+ http.authorizeHttpRequests { auth ->
+ // Set default security policy that permits Hilla internal requests and denies all other
+ auth.requestMatchers(routeUtil::isRouteAllowed).permitAll()
+ // Gameyfin static resources and public endpoints
+ .requestMatchers(
+ "/login",
+ "/setup",
+ "/reset-password",
+ "/accept-invitation",
+ "/public/**",
+ "/images/**",
+ "/favicon.ico",
+ "/favicon.svg"
+ ).permitAll()
+ // Dynamic public access for certain endpoints
+ .requestMatchers(
+ "/",
+ "/game/**",
+ "/library/**",
+ "/search/**",
+ "/requests/**",
+ "/download/**"
+ ).access(DynamicPublicAccessAuthorizationManager(config))
}
http.sessionManagement { sessionManagement ->
@@ -67,9 +76,10 @@ class SecurityConfig(
// Not needed since the frontend is served by the backend
http.cors { cors -> cors.disable() }
- super.configure(http)
-
- setLoginView(http, "/login", "/")
+ http.with(VaadinSecurityConfigurer.vaadin()) { configurer ->
+ // use a custom login view and redirect to root on logout
+ configurer.loginView("/login", "/")
+ }
if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) {
// Use custom success handler to handle user registration
@@ -82,15 +92,12 @@ class SecurityConfig(
exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint())
}
}
- }
-
- @Throws(Exception::class)
- public override fun configure(web: WebSecurity) {
- super.configure(web)
if ("dev" in environment.activeProfiles) {
- web.ignoring().requestMatchers("/h2-console/**")
+ http.authorizeHttpRequests { auth -> auth.requestMatchers("/h2-console/**").permitAll() }
}
+
+ return http.build()
}
@Bean
From 7cf227d3dd98de306acbc2fb2b0022ea8cb4d32e Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Thu, 25 Sep 2025 14:25:40 +0200
Subject: [PATCH 25/32] Fix logout for SSO users
---
app/src/main/frontend/components/ProfileMenu.tsx | 11 +----------
.../gameyfin/app/core/security/SecurityConfig.kt | 15 +++++++++++----
2 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/app/src/main/frontend/components/ProfileMenu.tsx b/app/src/main/frontend/components/ProfileMenu.tsx
index 71909ad..b6a8043 100644
--- a/app/src/main/frontend/components/ProfileMenu.tsx
+++ b/app/src/main/frontend/components/ProfileMenu.tsx
@@ -2,7 +2,6 @@ import {useAuth} from "Frontend/util/auth";
import {GearFine, Question, SignOut, User} from "@phosphor-icons/react";
import {Dropdown, DropdownItem, DropdownMenu, DropdownTrigger} from "@heroui/react";
import {useNavigate} from "react-router";
-import {ConfigEndpoint} from "Frontend/generated/endpoints";
import Avatar from "Frontend/components/general/Avatar";
import {CollectionElement} from "@react-types/shared";
import {isAdmin} from "Frontend/util/utils";
@@ -11,14 +10,6 @@ export default function ProfileMenu() {
const auth = useAuth();
const navigate = useNavigate();
- async function logout() {
- if (auth.state.user?.managedBySso) {
- window.location.href = (await ConfigEndpoint.getSsoLogoutUrl()) || "/";
- } else {
- await auth.logout();
- }
- }
-
const profileMenuItems = [
{
label: "My Profile",
@@ -39,7 +30,7 @@ export default function ProfileMenu() {
{
label: "Sign Out",
icon: ,
- onClick: logout,
+ onClick: auth.logout,
color: "primary"
},
];
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
index 9edcba0..8397afa 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityConfig.kt
@@ -76,12 +76,14 @@ class SecurityConfig(
// Not needed since the frontend is served by the backend
http.cors { cors -> cors.disable() }
- http.with(VaadinSecurityConfigurer.vaadin()) { configurer ->
- // use a custom login view and redirect to root on logout
- configurer.loginView("/login", "/")
- }
if (config.get(ConfigProperties.SSO.OIDC.Enabled) == true) {
+
+ http.with(VaadinSecurityConfigurer.vaadin()) { configurer ->
+ // Redirect to SSO provider on logout
+ configurer.loginView("/login", config.get(ConfigProperties.SSO.OIDC.LogoutUrl))
+ }
+
// Use custom success handler to handle user registration
http.oauth2Login { oauth2Login -> oauth2Login.successHandler(ssoAuthenticationSuccessHandler) }
// Prevent unnecessary redirects
@@ -91,6 +93,11 @@ class SecurityConfig(
http.exceptionHandling { exceptionHandling ->
exceptionHandling.authenticationEntryPoint(CustomAuthenticationEntryPoint())
}
+ } else {
+ // Use default Vaadin login URLs
+ http.with(VaadinSecurityConfigurer.vaadin()) { configurer ->
+ configurer.loginView("/login")
+ }
}
if ("dev" in environment.activeProfiles) {
From b034fd3ce71296ad5a5506cb1166f69cd5a1e3a0 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 13:30:52 +0200
Subject: [PATCH 26/32] Remove unused endpoints
---
.../org/gameyfin/app/config/ConfigEndpoint.kt | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
index 67b11ff..f243112 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
@@ -43,19 +43,6 @@ class ConfigEndpoint(
}
/** Specific read-only endpoints for all users **/
-
- @DynamicPublicAccess
- @AnonymousAllowed
- fun isSsoEnabled(): Boolean = configService.get(ConfigProperties.SSO.OIDC.Enabled) == true
-
- @DynamicPublicAccess
- @AnonymousAllowed
- fun getSsoLogoutUrl(): String? = configService.get(ConfigProperties.SSO.OIDC.LogoutUrl)
-
- @DynamicPublicAccess
- @AnonymousAllowed
- fun isPublicAccessEnabled(): Boolean = configService.get(ConfigProperties.Libraries.AllowPublicAccess) == true
-
@DynamicPublicAccess
@AnonymousAllowed
fun areGameRequestsEnabled(): Boolean = configService.get(ConfigProperties.Requests.Games.Enabled) == true
From 9283a61f7eb6ea83e483108ff0eab0da775790c8 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 13:31:03 +0200
Subject: [PATCH 27/32] Update frontend dependencies
---
app/package-lock.json | 1604 ++++++++++++++++++++---------------------
app/package.json | 222 +++---
app/vite.generated.ts | 25 +-
3 files changed, 936 insertions(+), 915 deletions(-)
diff --git a/app/package-lock.json b/app/package-lock.json
index 0532cf8..6d571d6 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "gameyfin",
- "version": "2.0.1",
+ "version": "2.1.0-preview",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gameyfin",
- "version": "2.0.1",
+ "version": "2.1.0-preview",
"dependencies": {
"@heroui/react": "2.7.9",
"@material-tailwind/react": "^2.1.10",
@@ -14,22 +14,22 @@
"@polymer/polymer": "3.5.2",
"@react-stately/data": "^3.12.2",
"@react-types/shared": "^3.28.0",
- "@vaadin/bundles": "24.8.6",
+ "@vaadin/bundles": "24.9.0",
"@vaadin/common-frontend": "0.0.19",
- "@vaadin/hilla-file-router": "24.8.7",
- "@vaadin/hilla-frontend": "24.8.7",
- "@vaadin/hilla-lit-form": "24.8.7",
- "@vaadin/hilla-react-auth": "24.8.7",
- "@vaadin/hilla-react-crud": "24.8.7",
- "@vaadin/hilla-react-form": "24.8.7",
- "@vaadin/hilla-react-i18n": "24.8.7",
- "@vaadin/hilla-react-signals": "24.8.7",
- "@vaadin/polymer-legacy-adapter": "24.8.6",
- "@vaadin/react-components": "24.8.6",
+ "@vaadin/hilla-file-router": "24.9.0",
+ "@vaadin/hilla-frontend": "24.9.0",
+ "@vaadin/hilla-lit-form": "24.9.0",
+ "@vaadin/hilla-react-auth": "24.9.0",
+ "@vaadin/hilla-react-crud": "24.9.0",
+ "@vaadin/hilla-react-form": "24.9.0",
+ "@vaadin/hilla-react-i18n": "24.9.0",
+ "@vaadin/hilla-react-signals": "24.9.0",
+ "@vaadin/polymer-legacy-adapter": "24.9.0",
+ "@vaadin/react-components": "24.9.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.6",
- "@vaadin/vaadin-material-styles": "24.8.6",
- "@vaadin/vaadin-themable-mixin": "24.8.6",
+ "@vaadin/vaadin-lumo-styles": "24.9.0",
+ "@vaadin/vaadin-material-styles": "24.9.0",
+ "@vaadin/vaadin-themable-mixin": "24.9.0",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"classnames": "^2.5.1",
"construct-style-sheets-polyfill": "3.1.0",
@@ -66,17 +66,17 @@
"@types/node": "^22.4.0",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
- "@vaadin/hilla-generator-cli": "24.8.7",
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
- "@vaadin/hilla-generator-plugin-barrel": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-plugin-model": "24.8.7",
- "@vaadin/hilla-generator-plugin-push": "24.8.7",
- "@vaadin/hilla-generator-plugin-signals": "24.8.7",
- "@vaadin/hilla-generator-plugin-subtypes": "24.8.7",
- "@vaadin/hilla-generator-plugin-transfertypes": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-cli": "24.9.0",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-backbone": "24.9.0",
+ "@vaadin/hilla-generator-plugin-barrel": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-plugin-model": "24.9.0",
+ "@vaadin/hilla-generator-plugin-push": "24.9.0",
+ "@vaadin/hilla-generator-plugin-signals": "24.9.0",
+ "@vaadin/hilla-generator-plugin-subtypes": "24.9.0",
+ "@vaadin/hilla-generator-plugin-transfertypes": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"@vitejs/plugin-react": "4.5.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"async": "3.2.6",
@@ -91,7 +91,7 @@
"tailwindcss": "^3.4.13",
"transform-ast": "2.4.4",
"typescript": "5.8.3",
- "vite": "6.3.5",
+ "vite": "6.3.6",
"vite-plugin-checker": "0.9.3",
"workbox-build": "7.3.0",
"workbox-core": "7.3.0",
@@ -7191,168 +7191,168 @@
"license": "ISC"
},
"node_modules/@vaadin/a11y-base": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-24.8.6.tgz",
- "integrity": "sha512-c8SEpMbgdOXtfHG7HS4FT80UnVQ1TiOx2g7F2YWkJDGLJ+/GjiX7dGZfX3zn92U8Yc3voSCB0JsOu9OCtnGQTw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-24.9.0.tgz",
+ "integrity": "sha512-0JWDQa2x9S0jwoZKncaKs9Xe63Xmg8CQkaXKPWAsjGOS5PAmlAdxr+XmPpjAsuhVNfuwKu/798UdkMinxmfp4Q==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/accordion": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/accordion/-/accordion-24.8.6.tgz",
- "integrity": "sha512-aXvLcxUWFA5Q7W07aTAbnSairTmahQS5pKffswWzawfhBq8XHxRAXuui5lzOcmGyagV0SUP5di3hq8D3p8J9Nw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/accordion/-/accordion-24.9.0.tgz",
+ "integrity": "sha512-yC2eJVN+8Av+yOKNxDt2fwdPYtUnpcPU+2+dw45GmmOAMREQHgVF/QT6ULKSWELLnaZGokZs7vs2u/IDgPI9kg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/details": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/details": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/app-layout": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/app-layout/-/app-layout-24.8.6.tgz",
- "integrity": "sha512-Jl+RciFBnQlLImk3zkl/0TQrQmeuxf4MYqfsQ1oW9xdOEeR04VJ6Cw5BM+gG9I8PfszlyHYbFmnTE3JurRK7cA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/app-layout/-/app-layout-24.9.0.tgz",
+ "integrity": "sha512-S2FqqiZo2R5K5AEDpA5VSpxTfBfi+gdpuG44WRmVwysXcPSu2aXtPLVQC8SWxCC+zhDTOMksM6K5CCe4TeZNlw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/avatar": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/avatar/-/avatar-24.8.6.tgz",
- "integrity": "sha512-ZWe5g5ZorJXyd3d8DqDQkJ6TpGkwTsOGMQQgEWVVs00DyYENFCs4dF+jL9taYXfr7DF8x58XIS9aARQpKWnyIQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/avatar/-/avatar-24.9.0.tgz",
+ "integrity": "sha512-oPn6VkH+OXvR3DNXeebKkAisJLD3QYuRya9o7hu88KaTliCE9gErckdyb9dcN2k7jbPtE4ypaKi4THAP3b9c9A==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/tooltip": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/tooltip": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/avatar-group": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/avatar-group/-/avatar-group-24.8.6.tgz",
- "integrity": "sha512-sGGzMNbsIigD8ijjyMgqmPJ4Qir7IhUSu87pl17Iy3nNVIXk9hsoIPTKoDbPPCzuRHo+bTEEYKeGqGJFKeAFeA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/avatar-group/-/avatar-group-24.9.0.tgz",
+ "integrity": "sha512-dJo/a2SoO4fJhhc4OhuSLrQnXEaDfzlzIcrN3Z7aDfm5OA8yXXy1i1EfCLQmpJLMc/w/owikOw6TDJr3HeQfdg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/avatar": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/list-box": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/tooltip": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/avatar": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/list-box": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/tooltip": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/bundles": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/bundles/-/bundles-24.8.6.tgz",
- "integrity": "sha512-/7R6FNJuD78UUT4csMuAEZhf0Qgz7zJ4mIvhG/uUhJauV48S9zi1RFp0NLRIpn41LRjb1hLEyMIlbjA9k2JJkw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/bundles/-/bundles-24.9.0.tgz",
+ "integrity": "sha512-sziAucXzhm1+FHkUn4VJxYDL52yXwjriazin7WkKSn51+cAggeVvKFbkbcgPh8Tnp+0b6/CqQwDW2zNX/RIAfw==",
"license": "(Apache-2.0 OR SEE LICENSE IN https://vaadin.com/license/cvdl-4.0)",
"peerDependencies": {
"@open-wc/dedupe-mixin": "1.4.0",
"@polymer/polymer": "3.5.2",
- "@vaadin/a11y-base": "24.8.6",
- "@vaadin/accordion": "24.8.6",
- "@vaadin/app-layout": "24.8.6",
- "@vaadin/avatar": "24.8.6",
- "@vaadin/avatar-group": "24.8.6",
- "@vaadin/board": "24.8.6",
- "@vaadin/button": "24.8.6",
- "@vaadin/card": "24.8.6",
- "@vaadin/charts": "24.8.6",
- "@vaadin/checkbox": "24.8.6",
- "@vaadin/checkbox-group": "24.8.6",
- "@vaadin/combo-box": "24.8.6",
- "@vaadin/component-base": "24.8.6",
- "@vaadin/confirm-dialog": "24.8.6",
- "@vaadin/context-menu": "24.8.6",
- "@vaadin/cookie-consent": "24.8.6",
- "@vaadin/crud": "24.8.6",
- "@vaadin/custom-field": "24.8.6",
- "@vaadin/dashboard": "24.8.6",
- "@vaadin/date-picker": "24.8.6",
- "@vaadin/date-time-picker": "24.8.6",
- "@vaadin/details": "24.8.6",
- "@vaadin/dialog": "24.8.6",
- "@vaadin/email-field": "24.8.6",
- "@vaadin/field-base": "24.8.6",
- "@vaadin/field-highlighter": "24.8.6",
- "@vaadin/form-layout": "24.8.6",
- "@vaadin/grid": "24.8.6",
- "@vaadin/grid-pro": "24.8.6",
- "@vaadin/horizontal-layout": "24.8.6",
- "@vaadin/icon": "24.8.6",
- "@vaadin/icons": "24.8.6",
- "@vaadin/input-container": "24.8.6",
- "@vaadin/integer-field": "24.8.6",
- "@vaadin/item": "24.8.6",
- "@vaadin/list-box": "24.8.6",
- "@vaadin/lit-renderer": "24.8.6",
- "@vaadin/login": "24.8.6",
- "@vaadin/map": "24.8.6",
- "@vaadin/markdown": "24.8.6",
- "@vaadin/master-detail-layout": "24.8.6",
- "@vaadin/menu-bar": "24.8.6",
- "@vaadin/message-input": "24.8.6",
- "@vaadin/message-list": "24.8.6",
- "@vaadin/multi-select-combo-box": "24.8.6",
- "@vaadin/notification": "24.8.6",
- "@vaadin/number-field": "24.8.6",
- "@vaadin/overlay": "24.8.6",
- "@vaadin/password-field": "24.8.6",
- "@vaadin/polymer-legacy-adapter": "24.8.6",
- "@vaadin/popover": "24.8.6",
- "@vaadin/progress-bar": "24.8.6",
- "@vaadin/radio-group": "24.8.6",
- "@vaadin/rich-text-editor": "24.8.6",
- "@vaadin/scroller": "24.8.6",
- "@vaadin/select": "24.8.6",
- "@vaadin/side-nav": "24.8.6",
- "@vaadin/split-layout": "24.8.6",
- "@vaadin/tabs": "24.8.6",
- "@vaadin/tabsheet": "24.8.6",
- "@vaadin/text-area": "24.8.6",
- "@vaadin/text-field": "24.8.6",
- "@vaadin/time-picker": "24.8.6",
- "@vaadin/tooltip": "24.8.6",
- "@vaadin/upload": "24.8.6",
+ "@vaadin/a11y-base": "24.9.0",
+ "@vaadin/accordion": "24.9.0",
+ "@vaadin/app-layout": "24.9.0",
+ "@vaadin/avatar": "24.9.0",
+ "@vaadin/avatar-group": "24.9.0",
+ "@vaadin/board": "24.9.0",
+ "@vaadin/button": "24.9.0",
+ "@vaadin/card": "24.9.0",
+ "@vaadin/charts": "24.9.0",
+ "@vaadin/checkbox": "24.9.0",
+ "@vaadin/checkbox-group": "24.9.0",
+ "@vaadin/combo-box": "24.9.0",
+ "@vaadin/component-base": "24.9.0",
+ "@vaadin/confirm-dialog": "24.9.0",
+ "@vaadin/context-menu": "24.9.0",
+ "@vaadin/cookie-consent": "24.9.0",
+ "@vaadin/crud": "24.9.0",
+ "@vaadin/custom-field": "24.9.0",
+ "@vaadin/dashboard": "24.9.0",
+ "@vaadin/date-picker": "24.9.0",
+ "@vaadin/date-time-picker": "24.9.0",
+ "@vaadin/details": "24.9.0",
+ "@vaadin/dialog": "24.9.0",
+ "@vaadin/email-field": "24.9.0",
+ "@vaadin/field-base": "24.9.0",
+ "@vaadin/field-highlighter": "24.9.0",
+ "@vaadin/form-layout": "24.9.0",
+ "@vaadin/grid": "24.9.0",
+ "@vaadin/grid-pro": "24.9.0",
+ "@vaadin/horizontal-layout": "24.9.0",
+ "@vaadin/icon": "24.9.0",
+ "@vaadin/icons": "24.9.0",
+ "@vaadin/input-container": "24.9.0",
+ "@vaadin/integer-field": "24.9.0",
+ "@vaadin/item": "24.9.0",
+ "@vaadin/list-box": "24.9.0",
+ "@vaadin/lit-renderer": "24.9.0",
+ "@vaadin/login": "24.9.0",
+ "@vaadin/map": "24.9.0",
+ "@vaadin/markdown": "24.9.0",
+ "@vaadin/master-detail-layout": "24.9.0",
+ "@vaadin/menu-bar": "24.9.0",
+ "@vaadin/message-input": "24.9.0",
+ "@vaadin/message-list": "24.9.0",
+ "@vaadin/multi-select-combo-box": "24.9.0",
+ "@vaadin/notification": "24.9.0",
+ "@vaadin/number-field": "24.9.0",
+ "@vaadin/overlay": "24.9.0",
+ "@vaadin/password-field": "24.9.0",
+ "@vaadin/polymer-legacy-adapter": "24.9.0",
+ "@vaadin/popover": "24.9.0",
+ "@vaadin/progress-bar": "24.9.0",
+ "@vaadin/radio-group": "24.9.0",
+ "@vaadin/rich-text-editor": "24.9.0",
+ "@vaadin/scroller": "24.9.0",
+ "@vaadin/select": "24.9.0",
+ "@vaadin/side-nav": "24.9.0",
+ "@vaadin/split-layout": "24.9.0",
+ "@vaadin/tabs": "24.9.0",
+ "@vaadin/tabsheet": "24.9.0",
+ "@vaadin/text-area": "24.9.0",
+ "@vaadin/text-field": "24.9.0",
+ "@vaadin/time-picker": "24.9.0",
+ "@vaadin/tooltip": "24.9.0",
+ "@vaadin/upload": "24.9.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.6",
- "@vaadin/vaadin-themable-mixin": "24.8.6",
+ "@vaadin/vaadin-lumo-styles": "24.9.0",
+ "@vaadin/vaadin-themable-mixin": "24.9.0",
"@vaadin/vaadin-usage-statistics": "2.1.3",
- "@vaadin/vertical-layout": "24.8.6",
- "@vaadin/virtual-list": "24.8.6",
+ "@vaadin/vertical-layout": "24.9.0",
+ "@vaadin/virtual-list": "24.9.0",
"cookieconsent": "3.1.1",
"dompurify": "3.2.6",
"highcharts": "9.2.2",
@@ -7609,87 +7609,87 @@
}
},
"node_modules/@vaadin/button": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/button/-/button-24.8.6.tgz",
- "integrity": "sha512-mXZLt2u3elJ3WMLaw35zLIpZiPPB+hlpKdfTlOF22vDm1OIalVYpQlGcdDDOshtIGaUH1W3eb1MmlQhVAEFmkA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/button/-/button-24.9.0.tgz",
+ "integrity": "sha512-jurFt1NebS/621Lsyw9e93iDbT1cIkXRuoGXUst9FcQZXvYR6m/flL5H0t9GwVULJZ5LPljkAtyQZclV9Nxw3A==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/card": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/card/-/card-24.8.6.tgz",
- "integrity": "sha512-VgnQUkHY8I06INUSwXuNOrevG7PYQsBKr6k1r42KqEzLESiWV/pk+WpdyGRTEH4MbKc8oj6Ut6Uu6DtUpgK+eg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/card/-/card-24.9.0.tgz",
+ "integrity": "sha512-Iur3h+KURdMDtEibVNx+P+y1YBSlVUSB0AMpI/AvmSG5DNo1R1FjI+k72ZkGJEDi+pe/5g3n00fLZXLELS4prg==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/checkbox": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-24.8.6.tgz",
- "integrity": "sha512-wLaKHniu8MKO4sfKJMiC+F/fMZVi1l2CpRw3RTTFzKYQMeBVwVRv3Lg1yV75LN8KtHU0DBR5l2Xfb4pa+rRGFg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-24.9.0.tgz",
+ "integrity": "sha512-I5s30JkE+4Ke+clSWMVvQ/92553lGmv8PkTb1AF4OZms5DBM9F4QYuh4iHqQEwxBT48dsuSoD9hYDnFJjpoTYw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/checkbox-group": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/checkbox-group/-/checkbox-group-24.8.6.tgz",
- "integrity": "sha512-odQ9PnF5SfWAniYnhKb6gEDPkn0ZO+P7gK5AUit3TadeAGc9t2+7BgqcJlg0o6fflgfEwD/RKWV6PXCU30BVEw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/checkbox-group/-/checkbox-group-24.9.0.tgz",
+ "integrity": "sha512-UsmBTnXeLEJeRfRmUKNRKN63v5dWKoBCnTqwtWfXxj/ls52nZoD+KxNq9q/8EZ+1Zg7QrvvmPX70MHFJE2GqhQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/checkbox": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/checkbox": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/combo-box": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/combo-box/-/combo-box-24.8.6.tgz",
- "integrity": "sha512-baeU/8jn7JdV4R3tqoVJMHc3wsMeo4RMREPAYjiQ1T4q1378cOpIixoPgXBnRj0g2bGwVD0dCHXcfb0BtVo9KQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/combo-box/-/combo-box-24.9.0.tgz",
+ "integrity": "sha512-LLmBT19NCVKFuIGRbhF77mmhQLiyI72F3TF6GFKo/q/UtEwwQaBOFta2KHu//XhxuXJrIW99WqRLE1HJa/dBfw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
@@ -7706,9 +7706,9 @@
}
},
"node_modules/@vaadin/component-base": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-24.8.6.tgz",
- "integrity": "sha512-EnhFwUXkZ53AASaPMJukI0w/DTSM74IXh+j65GUcFbrlN/S7tdFCQhtIsaLX95pcEtestSXOSufI/kW/B+6hgA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-24.9.0.tgz",
+ "integrity": "sha512-P1mI6dqGxeDWwi/F9NFkl75M78Ni5ZEMcBn/TvCnNxpUOP6C+kZPIrZx3aTmC2Eb3QR7+0xsNosbzuiA5ewBXw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
@@ -7719,223 +7719,223 @@
}
},
"node_modules/@vaadin/confirm-dialog": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/confirm-dialog/-/confirm-dialog-24.8.6.tgz",
- "integrity": "sha512-Cm0bJFSfd+MU++d6N15XypSzpJ/1wcF8/NmTKMOW3falJOMHNl3F5/pzo1I24PBDjM/moQrXtOjUS5eKD6nf7g==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/confirm-dialog/-/confirm-dialog-24.9.0.tgz",
+ "integrity": "sha512-XGpyunZexH59gl+DebpIbbgwhMFthHdwSV9I+HqnaRPtO+4BoXUgYSgSYo7akl3j4KNHm2zqmPW7MX05jk4zVg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/dialog": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/dialog": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/context-menu": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/context-menu/-/context-menu-24.8.6.tgz",
- "integrity": "sha512-oxgko1QeexR0ZFXEr0wt/AgX202rDb2VG0k3Rdjl05e4fzKI7Z5sJastkLEx15CG27JuPEC2iiwrIThzdpTm5Q==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/context-menu/-/context-menu-24.9.0.tgz",
+ "integrity": "sha512-Vrno4SDyPABjPg18EegG+fUafiCsG4G/A64G8fBIlqAcYMR2a22jBv4qf5/cz15oJslQluSO/qwoP12/QZpGhw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/list-box": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/list-box": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/custom-field": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/custom-field/-/custom-field-24.8.6.tgz",
- "integrity": "sha512-yi8fAOl4rW55qqj2+eBlHdNvRaIvBEqcm1cZLEQufxi+F/8oCcYj52HceKYTrtnL4eIcwCxDm+E4F/Bvft/8jw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/custom-field/-/custom-field-24.9.0.tgz",
+ "integrity": "sha512-aP58szUcJjkaBHzUBt/b3ursYFoKNkkvFIDm9c2dyHfSNXvfQo6duykOiLoW6FX5K+W/zWCHp1l/imLXXzxp7A==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/date-picker": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/date-picker/-/date-picker-24.8.6.tgz",
- "integrity": "sha512-ofuf57/hynJiiMlyWbSpraQtce+htN1SL4J7pUvB315Qekb6Piic+u11/Y9UC22yl0du39KetsXTZ6dgk0oi1g==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/date-picker/-/date-picker-24.9.0.tgz",
+ "integrity": "sha512-bxRTnAP1uRPpPy9klDG+4Zy8KCYEY/Fi4Oyzl18lWiXUOf/rYp5fFqKmHqy8eKlIolAz8F4ZkuW/ax0WPwto3Q==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.2.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/date-time-picker": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/date-time-picker/-/date-time-picker-24.8.6.tgz",
- "integrity": "sha512-55I5JgZz3GGoGSUvntovjpqDQxn267C1G3kn9zELVCVEN8yNG2q8cdyunHLpKx+fj6y2/PZIkiVPNiUpBw/Www==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/date-time-picker/-/date-time-picker-24.9.0.tgz",
+ "integrity": "sha512-eKPZPf5Oa+Eog/DOs6aYJBwoBjiH8LLwQ/AAHWAmfnqj3VK0oWaibT+xnVvxzhZLpIDfIC5uqeQqf8GwMgY+KA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/custom-field": "~24.8.6",
- "@vaadin/date-picker": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/time-picker": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/custom-field": "~24.9.0",
+ "@vaadin/date-picker": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/time-picker": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/details": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/details/-/details-24.8.6.tgz",
- "integrity": "sha512-vJ7ufMXabp5ltZcsxlTwPwwB5LDwbRsQzFts4Z187L8s0E0k/18xHkdggIPqKZV+hoEh+NsqyZwjAOpK/qSqtQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/details/-/details-24.9.0.tgz",
+ "integrity": "sha512-rbPRhcblBrBD2xm7iN9qoOOsOG4Nfm9OUlwWYpr8MWwTZlHC6klIBPW0iBhRdWj5mvEZ2QUi1UyvYU7GQ+ugkw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/dialog": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/dialog/-/dialog-24.8.6.tgz",
- "integrity": "sha512-87h1iwoiS/B5+YWnS2XXWTtqzWJvpYK7BOaqrSsQIbPI9jww3TguTNKiHgu4i/yXXavMBoEhvBrJxJ46i9Ftdg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/dialog/-/dialog-24.9.0.tgz",
+ "integrity": "sha512-+oYrROOWNUthYX0XQQ/kCP2Zk09gektyRoTkARcJXlT4sHw8b9JllYxoJePh/71tABZOVJVzzNJT3HgYxrdILg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/email-field": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/email-field/-/email-field-24.8.6.tgz",
- "integrity": "sha512-OjT0OCpU+ByTsCO6YxSnbuT8f8m3ALEabf/3r7uPZYVaAxGTaD/rd2oyyq/bYPe8ZsY7rcTHvRyW91zb8DOzOA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/email-field/-/email-field-24.9.0.tgz",
+ "integrity": "sha512-sDYo81TmMVkLNIHtyWsfamTNai10WZu2IvkU2k6AGds7lMau5M6aJ7O2yMvuEQmJhTZevq0s1pKE084aCAjhoQ==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/text-field": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/text-field": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/field-base": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-24.8.6.tgz",
- "integrity": "sha512-B3DXzH1wys6airDSNKfBJZE8kH34N52dhW3DBhcwaQ87iw4KdTG3IbQ+3c7oHc4mezOhtfgtst00PIs8RCg6Hg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-24.9.0.tgz",
+ "integrity": "sha512-NR2U7pWYv3hleA24PkMOmBXC30oU8WIiGNbQDPP8f6wZEyIJtsyMb0tXL+tSdjp664KmEs/UWal/T1kaVvF8uA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/field-highlighter": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/field-highlighter/-/field-highlighter-24.8.6.tgz",
- "integrity": "sha512-TJBf2Nib6SW/rRUVsK81p+qdkXw/WjWxceypmE6nJlcZqGquqcz8TMqY+3g6dXcfUrMKi5nResxjkmkd1RNQxQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/field-highlighter/-/field-highlighter-24.9.0.tgz",
+ "integrity": "sha512-bmZmsdOUxHfHtg9ctYEnyRJxtJY+QBwetZ2gVTNRK45R11dWa5dTWuIH/XCDhsTlqn59ZI+Papfy3RI7m8vo0g==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/form-layout": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/form-layout/-/form-layout-24.8.6.tgz",
- "integrity": "sha512-7rCr8DxK/Wdn8F7bB/JX70vA6fzYbuQbF22odP6auzVRKWoE6JWY8gvkFIHtuAFnNt+Fc56CRbtHXPewqCY8QQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/form-layout/-/form-layout-24.9.0.tgz",
+ "integrity": "sha512-hVLITSzSHgazwpmfhD8avAEtNajJQC9As7EUj8oaX33PUnEnEXOxndDS41sk0VRZbv2KOvQEG55qTjGBkTT3Cw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/grid": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-24.8.6.tgz",
- "integrity": "sha512-RZUhKOunOdfErNakt4xz99gmmqmjWemYZDWtECh03xTPq2rtZXICmaK4/JqwJ/i+I8/TwDyMSUu3boqlFNiMYA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-24.9.0.tgz",
+ "integrity": "sha512-jEQLl+xr3NF4L9COQ1cKngAmPIIkwG8ni6An09pZfXR+9VW5kvVEV2EkIXoOgUFOjRrGi8k9237iUsKRd7KRHw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/checkbox": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/text-field": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/checkbox": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/text-field": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/hilla-file-router": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-file-router/-/hilla-file-router-24.8.7.tgz",
- "integrity": "sha512-pWSzxpd8NU4rZjKtPNMfMG7Qhnl7A3CyKGArWBvOCfCeyW0cdLt+Gkxl+606dsgoSqdyHMHrX0EdrtWtsO50EA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-file-router/-/hilla-file-router-24.9.0.tgz",
+ "integrity": "sha512-eZlYwHKYLRK+cdzlZtiZ1arv0nYvBsCTCdzID8a+/hHhlB+tbkLZ5CviSOtGuc02zFsxuInHZ5CncfhR6MtImA==",
"license": "Apache-2.0",
"dependencies": {
"@ungap/with-resolvers": "0.1.0",
- "@vaadin/hilla-generator-utils": "24.8.7",
- "@vaadin/hilla-react-auth": "24.8.7",
- "@vaadin/hilla-react-signals": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.9.0",
+ "@vaadin/hilla-react-auth": "24.9.0",
+ "@vaadin/hilla-react-signals": "24.9.0",
"tsc-template": "0.2.3",
"typescript": "5.8.3"
},
@@ -7946,9 +7946,9 @@
}
},
"node_modules/@vaadin/hilla-frontend": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-frontend/-/hilla-frontend-24.8.7.tgz",
- "integrity": "sha512-WTwv8CAEXZrKhdP0mszuptWKBx453X1fTohkcNx3V58vAhzg/cakBQsdXo1OqGBd3ZPbHUgZ1R5lFp1lL4qAgQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-frontend/-/hilla-frontend-24.9.0.tgz",
+ "integrity": "sha512-lK5aYh0LxhNSqG2D2KVL7+r7d/Yp7oZuzKiON8IMG6sXV93FABW8AfetTO1U0BjjMpbTkk2Aq2aA8+/LcMSLuw==",
"license": "Apache-2.0",
"dependencies": {
"@vaadin/common-frontend": "0.0.19",
@@ -7960,14 +7960,14 @@
}
},
"node_modules/@vaadin/hilla-generator-cli": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-cli/-/hilla-generator-cli-24.8.7.tgz",
- "integrity": "sha512-qd+lfRXtJmy59X4IoBEuBuGynx5dI0QftBNBjCRSSANWbv31WWTOqKOUtZiGTbAYqDqDKcw3Tu1RqMV1KXqaCA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-cli/-/hilla-generator-cli-24.9.0.tgz",
+ "integrity": "sha512-3SVZRPZCdpG8tYeyIxj5lPQHmbZ5RIc2MjG9qenWouRoMlY6BGaSurVRs/5sGNSA2DgyNuZjNflYl5CgC+BYOg==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7"
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0"
},
"bin": {
"tsgen": "bin/index.js"
@@ -7977,14 +7977,14 @@
}
},
"node_modules/@vaadin/hilla-generator-core": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-core/-/hilla-generator-core-24.8.7.tgz",
- "integrity": "sha512-XmbruZpmssOjyGfOcL5l5eSACLzHjHq5tlItTBlsfHpYyrT6SImJQQn0r2MX/tpDV4cNALzCd3tAbOsH3oREzw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-core/-/hilla-generator-core-24.9.0.tgz",
+ "integrity": "sha512-OfeAFCBd2b5YlAgkUQc0stfFHFltZ6czlQFCLCf1kJgTCTz37LJ/3MiB9qh4RxZBVB764391K4N1vntqR25JJA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.1.1",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"meow": "13.2.0",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8007,15 +8007,15 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-backbone": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-backbone/-/hilla-generator-plugin-backbone-24.8.7.tgz",
- "integrity": "sha512-c62JKMCAt4YGfo9BWwDeaLIDYz8mVl/lwMdyW4anJH+1GLbN1PTnLbjUToNkyl/aRHFQ1wsDz4MSuVFrbmkpYA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-backbone/-/hilla-generator-plugin-backbone-24.9.0.tgz",
+ "integrity": "sha512-nVyQGkWs6DIz3wLO0UPV28d9Q0bvli3VpC3h6xISXFiiQbnWwiX4mBmIdhv/U89B/jm0+14fxO6jgbVr6LgJlQ==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8025,15 +8025,15 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-barrel": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-barrel/-/hilla-generator-plugin-barrel-24.8.7.tgz",
- "integrity": "sha512-4ha9b1hWc8qRryoS9xKEe2isy/Mrn6W5VWugbTjCLLr1PJtwT7meLE41W5IMtiUawoc8Y5VqHwvYoHmEQ8SdOw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-barrel/-/hilla-generator-plugin-barrel-24.9.0.tgz",
+ "integrity": "sha512-83JXTAArEUY4iY9n7KhdJkAHqHqVof+z2h4fG41BITOg+6QOCB4mUZ7qIj2RU2QlG5FdzvHczvGYC1ym1JHv1Q==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-backbone": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"typescript": "5.8.3"
},
"engines": {
@@ -8041,14 +8041,14 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-client": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-client/-/hilla-generator-plugin-client-24.8.7.tgz",
- "integrity": "sha512-w6HjsM8i+whzuvYHDLLttyzYydXJr2JMdcRy0wjwJrpJiDS9btvKN7dbicesfY6yi9BgT/4UtLtvAAAvEkaF0w==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-client/-/hilla-generator-plugin-client-24.9.0.tgz",
+ "integrity": "sha512-QIYExb11oGbbfyvenquIn2glHlvOA00ZdJpqmgvMgwz2lpyxmBr5xO3zqitP0CbYKnJrgpzajgS+2jSFX6u/fw==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"typescript": "5.8.3"
},
"engines": {
@@ -8056,16 +8056,16 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-model": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-model/-/hilla-generator-plugin-model-24.8.7.tgz",
- "integrity": "sha512-KwAqPqVdhbPOU0Afur59FUB3uuZyr7TvCMCvBcpebaUe+8t7kPekoX/dYbhQP5tYh4VoFusQNe9xjVSaHvP5lQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-model/-/hilla-generator-plugin-model-24.9.0.tgz",
+ "integrity": "sha512-gLF33f5Fsg6wxtxe8dtRBePY5agZM8TX3Sv5SwblzFI4WmYcsBTZvM8L868AJZ0chVmO0CxaQKt73mnwk2rbzA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
- "@vaadin/hilla-lit-form": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-backbone": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
+ "@vaadin/hilla-lit-form": "24.9.0",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8075,15 +8075,15 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-push": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-push/-/hilla-generator-plugin-push-24.8.7.tgz",
- "integrity": "sha512-zG8q/a1i+mHHhjTwn0+NHtJGsbdmkT6Bdp26j5lVM+Qs2aUMhHBtIQCkUbgK+Ack4vi5FfE20ELCLYD4QwsSkg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-push/-/hilla-generator-plugin-push-24.9.0.tgz",
+ "integrity": "sha512-kVmYsxNYYQu6e3s1YtRuOMXImjeMD6rUDa19CDFHjI/N8BOCMZuGFEM/jd9tqXK6I90vfI4SFRzKyu7IwKsp4w==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8093,14 +8093,14 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-signals": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-signals/-/hilla-generator-plugin-signals-24.8.7.tgz",
- "integrity": "sha512-qAC2+jn8oDPzc2nFNEosLhKysl0hGrbRVBCC9fDDUyo5XdgZnIyCoETRpo12qAqRyP8y/LToeyAm1deNs82MWQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-signals/-/hilla-generator-plugin-signals-24.9.0.tgz",
+ "integrity": "sha512-51/DUnxY2ghDfvmcG2iWy55Z27LqQW4NLaREK86SJai4c+wy6uXNBwcepAU1vc+wlqp1QpxsUNKytAzAc9UIIA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"fast-deep-equal": "3.1.3",
"iterator-helpers-polyfill": "3.0.1",
"openapi-types": "12.1.3",
@@ -8112,16 +8112,16 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-subtypes": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-subtypes/-/hilla-generator-plugin-subtypes-24.8.7.tgz",
- "integrity": "sha512-CTGlPc4MMbuY31pCBFec38XMZ8Z5sKAMZ+hh4+sXrMLD/2Wrxt7qwf+R7OBULdSXnP0cHqv22FMPwYjOdMrR4g==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-subtypes/-/hilla-generator-plugin-subtypes-24.9.0.tgz",
+ "integrity": "sha512-k+gUxHzLV9/s9CjG537GAOj30BiYQus3ThOVa5ZTqmTlbHCzeJLONIAgqro5UmJIIHpXz7HDEeIC4IuITMj9oA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-plugin-model": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-plugin-model": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"fast-deep-equal": "^3.1.3",
"openapi-types": "^12.1.3",
"typescript": "5.8.3"
@@ -8131,16 +8131,16 @@
}
},
"node_modules/@vaadin/hilla-generator-plugin-transfertypes": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-transfertypes/-/hilla-generator-plugin-transfertypes-24.8.7.tgz",
- "integrity": "sha512-EssIPC+kMCTL985x7JwcqXA1UfX0/BTcUq14Ur2bj4NQeO4LP2JU5F1+IeGNlyyGQMHwP5MO0qnnMPkvviJFew==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-plugin-transfertypes/-/hilla-generator-plugin-transfertypes-24.9.0.tgz",
+ "integrity": "sha512-ZffLn0lu6PWVxwoYHR+SYaRXf3c8UzOZ/tXqw/PIpFzaJ+ugTsDDhkE67U9LtC7TxhAg82wcrhDLNSQneZcdmA==",
"dev": true,
"license": "Apache 2.0",
"dependencies": {
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-plugin-model": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-plugin-model": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"fast-deep-equal": "3.1.3",
"openapi-types": "12.1.3",
"typescript": "5.8.3"
@@ -8150,9 +8150,9 @@
}
},
"node_modules/@vaadin/hilla-generator-utils": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-utils/-/hilla-generator-utils-24.8.7.tgz",
- "integrity": "sha512-dKFpGw9V9offx4zne92xEPEw6bMhQGs/abbwkrOJgRTXoZIRQlzGUrc50+LRd7zRoaoE0OLKnBepL1O9TgIPKg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-generator-utils/-/hilla-generator-utils-24.9.0.tgz",
+ "integrity": "sha512-ZNqQzBLRZtj3LgPA7Gu9KiVF+wrr6gjOqChGr5HMxPPqcjED7pb1/+2Wiwmhi0GqoeVSdUpvLObNf5c9lTBpFQ==",
"license": "Apache 2.0",
"dependencies": {
"pino": "9.6.0",
@@ -8164,12 +8164,12 @@
}
},
"node_modules/@vaadin/hilla-lit-form": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-lit-form/-/hilla-lit-form-24.8.7.tgz",
- "integrity": "sha512-1+NeXot0YfuVEKeXjgZ6wMEz6VMePioQYlif70xpsw31trDNqIOhb9jrawe5YJVw7qntPDO+g8mkVuwuOBuYNg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-lit-form/-/hilla-lit-form-24.9.0.tgz",
+ "integrity": "sha512-7kavaIfJ9BM3Me9/5wEJjFjnz76arCzja5KihbXdxfhOil9hCeXZxRmq8P/4mcIt8qazJcmsm//AQYNeDqt1MQ==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-frontend": "24.9.0",
"validator": "13.12.0"
},
"peerDependencies": {
@@ -8177,12 +8177,12 @@
}
},
"node_modules/@vaadin/hilla-react-auth": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-auth/-/hilla-react-auth-24.8.7.tgz",
- "integrity": "sha512-baE+BbaIyds+6Cxb7ylqLGYc7f4CSBCmbqDpYciGuU2vurNdWrx2vGjgm3yoae0WU8UHMz/mjsf0bp6fkIc2+Q==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-auth/-/hilla-react-auth-24.9.0.tgz",
+ "integrity": "sha512-bj93m+z3FJ4N9RAwJijK9hDCxnMxy6CnDMrq/lwV5gg9exsyQMV+fsR6RdrnXu5gNsMsKQn8J8rB7rACUl0miA==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.7"
+ "@vaadin/hilla-frontend": "24.9.0"
},
"peerDependencies": {
"react": "18 || 19",
@@ -8191,16 +8191,16 @@
}
},
"node_modules/@vaadin/hilla-react-crud": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-crud/-/hilla-react-crud-24.8.7.tgz",
- "integrity": "sha512-6UPwCHQpNm7Zc4GpjgzFAPLyF32UtTS2Yy/0LxBUKAC6qqfO87SMMMxNy8YHYifxE1T3NVvUwMAFd1yQhYE+EQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-crud/-/hilla-react-crud-24.9.0.tgz",
+ "integrity": "sha512-NvYkP47Yol3po/Pvifa7O+FBMjULrTgBam2oPsOO2842ubkEfVpu8Y/2WbgeTxdjRcFE4vX5aAKeujRatbdrag==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.7",
- "@vaadin/hilla-lit-form": "24.8.7",
- "@vaadin/hilla-react-form": "24.8.7",
- "@vaadin/react-components": "24.8.6",
- "@vaadin/vaadin-lumo-styles": "24.8.6",
+ "@vaadin/hilla-frontend": "24.9.0",
+ "@vaadin/hilla-lit-form": "24.9.0",
+ "@vaadin/hilla-react-form": "24.9.0",
+ "@vaadin/react-components": "24.9.0",
+ "@vaadin/vaadin-lumo-styles": "24.9.0",
"type-fest": "4.35.0"
},
"peerDependencies": {
@@ -8209,12 +8209,12 @@
}
},
"node_modules/@vaadin/hilla-react-form": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-form/-/hilla-react-form-24.8.7.tgz",
- "integrity": "sha512-bIXY6toMLgiHk3wVkQc/JnhogvB4+SdSmxiTH+AdGSHw1SOAx+F/wPZI1M24tkluBCCg6YRNQwAR/dlf/MkONw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-form/-/hilla-react-form-24.9.0.tgz",
+ "integrity": "sha512-jI7T+lQLd4+SfSvKmHxJQ/xDLpGgvg7bx++YBOdh/5+mq29rxrBZaLKJctm+dlv8ukiu+NnxszGZG9P0M3EVbA==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-lit-form": "24.8.7"
+ "@vaadin/hilla-lit-form": "24.9.0"
},
"peerDependencies": {
"react": "18 || 19",
@@ -8222,13 +8222,13 @@
}
},
"node_modules/@vaadin/hilla-react-i18n": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-i18n/-/hilla-react-i18n-24.8.7.tgz",
- "integrity": "sha512-1SU3ndFOTN7KA1aI5bx+2L98Yob+KCHsicX3B62IEbp1ChUl+FhoO5TF8/aZoKVFRDnGcHPtorv+5Js8ABlY1g==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-i18n/-/hilla-react-i18n-24.9.0.tgz",
+ "integrity": "sha512-SxQXDzHczLV+ycWNjFvLyv2uQ2jgs9jQLSxNwnBXxg8nuHkEDso4TauupexPLxtQC8dDoLuDqrl7puyYE3Zdxg==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/hilla-frontend": "24.8.7",
- "@vaadin/hilla-react-signals": "24.8.7",
+ "@vaadin/hilla-frontend": "24.9.0",
+ "@vaadin/hilla-react-signals": "24.9.0",
"intl-messageformat": "10.7.11"
},
"peerDependencies": {
@@ -8300,13 +8300,13 @@
}
},
"node_modules/@vaadin/hilla-react-signals": {
- "version": "24.8.7",
- "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-signals/-/hilla-react-signals-24.8.7.tgz",
- "integrity": "sha512-eCQzw3pYkzNCe0eaXjtfYVKjUEC+qx9S8lZ0EE2gC22Ux80scn5LCMISlWj14yvdmn9oy0utnrkJ2p5+H6dQ/w==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/hilla-react-signals/-/hilla-react-signals-24.9.0.tgz",
+ "integrity": "sha512-G6aGAlch2Ch6VOmw2/PMm9GlEMRbaOgtN9JN5OneAKFNuPUogsqF1Rrd70tpb2aZpXnUkjd4RHoLVst8ffkAHA==",
"license": "Apache-2.0",
"dependencies": {
"@preact/signals-react": "3.0.1",
- "@vaadin/hilla-frontend": "24.8.7",
+ "@vaadin/hilla-frontend": "24.9.0",
"nanoid": "5.0.9"
},
"peerDependencies": {
@@ -8315,437 +8315,437 @@
}
},
"node_modules/@vaadin/horizontal-layout": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/horizontal-layout/-/horizontal-layout-24.8.6.tgz",
- "integrity": "sha512-xInsr7wQ92V3a8DNVyIbR3PZKrqVnG1jAMWGxad854j/f0RMZMecF2u/98mPW4GrKGxswCh2AFWR8w9HFuXLUg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/horizontal-layout/-/horizontal-layout-24.9.0.tgz",
+ "integrity": "sha512-jycAwgnPkSHdx1lLVDXrB+5Ka/XG42vFTGfWsjVDvglzMjbdfxEwD9JrQQtrabCji4UALMwinqL8BfVo53uAyQ==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/icon": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-24.8.6.tgz",
- "integrity": "sha512-qnZMtMuEpDxEMwNkrtLorIclSzdlYZaFGm1UD25PLl2uu3jFzsTgsHKxKFHm5Kc2b6gKFHpe/7DK52/D28PVow==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-24.9.0.tgz",
+ "integrity": "sha512-zhOnfs7xIoakGBfeeM+vzlt/ybBr3iiAmoriuh96shzTEWfe1oYl1j+XJzVkl011PMwVR6h1RaJZAdYsHHQ3cQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/icons": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/icons/-/icons-24.8.6.tgz",
- "integrity": "sha512-g5cfbUE1IM1lPbkoAQ5/rUiaseBU5DloywkukWAXmOHswAiW8dWFo78Hs5LINiIG9c2BlPIZfcfrKbn6cgCwnQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/icons/-/icons-24.9.0.tgz",
+ "integrity": "sha512-hwM3GwHE675A8I2u9CBe48vMKaQndUPy/qQaWVgqNUuzf4zLe2w6DPG0TQd9Q2jhqOgReYuZlu4Cof0UpgxRRw==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/icon": "~24.8.6"
+ "@vaadin/icon": "~24.9.0"
}
},
"node_modules/@vaadin/input-container": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-24.8.6.tgz",
- "integrity": "sha512-lxnrsQCjYjDC+5K17tSuJWCwx4/bn5KCRoKz5HJcXzyXsFj8iKF7+Pv1+OjKzRRHTF27kwjviMAaP+2/V5dEgA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-24.9.0.tgz",
+ "integrity": "sha512-oTu94eiHL2bEzSTCcoVpKxQwjTpO6d7EFex1AbWqhJP69akAMzo/ecTiRquRLfVJTFjXLIIdmZhitaagQIl3Rg==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/integer-field": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/integer-field/-/integer-field-24.8.6.tgz",
- "integrity": "sha512-ZbntjrZaEohzYp1Qn94DVUbFqZdYZy5Xemmspsr4HG6ahobFsMpgdrHjgEjG5q1hah+Cuo5Yr1mYuzxyTxJFug==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/integer-field/-/integer-field-24.9.0.tgz",
+ "integrity": "sha512-aAZ5eotJ2URJOnFRCSclPLArmzIXvYjVOpj9LCTsKJNRJTicVwtrje7HL4k+M5dDVpnALl1qetWgjtjPWHCD4A==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/number-field": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6"
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/number-field": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0"
}
},
"node_modules/@vaadin/item": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/item/-/item-24.8.6.tgz",
- "integrity": "sha512-ZLgUA/s+9rGw34yUxF8XYpb2SR+xDINT48Sd8d7SFZZKFwlucyeChNKM1MEsa9floKqaFx4x4eGUdiveDdloyw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/item/-/item-24.9.0.tgz",
+ "integrity": "sha512-I9OzcI/J3qaPFBHhf0KBBiFv2ibp69/z0DGEWrbLbV8IFaCRF6DiCh9JNDsQ4qUpPX0qiwoR+sLBsoZcq6RoXA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/list-box": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/list-box/-/list-box-24.8.6.tgz",
- "integrity": "sha512-Pw9RhWt2UPwzMpgOU5gfxPCWwnw25ip5gshhGuUh8IsRlqQAHa9iIEgk8mt85hJvoaKQ19HKMWx2qAB7PMJdlA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/list-box/-/list-box-24.9.0.tgz",
+ "integrity": "sha512-G6rMElDW9Lf77Uhtp6BJSJ0i8Vl4DNaNI5Sbm1bc7cIM8m0qWqrj9aBwx06qZclylq5Fyeth9DaG1H3F31J2pA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/lit-renderer": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-24.8.6.tgz",
- "integrity": "sha512-mrIq+0EV/XEelTaNOxQ0EcSOp+uYRH2tL+nIF/Ct8qCOinndBbaIOeScNlCABB/0IH3ltPmrmtEHxDsExPy4lw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-24.9.0.tgz",
+ "integrity": "sha512-lIqiPRmcp6BSRlvqRX3bI1tpyW4128GQ3CW55SrdQa+Ezu+x9uG0K4thnm+zMQIRCRjbfwVLsf30rZVWPhqTPg==",
"license": "Apache-2.0",
"dependencies": {
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/login": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/login/-/login-24.8.6.tgz",
- "integrity": "sha512-D+3VYR6BWSAxozNG9Z+NSfC1V3XmxoxKNj6vkLs26eyuBxMkDAtbIO6+eKAfk+73U8PbO1+V4QnHeR2MM23eLA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/login/-/login-24.9.0.tgz",
+ "integrity": "sha512-NeGac/wIVRt9yESsg8ej4T/yVQ69buOeH7JOXlheCyC+ZrML5hvuB4k674jmLKGi0vmhWQuGQq/w8cMkSnwwRw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/password-field": "~24.8.6",
- "@vaadin/text-field": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/password-field": "~24.9.0",
+ "@vaadin/text-field": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/markdown": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/markdown/-/markdown-24.8.6.tgz",
- "integrity": "sha512-g+Hf+IkAnSgWpJ83URzbQ0+n6D4Xt0wh6zPuVv2rLAnFL8/krAhDE/mqPnKZ0HJV+72zJtT2qAvi8Gc+Nva4eg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/markdown/-/markdown-24.9.0.tgz",
+ "integrity": "sha512-uIlAnWIGw0sTmaOhfN3tRc/eRzGJyS7zkLyZIsm7Ln2mZ/1WPcu/JM/lM0QNF5Ky8B3RR3fxsv4HhN2r5H2epA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"dompurify": "^3.2.5",
"lit": "^3.0.0",
"marked": "^15.0.11"
}
},
"node_modules/@vaadin/master-detail-layout": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/master-detail-layout/-/master-detail-layout-24.8.6.tgz",
- "integrity": "sha512-cpetSApUbY9altW+FhI0O11gxWptjk35zM2YEUy0ZYsi7tb8+eHvY4k5Az1IDGdijoa++PBwwiW4Qh3Rkgf/HA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/master-detail-layout/-/master-detail-layout-24.9.0.tgz",
+ "integrity": "sha512-VWSPqY58+qatTr9v2ZgshqUS0wdl0A+i4sfR7pEjD16kxRU27RRN2uuZ65TfNcNl2bWRRdIoOCEGSr1qRD/Uog==",
"license": "Apache-2.0",
"dependencies": {
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/menu-bar": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/menu-bar/-/menu-bar-24.8.6.tgz",
- "integrity": "sha512-m5g1w3XJTir8SIBqVnlGIcMHQXl8j8w3PnW+u9C/MC94Hgms10zYIplYeljS4RMq2wnfos+FO3Gcdp177JJMlg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/menu-bar/-/menu-bar-24.9.0.tgz",
+ "integrity": "sha512-lHEpNNV8oV4vYtJqaDBmKS56uoCI4yip08yCVXeSFNJzhREBFkertB8/HsCuryEwKQeARyY0uJbu1Cof7O4ekg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/context-menu": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/list-box": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/context-menu": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/list-box": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/message-input": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/message-input/-/message-input-24.8.6.tgz",
- "integrity": "sha512-+e6pnKHcJPvOcO71j71MEY75o4T1EKO7FgD0Vpg2h4fhJSfZJkWGrn3Dxckbf9kyyYBZnIqTX6votS9CbHKiXg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/message-input/-/message-input-24.9.0.tgz",
+ "integrity": "sha512-CpH0+Q9J/esCPVqf1yyZH52Kb3ZHCU3sHKmnhwltdPsrMH6c3nVt1nBe4j0eFoBCZ2nfYgb3QMIjbYNS/2rBPg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/text-area": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/text-area": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/message-list": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/message-list/-/message-list-24.8.6.tgz",
- "integrity": "sha512-pTEQiXiDEzMoeEWCIx03Tr9I8dhiRNrdT0ylyKaKlHxQgYGOA3RbVk4ArXVF+L7zhCyYefax2EjWlaCb2GhpSQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/message-list/-/message-list-24.9.0.tgz",
+ "integrity": "sha512-5cvaaokU5b65u7E5bN3tz9rA4cy5Y3e5lvBLlBSN0sywYOY8wYzicWsvOKmlKi6JttZzh9wnWtgsS8eStmqpbg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/avatar": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/markdown": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/avatar": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/markdown": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/multi-select-combo-box": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/multi-select-combo-box/-/multi-select-combo-box-24.8.6.tgz",
- "integrity": "sha512-DaH2oco8QTZ2J09AN8k2XKIQefIS1FnHqevik9CffUaPDSx0jaFYcakK7SKrxXOVnDP/NapHLoDFhaZbfREjEw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/multi-select-combo-box/-/multi-select-combo-box-24.9.0.tgz",
+ "integrity": "sha512-r3OYGMIkGcUSGVOauv0bDTvfk6C0RrwECLsaXpFUwKgXe483fxrMOJw2m0/mBoG825pCg0Gy8tQDkVQanBkzVA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/combo-box": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/combo-box": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/notification": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/notification/-/notification-24.8.6.tgz",
- "integrity": "sha512-NYjn6EcF6fKaq/ogULqrYh1GkJG7u+y9KeqeA4W4cjd0XI0fGPUVvqPoqdgds/6+DZWvjeCIWZqU4I5YZkBBig==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/notification/-/notification-24.9.0.tgz",
+ "integrity": "sha512-Ispt/VgOFFusid3nga32PP1RC20MlR19tvHFo1xHgzF4i13LFS9TH6hmgE81Q7KcP5P8ZVOTe+h4NLp9S61QGA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/number-field": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/number-field/-/number-field-24.8.6.tgz",
- "integrity": "sha512-oCkN8w1OZnSPNBa/YE+50GuAgkNOhA/IDNVBqTtOD0749IsFdFL5VeT0w9a8wW5v29cWplCc09k4PZYXyNBQdw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/number-field/-/number-field-24.9.0.tgz",
+ "integrity": "sha512-vQaD5fuhOON4iwJYscOVxW5DpnStKUBr6WAxc0uPeNN5A/cCXl6oyCkEuWu7rS6qT8gaOXfFpeUJ8/CD5T3NPA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/overlay": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/overlay/-/overlay-24.8.6.tgz",
- "integrity": "sha512-2nWoqUtW0/y6Q8mIwWK312oSkbOoUjw6+9u8amqBHZLkd8yWj9bvFwsPCrrUPhIlAtLPjXdLjMqCixEsAnTCbA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/overlay/-/overlay-24.9.0.tgz",
+ "integrity": "sha512-9xUCoN7lj3bpiASE7rUgfZy6ENt5oC8Fpa1wOGNf/Q+IguFaBXRzHm/eL2e/WbeQBksof8o4TlGbi9Rh8u7N0Q==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/password-field": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/password-field/-/password-field-24.8.6.tgz",
- "integrity": "sha512-T3hQLeo+Dy6ElZZKpTzRYT35a3E9MBJt1iomLX2+zoi7oeUr2zJJeS34bOEIVXE4Oh6gKSn+q3NgHEXrXqkwBw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/password-field/-/password-field-24.9.0.tgz",
+ "integrity": "sha512-WWrx5v9PFbwzE18KpxQtyOvxTTy8ljwpkYpJ8eFDZGpZDGW/CCzrl4ufu2DQfn/guxPeTrViYVab00CNU81dsQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/text-field": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/text-field": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/polymer-legacy-adapter": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/polymer-legacy-adapter/-/polymer-legacy-adapter-24.8.6.tgz",
- "integrity": "sha512-C17ElnaP0m0ae/ZGEBinj+ArcffPj1QZ4MFW0wW98z3XUdunS4HaUaE82OBH0Jcabmb1SkteJX9l/DpDg+Os8Q==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/polymer-legacy-adapter/-/polymer-legacy-adapter-24.9.0.tgz",
+ "integrity": "sha512-fJURZstbnd8uIYa57C6+iy0cufFA8/cxWypny1mmAc0aLh5Mlt94TBCJX76p9BbvMw2OcrQ4YCH3EGcWPmrsaA==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/popover": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/popover/-/popover-24.8.6.tgz",
- "integrity": "sha512-xHYbjzX4lodcip5TvF2zSnzoTrrbKgF3snqjaL8puftrG5DywV8uMvBw4pcRNiwBKkvpjCUfDDXngLuvWxPxpQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/popover/-/popover-24.9.0.tgz",
+ "integrity": "sha512-KTdrnUZ7V1V4m5ylhuhtdW3YgT+U4mZhELJXz+HwgLPY5rbNif3Ov6vN0FdunEgZIE/8YzKxHtX4CEoIJl7+tQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/progress-bar": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/progress-bar/-/progress-bar-24.8.6.tgz",
- "integrity": "sha512-62Fpp3sPc54c2L2EzkERsuUNMys4FOb2NWOal/iX1KrRLaXN+ObZ5JUGFZ7RlOlhSfuM03mvJw4wT0+kwzD75w==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/progress-bar/-/progress-bar-24.9.0.tgz",
+ "integrity": "sha512-xG/Ll+Z9Q2u1y+a8axGzdV5oCfwwthqzN2GrhihVJL/HJ0G4waNngar2M8NiRNd0FQWqb6pUtRZbQkEeVtwixA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/radio-group": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/radio-group/-/radio-group-24.8.6.tgz",
- "integrity": "sha512-qD9bd4AwDhyv3v0YZ4js0gUeHDz7D20yYp6MU1JFf2N7mTHkIkMuBJXX0ZUMzpmae0IypYngRJewHAWPCDgpCA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/radio-group/-/radio-group-24.9.0.tgz",
+ "integrity": "sha512-MjOlcohhi1WqUcE6WJY+D1NQ2/JFR3qJtIFKCQwwr+R7RxAVxtOtKw+0Xbwm8h3Y43shqL00UpfSWsPqstYkQA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/react-components": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/react-components/-/react-components-24.8.6.tgz",
- "integrity": "sha512-mWFCwKgKTH3DZ73L52itbJyfTmwenctc/c1nheqTiL3KwpyAJr4Ql5NcSSM9DHQgt53KcM1xBTKdohrKE2rGNQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/react-components/-/react-components-24.9.0.tgz",
+ "integrity": "sha512-RCL5oSligRXV9DzRgbniEpxDD2Rcwk3ZFLkOSxaxNZBHwHPDn1oqAw9H39iFr/wejymnvW71ryuagEeqyrApUA==",
"license": "Apache-2.0",
"dependencies": {
"@lit/react": "^1.0.7",
- "@vaadin/a11y-base": "24.8.6",
- "@vaadin/accordion": "24.8.6",
- "@vaadin/app-layout": "24.8.6",
- "@vaadin/avatar": "24.8.6",
- "@vaadin/avatar-group": "24.8.6",
- "@vaadin/button": "24.8.6",
- "@vaadin/card": "24.8.6",
- "@vaadin/checkbox": "24.8.6",
- "@vaadin/checkbox-group": "24.8.6",
- "@vaadin/combo-box": "24.8.6",
- "@vaadin/component-base": "24.8.6",
- "@vaadin/confirm-dialog": "24.8.6",
- "@vaadin/context-menu": "24.8.6",
- "@vaadin/custom-field": "24.8.6",
- "@vaadin/date-picker": "24.8.6",
- "@vaadin/date-time-picker": "24.8.6",
- "@vaadin/details": "24.8.6",
- "@vaadin/dialog": "24.8.6",
- "@vaadin/email-field": "24.8.6",
- "@vaadin/field-base": "24.8.6",
- "@vaadin/field-highlighter": "24.8.6",
- "@vaadin/form-layout": "24.8.6",
- "@vaadin/grid": "24.8.6",
- "@vaadin/horizontal-layout": "24.8.6",
- "@vaadin/icon": "24.8.6",
- "@vaadin/icons": "24.8.6",
- "@vaadin/input-container": "24.8.6",
- "@vaadin/integer-field": "24.8.6",
- "@vaadin/item": "24.8.6",
- "@vaadin/list-box": "24.8.6",
- "@vaadin/lit-renderer": "24.8.6",
- "@vaadin/login": "24.8.6",
- "@vaadin/markdown": "24.8.6",
- "@vaadin/master-detail-layout": "24.8.6",
- "@vaadin/menu-bar": "24.8.6",
- "@vaadin/message-input": "24.8.6",
- "@vaadin/message-list": "24.8.6",
- "@vaadin/multi-select-combo-box": "24.8.6",
- "@vaadin/notification": "24.8.6",
- "@vaadin/number-field": "24.8.6",
- "@vaadin/overlay": "24.8.6",
- "@vaadin/password-field": "24.8.6",
- "@vaadin/popover": "24.8.6",
- "@vaadin/progress-bar": "24.8.6",
- "@vaadin/radio-group": "24.8.6",
- "@vaadin/scroller": "24.8.6",
- "@vaadin/select": "24.8.6",
- "@vaadin/side-nav": "24.8.6",
- "@vaadin/split-layout": "24.8.6",
- "@vaadin/tabs": "24.8.6",
- "@vaadin/tabsheet": "24.8.6",
- "@vaadin/text-area": "24.8.6",
- "@vaadin/text-field": "24.8.6",
- "@vaadin/time-picker": "24.8.6",
- "@vaadin/tooltip": "24.8.6",
- "@vaadin/upload": "24.8.6",
- "@vaadin/vaadin-lumo-styles": "24.8.6",
- "@vaadin/vaadin-material-styles": "24.8.6",
- "@vaadin/vaadin-themable-mixin": "24.8.6",
- "@vaadin/vertical-layout": "24.8.6",
- "@vaadin/virtual-list": "24.8.6"
+ "@vaadin/a11y-base": "24.9.0",
+ "@vaadin/accordion": "24.9.0",
+ "@vaadin/app-layout": "24.9.0",
+ "@vaadin/avatar": "24.9.0",
+ "@vaadin/avatar-group": "24.9.0",
+ "@vaadin/button": "24.9.0",
+ "@vaadin/card": "24.9.0",
+ "@vaadin/checkbox": "24.9.0",
+ "@vaadin/checkbox-group": "24.9.0",
+ "@vaadin/combo-box": "24.9.0",
+ "@vaadin/component-base": "24.9.0",
+ "@vaadin/confirm-dialog": "24.9.0",
+ "@vaadin/context-menu": "24.9.0",
+ "@vaadin/custom-field": "24.9.0",
+ "@vaadin/date-picker": "24.9.0",
+ "@vaadin/date-time-picker": "24.9.0",
+ "@vaadin/details": "24.9.0",
+ "@vaadin/dialog": "24.9.0",
+ "@vaadin/email-field": "24.9.0",
+ "@vaadin/field-base": "24.9.0",
+ "@vaadin/field-highlighter": "24.9.0",
+ "@vaadin/form-layout": "24.9.0",
+ "@vaadin/grid": "24.9.0",
+ "@vaadin/horizontal-layout": "24.9.0",
+ "@vaadin/icon": "24.9.0",
+ "@vaadin/icons": "24.9.0",
+ "@vaadin/input-container": "24.9.0",
+ "@vaadin/integer-field": "24.9.0",
+ "@vaadin/item": "24.9.0",
+ "@vaadin/list-box": "24.9.0",
+ "@vaadin/lit-renderer": "24.9.0",
+ "@vaadin/login": "24.9.0",
+ "@vaadin/markdown": "24.9.0",
+ "@vaadin/master-detail-layout": "24.9.0",
+ "@vaadin/menu-bar": "24.9.0",
+ "@vaadin/message-input": "24.9.0",
+ "@vaadin/message-list": "24.9.0",
+ "@vaadin/multi-select-combo-box": "24.9.0",
+ "@vaadin/notification": "24.9.0",
+ "@vaadin/number-field": "24.9.0",
+ "@vaadin/overlay": "24.9.0",
+ "@vaadin/password-field": "24.9.0",
+ "@vaadin/popover": "24.9.0",
+ "@vaadin/progress-bar": "24.9.0",
+ "@vaadin/radio-group": "24.9.0",
+ "@vaadin/scroller": "24.9.0",
+ "@vaadin/select": "24.9.0",
+ "@vaadin/side-nav": "24.9.0",
+ "@vaadin/split-layout": "24.9.0",
+ "@vaadin/tabs": "24.9.0",
+ "@vaadin/tabsheet": "24.9.0",
+ "@vaadin/text-area": "24.9.0",
+ "@vaadin/text-field": "24.9.0",
+ "@vaadin/time-picker": "24.9.0",
+ "@vaadin/tooltip": "24.9.0",
+ "@vaadin/upload": "24.9.0",
+ "@vaadin/vaadin-lumo-styles": "24.9.0",
+ "@vaadin/vaadin-material-styles": "24.9.0",
+ "@vaadin/vaadin-themable-mixin": "24.9.0",
+ "@vaadin/vertical-layout": "24.9.0",
+ "@vaadin/virtual-list": "24.9.0"
},
"peerDependencies": {
"@types/react": "^18.2.37 || ^19",
@@ -8763,197 +8763,197 @@
}
},
"node_modules/@vaadin/scroller": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/scroller/-/scroller-24.8.6.tgz",
- "integrity": "sha512-skSvbsjUSY7DD565ygTFliLCjCL+5WRXUE6ErZy0zi0rbMaenfoVSD0yGz5Ryjxk2y4pZqq63Dn8keJDsb9B1w==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/scroller/-/scroller-24.9.0.tgz",
+ "integrity": "sha512-eA0hLJpDyl4ZkiFqSbg4Tr74cjbsZpALtokRLFd++0HPHVHW39SmZZy8od0bbYWtqyvpyFjFjwxcsrtfXmcDUA==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/select": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/select/-/select-24.8.6.tgz",
- "integrity": "sha512-68CmCfEyx5bN9e63qLxshKcCB6++mNT3fo/IANAU6HyC97i5tTUZXcChO3hILqXJ7pVeR+h8NHudzd1IFPmtoQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/select/-/select-24.9.0.tgz",
+ "integrity": "sha512-i/2N7I1x+agUbu4oRB68Jckb/yk4bcQ4fxSpoHX8m/XTh63VZzIQAv+HgM78yz1VtjfYjkEaZByxnfDP79whzw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.2.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/list-box": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/list-box": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/side-nav": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/side-nav/-/side-nav-24.8.6.tgz",
- "integrity": "sha512-RD4gI4I8r9FNJZ2FqrpBkbnXVs5fiHOFwOF4fxgfD/lajac8yr/0Nwwktf6CPrCRRafHh78C2uEEk/YUfog2FA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/side-nav/-/side-nav-24.9.0.tgz",
+ "integrity": "sha512-TlAFheYoIXfkC21AqjJsSci0s5XBzdMug+jwQBE3JRPlYVI8pYt71KqRGZ7tJGK7BQGEjAwrwQY4EmdbtHZ3rg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/split-layout": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/split-layout/-/split-layout-24.8.6.tgz",
- "integrity": "sha512-zTm/vCS1xfbmIB2XRiChiREgEGsIAUIJ7w4qM2nZzeGXSePK0ZQm6/2AP3Qsekd6WJJu1kqO/6iTQ2a2DN7TLQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/split-layout/-/split-layout-24.9.0.tgz",
+ "integrity": "sha512-mUjQRq49ZAziL9XW0wDEig0ISsd5YtIGrnId0Fb6c7gldWQhWPj5kboBZl+flD8eddl/8deP1bd7dS5AjP9WXg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/tabs": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/tabs/-/tabs-24.8.6.tgz",
- "integrity": "sha512-F/Aa4o+bvI01Gibw38WgrxJodCc6vSLNs6CMaE72KbD7hmCgjjiDbhY2HHS99/q5sUtALWM0VP54HbqWqCuEkw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/tabs/-/tabs-24.9.0.tgz",
+ "integrity": "sha512-DHxIlcoxT7szIx+MaAETV42qMudjyw5XNQNWtNxoBeuzAX/INKlbp+FLCFESbprWt9n/RkaA8oEz7eOg3T74Bg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/tabsheet": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/tabsheet/-/tabsheet-24.8.6.tgz",
- "integrity": "sha512-oNO3T4Gxai3BwUWAveq5MNxAgM+bCu6Z8BwEwps3Hn08ps0GEPM5uv3RZHPW+GrrYP58tlk+OREHDgT1qyk4rw==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/tabsheet/-/tabsheet-24.9.0.tgz",
+ "integrity": "sha512-cuo7CxtP4TTDqzM1tXj2dqc0rk7LnnIY9JdpQUja+CFBrAARVZPF2Mc8NmdUCP+jfUOjx+ivr8nO7d7Mv51bUw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/scroller": "~24.8.6",
- "@vaadin/tabs": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/scroller": "~24.9.0",
+ "@vaadin/tabs": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/text-area": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/text-area/-/text-area-24.8.6.tgz",
- "integrity": "sha512-GFKGmG1mkF6VrzUk8XT1u3wJexM40BlO9REsDHGQw64y8UBVL9nKyCGVUv0SjJc/y5ECrZKHiKhVpho+QRb+2g==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/text-area/-/text-area-24.9.0.tgz",
+ "integrity": "sha512-f2foxfFykKy1s6y5C5hWx3sxj9jEmkjDI09vUC24J3eyqDEiGbZc8k/d8pXvsT1ZxDaH77W6iZk6kzz2ei+MOg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/text-field": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-24.8.6.tgz",
- "integrity": "sha512-nLw3Oi0hjHWFhqT0RUDOx6D7aT8drW+PM0vgcCa6Q1I0zZnYUnevtGzyZBOlD+XmveEiXNx3jHhXMwHeu2fOaA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-24.9.0.tgz",
+ "integrity": "sha512-FA8JAQ1OWLhL6+aB38RTlgANkOzKQLtNZxeh4zRlDxSryx/PJIAnHMT0sdNKj0u3ddHQ97yPt+1451AkCJez4Q==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/time-picker": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/time-picker/-/time-picker-24.8.6.tgz",
- "integrity": "sha512-cxRVPZjprCwx+6+P6b2Ad9m6BFzZpsNAuQO8aL0fcdkIiG8jmKl3/KPNviB7LUlORMhsWr1dJIPU7GiyIKxrLA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/time-picker/-/time-picker-24.9.0.tgz",
+ "integrity": "sha512-1gfAEL9h6Ra7TL63qVsf/nweKvNDyJnkbgm7Tq5VPnpSRVFXsIP/EbaSURWsW2JFNMdHpD6MWoUcrZhiRYXq7w==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/combo-box": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/field-base": "~24.8.6",
- "@vaadin/input-container": "~24.8.6",
- "@vaadin/item": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/combo-box": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/field-base": "~24.9.0",
+ "@vaadin/input-container": "~24.9.0",
+ "@vaadin/item": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/tooltip": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/tooltip/-/tooltip-24.8.6.tgz",
- "integrity": "sha512-1xSQw5Wq0psKnl+HehmCWa/jKyofQyHGmTcJ3rtWDrqFr/dviIwx1n3TZu9FDeCSIRQxmIAdkErDYmouSdlOwA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/tooltip/-/tooltip-24.9.0.tgz",
+ "integrity": "sha512-jsuFH7JKNJWNDq2GVXCk946BPQfaTONe+mPJ2an5oS9BaT+fNMVykHQVDoLrT4D72uObb6l2Wf1LrfL3aMW+Cg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/overlay": "~24.8.6",
- "@vaadin/popover": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/overlay": "~24.9.0",
+ "@vaadin/popover": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/upload": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/upload/-/upload-24.8.6.tgz",
- "integrity": "sha512-/1y8UT0UUdrypnyOPTVIejDpk6pPNPbqcPhqRGtCHKUHAAdZKVjCFMe6NT4kzTFqvEICdKCWOtC57yiwPk8WvA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/upload/-/upload-24.9.0.tgz",
+ "integrity": "sha512-9290kc2r2fi1dRVN1UfVigGbfpWvTdD9CbqMjSwP6OIK9TWxuGvCrT7RsmV4QwZZT+IVabj8wda1kI7HyYpyYQ==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/a11y-base": "~24.8.6",
- "@vaadin/button": "~24.8.6",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/progress-bar": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/a11y-base": "~24.9.0",
+ "@vaadin/button": "~24.9.0",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/progress-bar": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
@@ -8964,32 +8964,32 @@
"license": "Apache-2.0"
},
"node_modules/@vaadin/vaadin-lumo-styles": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-24.8.6.tgz",
- "integrity": "sha512-b1db8UKEtzXPu9FWFwPZcydOkZvW9ApbeImWGBd/kn/gy1eeqwijF7slXdlwM7KrgoJJ/r+Rpok2nRYrbzp3xQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-24.9.0.tgz",
+ "integrity": "sha512-TEX7viimyHh87WOFNa1nehOEiEuOHddwF5kMDrqdXh1gAwJ33i4EM1pmXBUx4nJSbMYy+B7bszQkLJe4D1Qo8A==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/icon": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6"
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/icon": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0"
}
},
"node_modules/@vaadin/vaadin-material-styles": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/vaadin-material-styles/-/vaadin-material-styles-24.8.6.tgz",
- "integrity": "sha512-69MwODqJ6ksiocutIG3dCTbPwrYKrZGN/To9YtYQzTuDt/3JsjmIRG2VLdvUVicKbdxnr6ZRRlPHoKGF5RS0RA==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/vaadin-material-styles/-/vaadin-material-styles-24.9.0.tgz",
+ "integrity": "sha512-x0v5hkx/n5NQn6Ec69SsqWZ9oeKOz8789FdG4AVr3M3kCR6tD9iUIxLW7khDqcHbBnlHDUWz5MbTdLcCNaattw==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6"
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0"
}
},
"node_modules/@vaadin/vaadin-themable-mixin": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-24.8.6.tgz",
- "integrity": "sha512-CSJYyExRRk4o7ehjtlnGlhjXJgLAHqRTvXL3891STIB4syAgGuOFiHqWyyKr7CV6VFqa9DIHHkAXeZ9OeeIEVg==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-24.9.0.tgz",
+ "integrity": "sha512-OotFzUzeXFBl1wugpfnqLqdRiGQ410tPmL5+QQAfFxsTt1rXvrZn47Jso3lAa4BlI/fN/qau9K3HtavwlqDJGg==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
@@ -9011,32 +9011,32 @@
}
},
"node_modules/@vaadin/vertical-layout": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/vertical-layout/-/vertical-layout-24.8.6.tgz",
- "integrity": "sha512-o5z9QYGaXghnspuzNWt20+4Xa5Fbdzlhs4Xuwj7CriS8+sXIi8ELuDpvBc0w3RPbWV7jm80PZciVAQWXFoTipQ==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/vertical-layout/-/vertical-layout-24.9.0.tgz",
+ "integrity": "sha512-+jvCq9GXgtFxI1EBdtDJ0lbe9rGCurfsQnF1MuDfqzvJpUBQN07gdsZufr1yXQ6a4BSRfGtBFqSFQPFAZTkx5Q==",
"license": "Apache-2.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
"node_modules/@vaadin/virtual-list": {
- "version": "24.8.6",
- "resolved": "https://registry.npmjs.org/@vaadin/virtual-list/-/virtual-list-24.8.6.tgz",
- "integrity": "sha512-H/5781WZunm3nGdfJ6PO1IWUUnRvdpKoJA+tmO6Ifdhe0j9weLSj5bczFrYcr/LMMGjXvWJdTN6oW3sp+jJN8A==",
+ "version": "24.9.0",
+ "resolved": "https://registry.npmjs.org/@vaadin/virtual-list/-/virtual-list-24.9.0.tgz",
+ "integrity": "sha512-COWfn8c3EtyC0ec59MUWqLKRDygCAvNat85VlFuwVBRisSyOPzgtgEz2dKekYyC/Z//iz/ddr9AdrXoDiNNryw==",
"license": "Apache-2.0",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
- "@vaadin/component-base": "~24.8.6",
- "@vaadin/lit-renderer": "~24.8.6",
- "@vaadin/vaadin-lumo-styles": "~24.8.6",
- "@vaadin/vaadin-material-styles": "~24.8.6",
- "@vaadin/vaadin-themable-mixin": "~24.8.6",
+ "@vaadin/component-base": "~24.9.0",
+ "@vaadin/lit-renderer": "~24.9.0",
+ "@vaadin/vaadin-lumo-styles": "~24.9.0",
+ "@vaadin/vaadin-material-styles": "~24.9.0",
+ "@vaadin/vaadin-themable-mixin": "~24.9.0",
"lit": "^3.0.0"
}
},
@@ -16107,9 +16107,9 @@
}
},
"node_modules/vite": {
- "version": "6.3.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
- "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "version": "6.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
+ "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/app/package.json b/app/package.json
index 2cdd2dd..91c79ae 100644
--- a/app/package.json
+++ b/app/package.json
@@ -9,22 +9,22 @@
"@polymer/polymer": "3.5.2",
"@react-stately/data": "^3.12.2",
"@react-types/shared": "^3.28.0",
- "@vaadin/bundles": "24.8.6",
+ "@vaadin/bundles": "24.9.0",
"@vaadin/common-frontend": "0.0.19",
- "@vaadin/hilla-file-router": "24.8.7",
- "@vaadin/hilla-frontend": "24.8.7",
- "@vaadin/hilla-lit-form": "24.8.7",
- "@vaadin/hilla-react-auth": "24.8.7",
- "@vaadin/hilla-react-crud": "24.8.7",
- "@vaadin/hilla-react-form": "24.8.7",
- "@vaadin/hilla-react-i18n": "24.8.7",
- "@vaadin/hilla-react-signals": "24.8.7",
- "@vaadin/polymer-legacy-adapter": "24.8.6",
- "@vaadin/react-components": "24.8.6",
+ "@vaadin/hilla-file-router": "24.9.0",
+ "@vaadin/hilla-frontend": "24.9.0",
+ "@vaadin/hilla-lit-form": "24.9.0",
+ "@vaadin/hilla-react-auth": "24.9.0",
+ "@vaadin/hilla-react-crud": "24.9.0",
+ "@vaadin/hilla-react-form": "24.9.0",
+ "@vaadin/hilla-react-i18n": "24.9.0",
+ "@vaadin/hilla-react-signals": "24.9.0",
+ "@vaadin/polymer-legacy-adapter": "24.9.0",
+ "@vaadin/react-components": "24.9.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.6",
- "@vaadin/vaadin-material-styles": "24.8.6",
- "@vaadin/vaadin-themable-mixin": "24.8.6",
+ "@vaadin/vaadin-lumo-styles": "24.9.0",
+ "@vaadin/vaadin-material-styles": "24.9.0",
+ "@vaadin/vaadin-themable-mixin": "24.9.0",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"classnames": "^2.5.1",
"construct-style-sheets-polyfill": "3.1.0",
@@ -61,17 +61,17 @@
"@types/node": "^22.4.0",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
- "@vaadin/hilla-generator-cli": "24.8.7",
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
- "@vaadin/hilla-generator-plugin-barrel": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-plugin-model": "24.8.7",
- "@vaadin/hilla-generator-plugin-push": "24.8.7",
- "@vaadin/hilla-generator-plugin-signals": "24.8.7",
- "@vaadin/hilla-generator-plugin-subtypes": "24.8.7",
- "@vaadin/hilla-generator-plugin-transfertypes": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-cli": "24.9.0",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-backbone": "24.9.0",
+ "@vaadin/hilla-generator-plugin-barrel": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-plugin-model": "24.9.0",
+ "@vaadin/hilla-generator-plugin-push": "24.9.0",
+ "@vaadin/hilla-generator-plugin-signals": "24.9.0",
+ "@vaadin/hilla-generator-plugin-subtypes": "24.9.0",
+ "@vaadin/hilla-generator-plugin-transfertypes": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"@vitejs/plugin-react": "4.5.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"async": "3.2.6",
@@ -86,7 +86,7 @@
"tailwindcss": "^3.4.13",
"transform-ast": "2.4.4",
"typescript": "5.8.3",
- "vite": "6.3.5",
+ "vite": "6.3.6",
"vite-plugin-checker": "0.9.3",
"workbox-build": "7.3.0",
"workbox-core": "7.3.0",
@@ -142,85 +142,85 @@
"valtio": "$valtio",
"valtio-reactive": "$valtio-reactive",
"fzf": "$fzf",
- "@vaadin/a11y-base": "24.8.6",
- "@vaadin/accordion": "24.8.6",
- "@vaadin/app-layout": "24.8.6",
- "@vaadin/avatar": "24.8.6",
- "@vaadin/avatar-group": "24.8.6",
- "@vaadin/button": "24.8.6",
- "@vaadin/card": "24.8.6",
- "@vaadin/checkbox": "24.8.6",
- "@vaadin/checkbox-group": "24.8.6",
- "@vaadin/combo-box": "24.8.6",
- "@vaadin/component-base": "24.8.6",
- "@vaadin/confirm-dialog": "24.8.6",
- "@vaadin/context-menu": "24.8.6",
- "@vaadin/custom-field": "24.8.6",
- "@vaadin/date-picker": "24.8.6",
- "@vaadin/date-time-picker": "24.8.6",
- "@vaadin/details": "24.8.6",
- "@vaadin/dialog": "24.8.6",
- "@vaadin/email-field": "24.8.6",
- "@vaadin/field-base": "24.8.6",
- "@vaadin/field-highlighter": "24.8.6",
- "@vaadin/form-layout": "24.8.6",
- "@vaadin/grid": "24.8.6",
- "@vaadin/horizontal-layout": "24.8.6",
- "@vaadin/icon": "24.8.6",
- "@vaadin/icons": "24.8.6",
- "@vaadin/input-container": "24.8.6",
- "@vaadin/integer-field": "24.8.6",
- "@vaadin/item": "24.8.6",
- "@vaadin/list-box": "24.8.6",
- "@vaadin/lit-renderer": "24.8.6",
- "@vaadin/login": "24.8.6",
- "@vaadin/markdown": "24.8.6",
- "@vaadin/master-detail-layout": "24.8.6",
- "@vaadin/menu-bar": "24.8.6",
- "@vaadin/message-input": "24.8.6",
- "@vaadin/message-list": "24.8.6",
- "@vaadin/multi-select-combo-box": "24.8.6",
- "@vaadin/notification": "24.8.6",
- "@vaadin/number-field": "24.8.6",
- "@vaadin/overlay": "24.8.6",
- "@vaadin/password-field": "24.8.6",
- "@vaadin/popover": "24.8.6",
- "@vaadin/progress-bar": "24.8.6",
- "@vaadin/radio-group": "24.8.6",
- "@vaadin/scroller": "24.8.6",
- "@vaadin/select": "24.8.6",
- "@vaadin/side-nav": "24.8.6",
- "@vaadin/split-layout": "24.8.6",
- "@vaadin/tabs": "24.8.6",
- "@vaadin/tabsheet": "24.8.6",
- "@vaadin/text-area": "24.8.6",
- "@vaadin/text-field": "24.8.6",
- "@vaadin/time-picker": "24.8.6",
- "@vaadin/tooltip": "24.8.6",
- "@vaadin/upload": "24.8.6",
+ "@vaadin/a11y-base": "24.9.0",
+ "@vaadin/accordion": "24.9.0",
+ "@vaadin/app-layout": "24.9.0",
+ "@vaadin/avatar": "24.9.0",
+ "@vaadin/avatar-group": "24.9.0",
+ "@vaadin/button": "24.9.0",
+ "@vaadin/card": "24.9.0",
+ "@vaadin/checkbox": "24.9.0",
+ "@vaadin/checkbox-group": "24.9.0",
+ "@vaadin/combo-box": "24.9.0",
+ "@vaadin/component-base": "24.9.0",
+ "@vaadin/confirm-dialog": "24.9.0",
+ "@vaadin/context-menu": "24.9.0",
+ "@vaadin/custom-field": "24.9.0",
+ "@vaadin/date-picker": "24.9.0",
+ "@vaadin/date-time-picker": "24.9.0",
+ "@vaadin/details": "24.9.0",
+ "@vaadin/dialog": "24.9.0",
+ "@vaadin/email-field": "24.9.0",
+ "@vaadin/field-base": "24.9.0",
+ "@vaadin/field-highlighter": "24.9.0",
+ "@vaadin/form-layout": "24.9.0",
+ "@vaadin/grid": "24.9.0",
+ "@vaadin/horizontal-layout": "24.9.0",
+ "@vaadin/icon": "24.9.0",
+ "@vaadin/icons": "24.9.0",
+ "@vaadin/input-container": "24.9.0",
+ "@vaadin/integer-field": "24.9.0",
+ "@vaadin/item": "24.9.0",
+ "@vaadin/list-box": "24.9.0",
+ "@vaadin/lit-renderer": "24.9.0",
+ "@vaadin/login": "24.9.0",
+ "@vaadin/markdown": "24.9.0",
+ "@vaadin/master-detail-layout": "24.9.0",
+ "@vaadin/menu-bar": "24.9.0",
+ "@vaadin/message-input": "24.9.0",
+ "@vaadin/message-list": "24.9.0",
+ "@vaadin/multi-select-combo-box": "24.9.0",
+ "@vaadin/notification": "24.9.0",
+ "@vaadin/number-field": "24.9.0",
+ "@vaadin/overlay": "24.9.0",
+ "@vaadin/password-field": "24.9.0",
+ "@vaadin/popover": "24.9.0",
+ "@vaadin/progress-bar": "24.9.0",
+ "@vaadin/radio-group": "24.9.0",
+ "@vaadin/scroller": "24.9.0",
+ "@vaadin/select": "24.9.0",
+ "@vaadin/side-nav": "24.9.0",
+ "@vaadin/split-layout": "24.9.0",
+ "@vaadin/tabs": "24.9.0",
+ "@vaadin/tabsheet": "24.9.0",
+ "@vaadin/text-area": "24.9.0",
+ "@vaadin/text-field": "24.9.0",
+ "@vaadin/time-picker": "24.9.0",
+ "@vaadin/tooltip": "24.9.0",
+ "@vaadin/upload": "24.9.0",
"@vaadin/router": "2.0.0",
- "@vaadin/vertical-layout": "24.8.6",
- "@vaadin/virtual-list": "24.8.6"
+ "@vaadin/vertical-layout": "24.9.0",
+ "@vaadin/virtual-list": "24.9.0"
},
"vaadin": {
"dependencies": {
"@polymer/polymer": "3.5.2",
- "@vaadin/bundles": "24.8.6",
+ "@vaadin/bundles": "24.9.0",
"@vaadin/common-frontend": "0.0.19",
- "@vaadin/hilla-file-router": "24.8.7",
- "@vaadin/hilla-frontend": "24.8.7",
- "@vaadin/hilla-lit-form": "24.8.7",
- "@vaadin/hilla-react-auth": "24.8.7",
- "@vaadin/hilla-react-crud": "24.8.7",
- "@vaadin/hilla-react-form": "24.8.7",
- "@vaadin/hilla-react-i18n": "24.8.7",
- "@vaadin/hilla-react-signals": "24.8.7",
- "@vaadin/polymer-legacy-adapter": "24.8.6",
- "@vaadin/react-components": "24.8.6",
+ "@vaadin/hilla-file-router": "24.9.0",
+ "@vaadin/hilla-frontend": "24.9.0",
+ "@vaadin/hilla-lit-form": "24.9.0",
+ "@vaadin/hilla-react-auth": "24.9.0",
+ "@vaadin/hilla-react-crud": "24.9.0",
+ "@vaadin/hilla-react-form": "24.9.0",
+ "@vaadin/hilla-react-i18n": "24.9.0",
+ "@vaadin/hilla-react-signals": "24.9.0",
+ "@vaadin/polymer-legacy-adapter": "24.9.0",
+ "@vaadin/react-components": "24.9.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
- "@vaadin/vaadin-lumo-styles": "24.8.6",
- "@vaadin/vaadin-material-styles": "24.8.6",
- "@vaadin/vaadin-themable-mixin": "24.8.6",
+ "@vaadin/vaadin-lumo-styles": "24.9.0",
+ "@vaadin/vaadin-material-styles": "24.9.0",
+ "@vaadin/vaadin-themable-mixin": "24.9.0",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3",
@@ -236,17 +236,17 @@
"@rollup/pluginutils": "5.1.4",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
- "@vaadin/hilla-generator-cli": "24.8.7",
- "@vaadin/hilla-generator-core": "24.8.7",
- "@vaadin/hilla-generator-plugin-backbone": "24.8.7",
- "@vaadin/hilla-generator-plugin-barrel": "24.8.7",
- "@vaadin/hilla-generator-plugin-client": "24.8.7",
- "@vaadin/hilla-generator-plugin-model": "24.8.7",
- "@vaadin/hilla-generator-plugin-push": "24.8.7",
- "@vaadin/hilla-generator-plugin-signals": "24.8.7",
- "@vaadin/hilla-generator-plugin-subtypes": "24.8.7",
- "@vaadin/hilla-generator-plugin-transfertypes": "24.8.7",
- "@vaadin/hilla-generator-utils": "24.8.7",
+ "@vaadin/hilla-generator-cli": "24.9.0",
+ "@vaadin/hilla-generator-core": "24.9.0",
+ "@vaadin/hilla-generator-plugin-backbone": "24.9.0",
+ "@vaadin/hilla-generator-plugin-barrel": "24.9.0",
+ "@vaadin/hilla-generator-plugin-client": "24.9.0",
+ "@vaadin/hilla-generator-plugin-model": "24.9.0",
+ "@vaadin/hilla-generator-plugin-push": "24.9.0",
+ "@vaadin/hilla-generator-plugin-signals": "24.9.0",
+ "@vaadin/hilla-generator-plugin-subtypes": "24.9.0",
+ "@vaadin/hilla-generator-plugin-transfertypes": "24.9.0",
+ "@vaadin/hilla-generator-utils": "24.9.0",
"@vitejs/plugin-react": "4.5.0",
"async": "3.2.6",
"glob": "11.0.2",
@@ -256,13 +256,13 @@
"strip-css-comments": "5.0.0",
"transform-ast": "2.4.4",
"typescript": "5.8.3",
- "vite": "6.3.5",
+ "vite": "6.3.6",
"vite-plugin-checker": "0.9.3",
"workbox-build": "7.3.0",
"workbox-core": "7.3.0",
"workbox-precaching": "7.3.0"
},
"disableUsageStatistics": true,
- "hash": "e499c8893c397649c698f302e100ee1e48833c88e57bd0829fbf86dc5a14cfd8"
+ "hash": "dba97848bdace60924f9cee496353baae70cfa4fccc7bacaf827807c51908866"
}
}
\ No newline at end of file
diff --git a/app/vite.generated.ts b/app/vite.generated.ts
index 7667173..703ba4c 100644
--- a/app/vite.generated.ts
+++ b/app/vite.generated.ts
@@ -82,6 +82,8 @@ const themeOptions = {
};
const hasExportedWebComponents = existsSync(path.resolve(frontendFolder, 'web-component.html'));
+const commercialBannerComponent = path.resolve(frontendFolder, settings.generatedFolder, 'commercial-banner.js');
+const hasCommercialBanner = existsSync(commercialBannerComponent);
const target = ['safari15', 'es2022'];
@@ -259,6 +261,10 @@ function statsExtracterPlugin(): PluginOption {
);
frontendFiles[`index.ts`] = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');
}
+ if (hasCommercialBanner) {
+ const fileBuffer = readFileSync(commercialBannerComponent, { encoding: 'utf-8' }).replace(/\r\n/g, '\n');
+ frontendFiles[settings.generatedFolder + '/commercial-banner.js'] = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');
+ }
const themeJsonContents: Record = {};
const themesFolder = path.resolve(jarResourcesFolder, 'themes');
@@ -576,6 +582,7 @@ function preserveUsageStats() {
export const vaadinConfig: UserConfigFn = (env) => {
const devMode = env.mode === 'development';
const productionMode = !devMode && !devBundle
+ const commercialBanner = productionMode && hasCommercialBanner;
if (devMode && process.env.watchDogPort) {
// Open a connection with the Java dev-mode handler in order to finish
@@ -734,14 +741,21 @@ export const vaadinConfig: UserConfigFn = (env) => {
if (path !== '/web-component.html') {
return;
}
-
- return [
+ const scripts = [
{
tag: 'script',
attrs: { type: 'module', src: `/generated/vaadin-web-component.ts` },
injectTo: 'head'
}
];
+ if (commercialBanner) {
+ scripts.push({
+ tag: 'script',
+ attrs: { type: 'module', src: '/generated/commercial-banner.js' },
+ injectTo: 'head'
+ });
+ }
+ return scripts;
}
}
},
@@ -768,6 +782,13 @@ export const vaadinConfig: UserConfigFn = (env) => {
attrs: { type: 'module', src: '/generated/vaadin.ts' },
injectTo: 'head'
});
+ if (commercialBanner) {
+ scripts.push({
+ tag: 'script',
+ attrs: { type: 'module', src: '/generated/commercial-banner.js' },
+ injectTo: 'head'
+ });
+ }
return scripts;
}
}
From 1220fb52850a6fcc789bff1b9c8141f27db484d0 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 13:52:46 +0200
Subject: [PATCH 28/32] Add Flyway Add baseline schema Add v2.1.0 db migration
---
app/build.gradle.kts | 1 +
app/src/main/resources/application.yml | 5 +-
.../V2.0.0__Initial_schema_creation.sql | 349 ++++++++++++++++++
...e_image_uniques_and_rename_constraints.sql | 122 ++++++
4 files changed, 476 insertions(+), 1 deletion(-)
create mode 100644 app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql
create mode 100644 app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 54760e4..306f01e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -54,6 +54,7 @@ dependencies {
// Persistence & I/O
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.17")
+ implementation("org.flywaydb:flyway-core")
implementation("commons-io:commons-io:2.18.0")
// SSO
diff --git a/app/src/main/resources/application.yml b/app/src/main/resources/application.yml
index 33e4b67..29374e8 100644
--- a/app/src/main/resources/application.yml
+++ b/app/src/main/resources/application.yml
@@ -31,7 +31,7 @@ spring:
jpa:
# defer-datasource-initialization: true
hibernate:
- ddl-auto: update
+ ddl-auto: validate
open-in-view: true
mustache:
check-template-location: false
@@ -50,6 +50,9 @@ spring:
virtual.enabled: true
mvc:
async.request-timeout: 0
+ flyway:
+ baseline-on-migrate: true
+ baseline-version: 2.0.0
vaadin:
# To improve the performance during development.
diff --git a/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql b/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql
new file mode 100644
index 0000000..b9c7c17
--- /dev/null
+++ b/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql
@@ -0,0 +1,349 @@
+-- Flyway Migration: V2.0.0
+-- Purpose: Initial schema creation for Gameyfin application.
+
+/******************************************************************************************
+ * 1. Sequences (hi/lo allocation size = 50 for performance)
+ ******************************************************************************************/
+create sequence COMPANY_SEQ
+ increment by 50;
+
+create sequence DIRECTORY_MAPPING_SEQ
+ increment by 50;
+
+create sequence GAME_FIELD_METADATA_SEQ
+ increment by 50;
+
+create sequence GAME_FIELD_SOURCE_SEQ
+ increment by 50;
+
+create sequence GAME_SEQ
+ increment by 50;
+
+create sequence IMAGE_SEQ
+ increment by 50;
+
+create sequence LIBRARY_SEQ
+ increment by 50;
+
+create sequence USERS_SEQ
+ increment by 50;
+
+/******************************************************************************************
+ * 2. Tables
+ ******************************************************************************************/
+create table APP_CONFIG
+(
+ "key" CHARACTER VARYING(255) not null
+ primary key,
+ "value" CHARACTER VARYING(255)
+);
+
+create table COMPANY
+(
+ ID BIGINT not null
+ primary key,
+ NAME CHARACTER VARYING(255),
+ TYPE TINYINT,
+ constraint UK4UCNYHR8I0URHWDUDFAHKOB9E
+ unique (NAME, TYPE),
+ check ("TYPE" BETWEEN 0 AND 1)
+);
+
+create table DIRECTORY_MAPPING
+(
+ ID BIGINT not null
+ primary key,
+ EXTERNAL_PATH CHARACTER VARYING(255),
+ INTERNAL_PATH CHARACTER VARYING(255)
+ constraint UKJ3GSATFAHEWFOLSEAJ29O3KYT
+ unique
+);
+
+create table IMAGE
+(
+ ID BIGINT not null
+ primary key,
+ CONTENT_ID CHARACTER VARYING(255),
+ CONTENT_LENGTH BIGINT,
+ MIME_TYPE CHARACTER VARYING(255),
+ ORIGINAL_URL CHARACTER VARYING(255),
+ TYPE TINYINT,
+ check ("TYPE" BETWEEN 0 AND 3)
+);
+
+create table LIBRARY
+(
+ ID BIGINT not null
+ primary key,
+ CREATED_AT TIMESTAMP WITH TIME ZONE not null,
+ NAME CHARACTER VARYING(255),
+ UPDATED_AT TIMESTAMP WITH TIME ZONE not null
+);
+
+create table GAME
+(
+ ID BIGINT not null
+ primary key,
+ COMMENT CHARACTER LARGE OBJECT,
+ CREATED_AT TIMESTAMP WITH TIME ZONE not null,
+ CRITIC_RATING INTEGER,
+ DOWNLOAD_COUNT INTEGER,
+ FILE_SIZE BIGINT,
+ MATCH_CONFIRMED BOOLEAN,
+ PATH CHARACTER VARYING(255)
+ constraint UK4WXN9FPXFQ8QXPSB7FY0O3NOA
+ unique,
+ RELEASE TIMESTAMP WITH TIME ZONE,
+ SUMMARY CHARACTER LARGE OBJECT,
+ TITLE CHARACTER VARYING(255),
+ UPDATED_AT TIMESTAMP WITH TIME ZONE not null,
+ USER_RATING INTEGER,
+ COVER_IMAGE_ID BIGINT
+ constraint UK52RQ62FLPBNTI77BYKM7UAHKQ
+ unique,
+ HEADER_IMAGE_ID BIGINT
+ constraint UK30B16LLQV54H40XIOGP7T9P35
+ unique,
+ LIBRARY_ID BIGINT,
+ constraint FK6CVB43REAYSNYPI0XDY6HQTVF
+ foreign key (COVER_IMAGE_ID) references IMAGE,
+ constraint FK8N86NDPGKMOO7YOLX6HL8N84G
+ foreign key (HEADER_IMAGE_ID) references IMAGE,
+ constraint FKIUVR8XFB63T1K6T43EYYXVO2C
+ foreign key (LIBRARY_ID) references LIBRARY
+);
+
+create table GAME_DEVELOPERS
+(
+ GAME_ID BIGINT not null,
+ DEVELOPERS_ID BIGINT not null,
+ constraint FKB12PO9L2B9OJBAIHC82MM2QXB
+ foreign key (DEVELOPERS_ID) references COMPANY,
+ constraint FKS4IJSVPIJ53DSL143XVRGBS09
+ foreign key (GAME_ID) references GAME
+);
+
+create table GAME_FEATURES
+(
+ GAME_ID BIGINT not null,
+ FEATURES TINYINT,
+ constraint FK63XLTCT60SCIMPM06K8BHBE4A
+ foreign key (GAME_ID) references GAME,
+ check ("FEATURES" BETWEEN 0 AND 23)
+);
+
+create table GAME_GENRES
+(
+ GAME_ID BIGINT not null,
+ GENRES TINYINT,
+ constraint FKDTSX09YOPD98E0LUEWRUSJD9E
+ foreign key (GAME_ID) references GAME,
+ check ("GENRES" BETWEEN 0 AND 25)
+);
+
+create table GAME_IMAGES
+(
+ GAME_ID BIGINT not null,
+ IMAGES_ID BIGINT not null
+ constraint UKBDE7M3TKHIEEYBINM2ED0B6X1
+ unique,
+ constraint FK5YWV1DMXCM2VSQUEB7RHQ3JK9
+ foreign key (IMAGES_ID) references IMAGE,
+ constraint FKOWCPUCV45OX8GT28TXGVHF1AA
+ foreign key (GAME_ID) references GAME
+);
+
+create table GAME_KEYWORDS
+(
+ GAME_ID BIGINT not null,
+ KEYWORDS CHARACTER VARYING(255),
+ constraint FKMVF6HNJ7ROMQQM2EX70A9NVAC
+ foreign key (GAME_ID) references GAME
+);
+
+create table GAME_PERSPECTIVES
+(
+ GAME_ID BIGINT not null,
+ PERSPECTIVES TINYINT,
+ constraint FKHUEENG29Y1GHBRDI5QHGUXH6E
+ foreign key (GAME_ID) references GAME,
+ check ("PERSPECTIVES" BETWEEN 0 AND 7)
+);
+
+create table GAME_PUBLISHERS
+(
+ GAME_ID BIGINT not null,
+ PUBLISHERS_ID BIGINT not null,
+ constraint FK49R2KB61LIJ54BQB4VNTST97N
+ foreign key (GAME_ID) references GAME,
+ constraint FKNGLD5ESGRBRH95J5BJF0HEF85
+ foreign key (PUBLISHERS_ID) references COMPANY
+);
+
+create table GAME_THEMES
+(
+ GAME_ID BIGINT not null,
+ THEMES TINYINT,
+ constraint FKRV351JXLIOY0A17Y5BBJJ6FW4
+ foreign key (GAME_ID) references GAME,
+ check ("THEMES" BETWEEN 0 AND 22)
+);
+
+create table GAME_VIDEO_URLS
+(
+ GAME_ID BIGINT not null,
+ VIDEO_URLS BINARY VARYING(255),
+ constraint FKJKKWO8WDS086AS7B2KSLSVKM6
+ foreign key (GAME_ID) references GAME
+);
+
+create table LIBRARY_DIRECTORIES
+(
+ LIBRARY_ID BIGINT not null,
+ DIRECTORIES_ID BIGINT not null
+ constraint UKB5UM4CADBNC6UC8DVOMO81N5F
+ unique,
+ constraint FKFNCKIU58I9L89MLXV388DY13B
+ foreign key (LIBRARY_ID) references LIBRARY,
+ constraint FKJDXS58Q1IRTU0IDP6DXJHWAPM
+ foreign key (DIRECTORIES_ID) references DIRECTORY_MAPPING
+);
+
+create table LIBRARY_GAMES
+(
+ LIBRARY_ID BIGINT not null,
+ GAMES_ID BIGINT not null
+ constraint UK3E4VB9NQXPY27VMTA27GU5FY8
+ unique,
+ constraint FK6C71EEDM0I2N1JXDE9BOBWG5M
+ foreign key (LIBRARY_ID) references LIBRARY,
+ constraint FKDKKKES3DAY0WJ1QMV42KMMFDK
+ foreign key (GAMES_ID) references GAME
+);
+
+create table LIBRARY_UNMATCHED_PATHS
+(
+ LIBRARY_ID BIGINT not null,
+ UNMATCHED_PATHS CHARACTER VARYING(255),
+ constraint FKSJ51WC2LBNNXY0LKLWELI6VSB
+ foreign key (LIBRARY_ID) references LIBRARY
+);
+
+create table PLUGIN_CONFIG
+(
+ "key" CHARACTER VARYING(255) not null,
+ PLUGIN_ID CHARACTER VARYING(255) not null,
+ "value" CHARACTER VARYING(255),
+ primary key ("key", PLUGIN_ID)
+);
+
+create table PLUGIN_MANAGEMENT_ENTRY
+(
+ PLUGIN_ID CHARACTER VARYING(255) not null
+ primary key,
+ ENABLED BOOLEAN not null,
+ PRIORITY INTEGER not null,
+ TRUST_LEVEL TINYINT,
+ check ("TRUST_LEVEL" BETWEEN 0 AND 4)
+);
+
+create table GAME_ORIGINAL_IDS
+(
+ GAME_ID BIGINT not null,
+ ORIGINAL_IDS CHARACTER VARYING(255),
+ ORIGINAL_IDS_KEY CHARACTER VARYING(255) not null,
+ primary key (GAME_ID, ORIGINAL_IDS_KEY),
+ constraint FK1CSD5QD7VJT7BTTA3G7HGYBUX
+ foreign key (GAME_ID) references GAME,
+ constraint FKMT0XWLPWPU9NP0Q289JBAHJRY
+ foreign key (ORIGINAL_IDS_KEY) references PLUGIN_MANAGEMENT_ENTRY
+);
+
+create table USERS
+(
+ ID BIGINT not null
+ primary key,
+ EMAIL CHARACTER VARYING(255)
+ constraint UK6DOTKOTT2KJSP8VW4D0M25FB7
+ unique,
+ EMAIL_CONFIRMED BOOLEAN not null,
+ ENABLED BOOLEAN not null,
+ OIDC_PROVIDER_ID CHARACTER VARYING(255),
+ PASSWORD CHARACTER VARYING(255),
+ USERNAME CHARACTER VARYING(255)
+ constraint UKR43AF9AP4EDM43MMTQ01ODDJ6
+ unique,
+ AVATAR_ID BIGINT
+ constraint UKRSULCN2GYNJY3CDDPWMOSV881
+ unique,
+ constraint FK19LFLPG5SEIS4DWRM2LVJLXFV
+ foreign key (AVATAR_ID) references IMAGE
+);
+
+create table GAME_FIELD_SOURCE
+(
+ DTYPE CHARACTER VARYING(31) not null,
+ ID BIGINT not null
+ primary key,
+ PLUGIN_PLUGIN_ID CHARACTER VARYING(255),
+ USER_ID BIGINT,
+ constraint FKNJC4QSS5APFHTPWP42OAEAL5G
+ foreign key (PLUGIN_PLUGIN_ID) references PLUGIN_MANAGEMENT_ENTRY,
+ constraint FKSR1BGTX5XJVMAL7FEFGL982TP
+ foreign key (USER_ID) references USERS
+);
+
+create table GAME_FIELD_METADATA
+(
+ ID BIGINT not null
+ primary key,
+ UPDATED_AT TIMESTAMP WITH TIME ZONE,
+ SOURCE_ID BIGINT
+ constraint UKHW6U2Y9FLWPTI57QB7K0P27BL
+ unique,
+ constraint FKQ4RC409TP8FUBTTM733PMJD8F
+ foreign key (SOURCE_ID) references GAME_FIELD_SOURCE
+);
+
+create table GAME_FIELDS
+(
+ GAME_ID BIGINT not null,
+ FIELDS_ID BIGINT not null
+ constraint UK1L5OAH0UOOUV4V5A9P0PAK77X
+ unique,
+ FIELDS_KEY CHARACTER VARYING(255) not null,
+ primary key (GAME_ID, FIELDS_KEY),
+ constraint FKLNEPI7YWCI86YH21KO9WD9PYF
+ foreign key (GAME_ID) references GAME,
+ constraint FKT8FLOFDAPX5M746S5LW54C5B3
+ foreign key (FIELDS_ID) references GAME_FIELD_METADATA
+);
+
+create table TOKEN
+(
+ SECRET CHARACTER VARYING(255) not null
+ primary key,
+ CREATED_ON TIMESTAMP WITH TIME ZONE,
+ PAYLOAD CHARACTER VARYING(255),
+ TYPE CHARACTER VARYING(255),
+ CREATOR_ID BIGINT,
+ constraint FKGHOIALAPTI5JFEJ506JBB1O8Y
+ foreign key (CREATOR_ID) references USERS
+ on delete cascade
+);
+
+create table USER_PREFERENCE
+(
+ "key" CHARACTER VARYING(255) not null,
+ USER_ID BIGINT not null,
+ "value" CHARACTER VARYING(255),
+ primary key ("key", USER_ID)
+);
+
+create table USER_ROLES
+(
+ USER_ID BIGINT not null,
+ ROLES ENUM ('ADMIN', 'SUPERADMIN', 'USER'),
+ constraint FKHFH9DX7W3UBF1CO1VDEV94G3F
+ foreign key (USER_ID) references USERS
+);
diff --git a/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql b/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
new file mode 100644
index 0000000..092f609
--- /dev/null
+++ b/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
@@ -0,0 +1,122 @@
+-- Flyway Migration: V2.1.0
+-- Purpose:
+-- 1. Drop unique constraints on GAME.COVER_IMAGE_ID and GAME.HEADER_IMAGE_ID
+-- 2. Rename all remaining UK*/FK* constraints to human-readable names
+
+/******************************************************************************************
+ * 1. Drop the two unwanted unique constraints on GAME
+ ******************************************************************************************/
+ALTER TABLE GAME
+ DROP CONSTRAINT UK52RQ62FLPBNTI77BYKM7UAHKQ; -- COVER_IMAGE_ID unique
+ALTER TABLE GAME
+ DROP CONSTRAINT UK30B16LLQV54H40XIOGP7T9P35;
+-- HEADER_IMAGE_ID unique
+
+/******************************************************************************************
+ * 2. Rename remaining UNIQUE constraints (UK*)
+ ******************************************************************************************/
+ALTER TABLE COMPANY
+ RENAME CONSTRAINT UK4UCNYHR8I0URHWDUDFAHKOB9E TO UQ_COMPANY_NAME_TYPE;
+ALTER TABLE DIRECTORY_MAPPING
+ RENAME CONSTRAINT UKJ3GSATFAHEWFOLSEAJ29O3KYT TO UQ_DIRECTORY_MAPPING_INTERNAL_PATH;
+ALTER TABLE GAME
+ RENAME CONSTRAINT UK4WXN9FPXFQ8QXPSB7FY0O3NOA TO UQ_GAME_PATH;
+ALTER TABLE GAME_IMAGES
+ RENAME CONSTRAINT UKBDE7M3TKHIEEYBINM2ED0B6X1 TO UQ_GAME_IMAGES_IMAGE_ID;
+ALTER TABLE LIBRARY_DIRECTORIES
+ RENAME CONSTRAINT UKB5UM4CADBNC6UC8DVOMO81N5F TO UQ_LIBRARY_DIRECTORIES_DIRECTORY_ID;
+ALTER TABLE LIBRARY_GAMES
+ RENAME CONSTRAINT UK3E4VB9NQXPY27VMTA27GU5FY8 TO UQ_LIBRARY_GAMES_GAME_ID;
+ALTER TABLE USERS
+ RENAME CONSTRAINT UK6DOTKOTT2KJSP8VW4D0M25FB7 TO UQ_USERS_EMAIL;
+ALTER TABLE USERS
+ RENAME CONSTRAINT UKR43AF9AP4EDM43MMTQ01ODDJ6 TO UQ_USERS_USERNAME;
+ALTER TABLE USERS
+ RENAME CONSTRAINT UKRSULCN2GYNJY3CDDPWMOSV881 TO UQ_USERS_AVATAR_ID;
+ALTER TABLE GAME_FIELD_METADATA
+ RENAME CONSTRAINT UKHW6U2Y9FLWPTI57QB7K0P27BL TO UQ_GAME_FIELD_METADATA_SOURCE_ID;
+ALTER TABLE GAME_FIELDS
+ RENAME CONSTRAINT UK1L5OAH0UOOUV4V5A9P0PAK77X TO UQ_GAME_FIELDS_FIELD_METADATA_ID;
+
+/******************************************************************************************
+ * 3. Rename FOREIGN KEY constraints (FK*)
+ ******************************************************************************************/
+ALTER TABLE GAME
+ RENAME CONSTRAINT FK6CVB43REAYSNYPI0XDY6HQTVF TO FK_GAME_COVER_IMAGE;
+ALTER TABLE GAME
+ RENAME CONSTRAINT FK8N86NDPGKMOO7YOLX6HL8N84G TO FK_GAME_HEADER_IMAGE;
+ALTER TABLE GAME
+ RENAME CONSTRAINT FKIUVR8XFB63T1K6T43EYYXVO2C TO FK_GAME_LIBRARY;
+
+ALTER TABLE GAME_DEVELOPERS
+ RENAME CONSTRAINT FKB12PO9L2B9OJBAIHC82MM2QXB TO FK_GAME_DEVELOPERS_COMPANY;
+ALTER TABLE GAME_DEVELOPERS
+ RENAME CONSTRAINT FKS4IJSVPIJ53DSL143XVRGBS09 TO FK_GAME_DEVELOPERS_GAME;
+
+ALTER TABLE GAME_FEATURES
+ RENAME CONSTRAINT FK63XLTCT60SCIMPM06K8BHBE4A TO FK_GAME_FEATURES_GAME;
+ALTER TABLE GAME_GENRES
+ RENAME CONSTRAINT FKDTSX09YOPD98E0LUEWRUSJD9E TO FK_GAME_GENRES_GAME;
+
+ALTER TABLE GAME_IMAGES
+ RENAME CONSTRAINT FK5YWV1DMXCM2VSQUEB7RHQ3JK9 TO FK_GAME_IMAGES_IMAGE;
+ALTER TABLE GAME_IMAGES
+ RENAME CONSTRAINT FKOWCPUCV45OX8GT28TXGVHF1AA TO FK_GAME_IMAGES_GAME;
+
+ALTER TABLE GAME_KEYWORDS
+ RENAME CONSTRAINT FKMVF6HNJ7ROMQQM2EX70A9NVAC TO FK_GAME_KEYWORDS_GAME;
+ALTER TABLE GAME_PERSPECTIVES
+ RENAME CONSTRAINT FKHUEENG29Y1GHBRDI5QHGUXH6E TO FK_GAME_PERSPECTIVES_GAME;
+
+ALTER TABLE GAME_PUBLISHERS
+ RENAME CONSTRAINT FK49R2KB61LIJ54BQB4VNTST97N TO FK_GAME_PUBLISHERS_GAME;
+ALTER TABLE GAME_PUBLISHERS
+ RENAME CONSTRAINT FKNGLD5ESGRBRH95J5BJF0HEF85 TO FK_GAME_PUBLISHERS_COMPANY;
+
+ALTER TABLE GAME_THEMES
+ RENAME CONSTRAINT FKRV351JXLIOY0A17Y5BBJJ6FW4 TO FK_GAME_THEMES_GAME;
+ALTER TABLE GAME_VIDEO_URLS
+ RENAME CONSTRAINT FKJKKWO8WDS086AS7B2KSLSVKM6 TO FK_GAME_VIDEO_URLS_GAME;
+
+ALTER TABLE LIBRARY_DIRECTORIES
+ RENAME CONSTRAINT FKFNCKIU58I9L89MLXV388DY13B TO FK_LIBRARY_DIRECTORIES_LIBRARY;
+ALTER TABLE LIBRARY_DIRECTORIES
+ RENAME CONSTRAINT FKJDXS58Q1IRTU0IDP6DXJHWAPM TO FK_LIBRARY_DIRECTORIES_DIRECTORY_MAPPING;
+
+ALTER TABLE LIBRARY_GAMES
+ RENAME CONSTRAINT FK6C71EEDM0I2N1JXDE9BOBWG5M TO FK_LIBRARY_GAMES_LIBRARY;
+ALTER TABLE LIBRARY_GAMES
+ RENAME CONSTRAINT FKDKKKES3DAY0WJ1QMV42KMMFDK TO FK_LIBRARY_GAMES_GAME;
+
+ALTER TABLE LIBRARY_UNMATCHED_PATHS
+ RENAME CONSTRAINT FKSJ51WC2LBNNXY0LKLWELI6VSB TO FK_LIBRARY_UNMATCHED_PATHS_LIBRARY;
+
+ALTER TABLE GAME_ORIGINAL_IDS
+ RENAME CONSTRAINT FK1CSD5QD7VJT7BTTA3G7HGYBUX TO FK_GAME_ORIGINAL_IDS_GAME;
+ALTER TABLE GAME_ORIGINAL_IDS
+ RENAME CONSTRAINT FKMT0XWLPWPU9NP0Q289JBAHJRY TO FK_GAME_ORIGINAL_IDS_PLUGIN;
+
+ALTER TABLE USERS
+ RENAME CONSTRAINT FK19LFLPG5SEIS4DWRM2LVJLXFV TO FK_USERS_AVATAR_IMAGE;
+
+ALTER TABLE GAME_FIELD_SOURCE
+ RENAME CONSTRAINT FKNJC4QSS5APFHTPWP42OAEAL5G TO FK_GAME_FIELD_SOURCE_PLUGIN;
+ALTER TABLE GAME_FIELD_SOURCE
+ RENAME CONSTRAINT FKSR1BGTX5XJVMAL7FEFGL982TP TO FK_GAME_FIELD_SOURCE_USER;
+
+ALTER TABLE GAME_FIELD_METADATA
+ RENAME CONSTRAINT FKQ4RC409TP8FUBTTM733PMJD8F TO FK_GAME_FIELD_METADATA_SOURCE;
+
+ALTER TABLE GAME_FIELDS
+ RENAME CONSTRAINT FKLNEPI7YWCI86YH21KO9WD9PYF TO FK_GAME_FIELDS_GAME;
+ALTER TABLE GAME_FIELDS
+ RENAME CONSTRAINT FKT8FLOFDAPX5M746S5LW54C5B3 TO FK_GAME_FIELDS_METADATA;
+
+ALTER TABLE TOKEN
+ RENAME CONSTRAINT FKGHOIALAPTI5JFEJ506JBB1O8Y TO FK_TOKEN_CREATOR_USER;
+
+ALTER TABLE USER_ROLES
+ RENAME CONSTRAINT FKHFH9DX7W3UBF1CO1VDEV94G3F TO FK_USER_ROLES_USER;
+
+-- End of migration
+
From ec55db54af9237cc3294967ae7e5c6434fc34ca6 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 15:19:56 +0200
Subject: [PATCH 29/32] Fix: Only drop constraints if they exist
---
...2.1.0__Drop_game_image_uniques_and_rename_constraints.sql | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql b/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
index 092f609..9710062 100644
--- a/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
+++ b/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
@@ -7,9 +7,9 @@
* 1. Drop the two unwanted unique constraints on GAME
******************************************************************************************/
ALTER TABLE GAME
- DROP CONSTRAINT UK52RQ62FLPBNTI77BYKM7UAHKQ; -- COVER_IMAGE_ID unique
+ DROP CONSTRAINT IF EXISTS UK52RQ62FLPBNTI77BYKM7UAHKQ; -- COVER_IMAGE_ID unique
ALTER TABLE GAME
- DROP CONSTRAINT UK30B16LLQV54H40XIOGP7T9P35;
+ DROP CONSTRAINT IF EXISTS UK30B16LLQV54H40XIOGP7T9P35;
-- HEADER_IMAGE_ID unique
/******************************************************************************************
@@ -119,4 +119,3 @@ ALTER TABLE USER_ROLES
RENAME CONSTRAINT FKHFH9DX7W3UBF1CO1VDEV94G3F TO FK_USER_ROLES_USER;
-- End of migration
-
From 50868320ab22fab73a30da8474b11a8f37ab32b2 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 15:58:24 +0200
Subject: [PATCH 30/32] Add missing migrations
---
.../V2.0.0__Initial_schema_creation.sql | 410 +++++++++---------
..._image_uniques_and_rename_constraints.sql} | 2 +-
.../V2.1.0.2__Create_game_requests_table.sql | 42 ++
3 files changed, 253 insertions(+), 201 deletions(-)
rename app/src/main/resources/db/migration/{V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql => V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql} (99%)
create mode 100644 app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql
diff --git a/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql b/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql
index b9c7c17..ca1bfcf 100644
--- a/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql
+++ b/app/src/main/resources/db/migration/V2.0.0__Initial_schema_creation.sql
@@ -4,346 +4,356 @@
/******************************************************************************************
* 1. Sequences (hi/lo allocation size = 50 for performance)
******************************************************************************************/
-create sequence COMPANY_SEQ
- increment by 50;
+CREATE SEQUENCE COMPANY_SEQ
+ INCREMENT BY 50;
-create sequence DIRECTORY_MAPPING_SEQ
- increment by 50;
+CREATE SEQUENCE DIRECTORY_MAPPING_SEQ
+ INCREMENT BY 50;
-create sequence GAME_FIELD_METADATA_SEQ
- increment by 50;
+CREATE SEQUENCE GAME_FIELD_METADATA_SEQ
+ INCREMENT BY 50;
-create sequence GAME_FIELD_SOURCE_SEQ
- increment by 50;
+CREATE SEQUENCE GAME_FIELD_SOURCE_SEQ
+ INCREMENT BY 50;
-create sequence GAME_SEQ
- increment by 50;
+CREATE SEQUENCE GAME_SEQ
+ INCREMENT BY 50;
-create sequence IMAGE_SEQ
- increment by 50;
+CREATE SEQUENCE IMAGE_SEQ
+ INCREMENT BY 50;
-create sequence LIBRARY_SEQ
- increment by 50;
+CREATE SEQUENCE LIBRARY_SEQ
+ INCREMENT BY 50;
-create sequence USERS_SEQ
- increment by 50;
+CREATE SEQUENCE USERS_SEQ
+ INCREMENT BY 50;
/******************************************************************************************
* 2. Tables
******************************************************************************************/
-create table APP_CONFIG
+CREATE TABLE APP_CONFIG
(
- "key" CHARACTER VARYING(255) not null
- primary key,
+ "key" CHARACTER VARYING(255) NOT NULL
+ PRIMARY KEY,
"value" CHARACTER VARYING(255)
);
-create table COMPANY
+CREATE TABLE COMPANY
(
- ID BIGINT not null
- primary key,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
NAME CHARACTER VARYING(255),
TYPE TINYINT,
- constraint UK4UCNYHR8I0URHWDUDFAHKOB9E
- unique (NAME, TYPE),
- check ("TYPE" BETWEEN 0 AND 1)
+ CONSTRAINT UK4UCNYHR8I0URHWDUDFAHKOB9E
+ UNIQUE (NAME, TYPE),
+ CHECK ("TYPE" BETWEEN 0 AND 1)
);
-create table DIRECTORY_MAPPING
+CREATE TABLE DIRECTORY_MAPPING
(
- ID BIGINT not null
- primary key,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
EXTERNAL_PATH CHARACTER VARYING(255),
INTERNAL_PATH CHARACTER VARYING(255)
- constraint UKJ3GSATFAHEWFOLSEAJ29O3KYT
- unique
+ CONSTRAINT UKJ3GSATFAHEWFOLSEAJ29O3KYT
+ UNIQUE
);
-create table IMAGE
+CREATE TABLE IMAGE
(
- ID BIGINT not null
- primary key,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
CONTENT_ID CHARACTER VARYING(255),
CONTENT_LENGTH BIGINT,
MIME_TYPE CHARACTER VARYING(255),
ORIGINAL_URL CHARACTER VARYING(255),
TYPE TINYINT,
- check ("TYPE" BETWEEN 0 AND 3)
+ CHECK ("TYPE" BETWEEN 0 AND 3)
);
-create table LIBRARY
+CREATE TABLE LIBRARY
(
- ID BIGINT not null
- primary key,
- CREATED_AT TIMESTAMP WITH TIME ZONE not null,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
+ CREATED_AT TIMESTAMP WITH TIME ZONE NOT NULL,
NAME CHARACTER VARYING(255),
- UPDATED_AT TIMESTAMP WITH TIME ZONE not null
+ UPDATED_AT TIMESTAMP WITH TIME ZONE NOT NULL
);
-create table GAME
+CREATE TABLE GAME
(
- ID BIGINT not null
- primary key,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
COMMENT CHARACTER LARGE OBJECT,
- CREATED_AT TIMESTAMP WITH TIME ZONE not null,
+ CREATED_AT TIMESTAMP WITH TIME ZONE NOT NULL,
CRITIC_RATING INTEGER,
DOWNLOAD_COUNT INTEGER,
FILE_SIZE BIGINT,
MATCH_CONFIRMED BOOLEAN,
PATH CHARACTER VARYING(255)
- constraint UK4WXN9FPXFQ8QXPSB7FY0O3NOA
- unique,
+ CONSTRAINT UK4WXN9FPXFQ8QXPSB7FY0O3NOA
+ UNIQUE,
RELEASE TIMESTAMP WITH TIME ZONE,
SUMMARY CHARACTER LARGE OBJECT,
TITLE CHARACTER VARYING(255),
- UPDATED_AT TIMESTAMP WITH TIME ZONE not null,
+ UPDATED_AT TIMESTAMP WITH TIME ZONE NOT NULL,
USER_RATING INTEGER,
COVER_IMAGE_ID BIGINT
- constraint UK52RQ62FLPBNTI77BYKM7UAHKQ
- unique,
+ CONSTRAINT UK52RQ62FLPBNTI77BYKM7UAHKQ
+ UNIQUE,
HEADER_IMAGE_ID BIGINT
- constraint UK30B16LLQV54H40XIOGP7T9P35
- unique,
+ CONSTRAINT UK30B16LLQV54H40XIOGP7T9P35
+ UNIQUE,
LIBRARY_ID BIGINT,
- constraint FK6CVB43REAYSNYPI0XDY6HQTVF
- foreign key (COVER_IMAGE_ID) references IMAGE,
- constraint FK8N86NDPGKMOO7YOLX6HL8N84G
- foreign key (HEADER_IMAGE_ID) references IMAGE,
- constraint FKIUVR8XFB63T1K6T43EYYXVO2C
- foreign key (LIBRARY_ID) references LIBRARY
+ CONSTRAINT FK6CVB43REAYSNYPI0XDY6HQTVF
+ FOREIGN KEY (COVER_IMAGE_ID) REFERENCES IMAGE,
+ CONSTRAINT FK8N86NDPGKMOO7YOLX6HL8N84G
+ FOREIGN KEY (HEADER_IMAGE_ID) REFERENCES IMAGE,
+ CONSTRAINT FKIUVR8XFB63T1K6T43EYYXVO2C
+ FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY
);
-create table GAME_DEVELOPERS
+CREATE TABLE GAME_DEVELOPERS
(
- GAME_ID BIGINT not null,
- DEVELOPERS_ID BIGINT not null,
- constraint FKB12PO9L2B9OJBAIHC82MM2QXB
- foreign key (DEVELOPERS_ID) references COMPANY,
- constraint FKS4IJSVPIJ53DSL143XVRGBS09
- foreign key (GAME_ID) references GAME
+ GAME_ID BIGINT NOT NULL,
+ DEVELOPERS_ID BIGINT NOT NULL,
+ CONSTRAINT FKB12PO9L2B9OJBAIHC82MM2QXB
+ FOREIGN KEY (DEVELOPERS_ID) REFERENCES COMPANY,
+ CONSTRAINT FKS4IJSVPIJ53DSL143XVRGBS09
+ FOREIGN KEY (GAME_ID) REFERENCES GAME
);
-create table GAME_FEATURES
+CREATE TABLE GAME_FEATURES
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
FEATURES TINYINT,
- constraint FK63XLTCT60SCIMPM06K8BHBE4A
- foreign key (GAME_ID) references GAME,
- check ("FEATURES" BETWEEN 0 AND 23)
+ CONSTRAINT FK63XLTCT60SCIMPM06K8BHBE4A
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CHECK ("FEATURES" BETWEEN 0 AND 23)
);
-create table GAME_GENRES
+CREATE TABLE GAME_GENRES
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
GENRES TINYINT,
- constraint FKDTSX09YOPD98E0LUEWRUSJD9E
- foreign key (GAME_ID) references GAME,
- check ("GENRES" BETWEEN 0 AND 25)
+ CONSTRAINT FKDTSX09YOPD98E0LUEWRUSJD9E
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CHECK ("GENRES" BETWEEN 0 AND 25)
);
-create table GAME_IMAGES
+CREATE TABLE GAME_IMAGES
(
- GAME_ID BIGINT not null,
- IMAGES_ID BIGINT not null
- constraint UKBDE7M3TKHIEEYBINM2ED0B6X1
- unique,
- constraint FK5YWV1DMXCM2VSQUEB7RHQ3JK9
- foreign key (IMAGES_ID) references IMAGE,
- constraint FKOWCPUCV45OX8GT28TXGVHF1AA
- foreign key (GAME_ID) references GAME
+ GAME_ID BIGINT NOT NULL,
+ IMAGES_ID BIGINT NOT NULL
+ CONSTRAINT UKBDE7M3TKHIEEYBINM2ED0B6X1
+ UNIQUE,
+ CONSTRAINT FK5YWV1DMXCM2VSQUEB7RHQ3JK9
+ FOREIGN KEY (IMAGES_ID) REFERENCES IMAGE,
+ CONSTRAINT FKOWCPUCV45OX8GT28TXGVHF1AA
+ FOREIGN KEY (GAME_ID) REFERENCES GAME
);
-create table GAME_KEYWORDS
+CREATE TABLE GAME_KEYWORDS
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
KEYWORDS CHARACTER VARYING(255),
- constraint FKMVF6HNJ7ROMQQM2EX70A9NVAC
- foreign key (GAME_ID) references GAME
+ CONSTRAINT FKMVF6HNJ7ROMQQM2EX70A9NVAC
+ FOREIGN KEY (GAME_ID) REFERENCES GAME
);
-create table GAME_PERSPECTIVES
+CREATE TABLE GAME_PERSPECTIVES
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
PERSPECTIVES TINYINT,
- constraint FKHUEENG29Y1GHBRDI5QHGUXH6E
- foreign key (GAME_ID) references GAME,
- check ("PERSPECTIVES" BETWEEN 0 AND 7)
+ CONSTRAINT FKHUEENG29Y1GHBRDI5QHGUXH6E
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CHECK ("PERSPECTIVES" BETWEEN 0 AND 7)
);
-create table GAME_PUBLISHERS
+CREATE TABLE GAME_PUBLISHERS
(
- GAME_ID BIGINT not null,
- PUBLISHERS_ID BIGINT not null,
- constraint FK49R2KB61LIJ54BQB4VNTST97N
- foreign key (GAME_ID) references GAME,
- constraint FKNGLD5ESGRBRH95J5BJF0HEF85
- foreign key (PUBLISHERS_ID) references COMPANY
+ GAME_ID BIGINT NOT NULL,
+ PUBLISHERS_ID BIGINT NOT NULL,
+ CONSTRAINT FK49R2KB61LIJ54BQB4VNTST97N
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CONSTRAINT FKNGLD5ESGRBRH95J5BJF0HEF85
+ FOREIGN KEY (PUBLISHERS_ID) REFERENCES COMPANY
);
-create table GAME_THEMES
+CREATE TABLE GAME_THEMES
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
THEMES TINYINT,
- constraint FKRV351JXLIOY0A17Y5BBJJ6FW4
- foreign key (GAME_ID) references GAME,
- check ("THEMES" BETWEEN 0 AND 22)
+ CONSTRAINT FKRV351JXLIOY0A17Y5BBJJ6FW4
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CHECK ("THEMES" BETWEEN 0 AND 22)
);
-create table GAME_VIDEO_URLS
+CREATE TABLE GAME_VIDEO_URLS
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
VIDEO_URLS BINARY VARYING(255),
- constraint FKJKKWO8WDS086AS7B2KSLSVKM6
- foreign key (GAME_ID) references GAME
+ CONSTRAINT FKJKKWO8WDS086AS7B2KSLSVKM6
+ FOREIGN KEY (GAME_ID) REFERENCES GAME
);
-create table LIBRARY_DIRECTORIES
+CREATE TABLE LIBRARY_DIRECTORIES
(
- LIBRARY_ID BIGINT not null,
- DIRECTORIES_ID BIGINT not null
- constraint UKB5UM4CADBNC6UC8DVOMO81N5F
- unique,
- constraint FKFNCKIU58I9L89MLXV388DY13B
- foreign key (LIBRARY_ID) references LIBRARY,
- constraint FKJDXS58Q1IRTU0IDP6DXJHWAPM
- foreign key (DIRECTORIES_ID) references DIRECTORY_MAPPING
+ LIBRARY_ID BIGINT NOT NULL,
+ DIRECTORIES_ID BIGINT NOT NULL
+ CONSTRAINT UKB5UM4CADBNC6UC8DVOMO81N5F
+ UNIQUE,
+ CONSTRAINT FKFNCKIU58I9L89MLXV388DY13B
+ FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY,
+ CONSTRAINT FKJDXS58Q1IRTU0IDP6DXJHWAPM
+ FOREIGN KEY (DIRECTORIES_ID) REFERENCES DIRECTORY_MAPPING
);
-create table LIBRARY_GAMES
+CREATE TABLE LIBRARY_GAMES
(
- LIBRARY_ID BIGINT not null,
- GAMES_ID BIGINT not null
- constraint UK3E4VB9NQXPY27VMTA27GU5FY8
- unique,
- constraint FK6C71EEDM0I2N1JXDE9BOBWG5M
- foreign key (LIBRARY_ID) references LIBRARY,
- constraint FKDKKKES3DAY0WJ1QMV42KMMFDK
- foreign key (GAMES_ID) references GAME
+ LIBRARY_ID BIGINT NOT NULL,
+ GAMES_ID BIGINT NOT NULL
+ CONSTRAINT UK3E4VB9NQXPY27VMTA27GU5FY8
+ UNIQUE,
+ CONSTRAINT FK6C71EEDM0I2N1JXDE9BOBWG5M
+ FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY,
+ CONSTRAINT FKDKKKES3DAY0WJ1QMV42KMMFDK
+ FOREIGN KEY (GAMES_ID) REFERENCES GAME
);
-create table LIBRARY_UNMATCHED_PATHS
+CREATE TABLE LIBRARY_UNMATCHED_PATHS
(
- LIBRARY_ID BIGINT not null,
+ LIBRARY_ID BIGINT NOT NULL,
UNMATCHED_PATHS CHARACTER VARYING(255),
- constraint FKSJ51WC2LBNNXY0LKLWELI6VSB
- foreign key (LIBRARY_ID) references LIBRARY
+ CONSTRAINT FKSJ51WC2LBNNXY0LKLWELI6VSB
+ FOREIGN KEY (LIBRARY_ID) REFERENCES LIBRARY
);
-create table PLUGIN_CONFIG
+CREATE TABLE PLUGIN_CONFIG
(
- "key" CHARACTER VARYING(255) not null,
- PLUGIN_ID CHARACTER VARYING(255) not null,
+ "key" CHARACTER VARYING(255) NOT NULL,
+ PLUGIN_ID CHARACTER VARYING(255) NOT NULL,
"value" CHARACTER VARYING(255),
- primary key ("key", PLUGIN_ID)
+ PRIMARY KEY ("key", PLUGIN_ID)
);
-create table PLUGIN_MANAGEMENT_ENTRY
+CREATE TABLE PLUGIN_MANAGEMENT_ENTRY
(
- PLUGIN_ID CHARACTER VARYING(255) not null
- primary key,
- ENABLED BOOLEAN not null,
- PRIORITY INTEGER not null,
+ PLUGIN_ID CHARACTER VARYING(255) NOT NULL
+ PRIMARY KEY,
+ ENABLED BOOLEAN NOT NULL,
+ PRIORITY INTEGER NOT NULL,
TRUST_LEVEL TINYINT,
- check ("TRUST_LEVEL" BETWEEN 0 AND 4)
+ CHECK ("TRUST_LEVEL" BETWEEN 0 AND 4)
);
-create table GAME_ORIGINAL_IDS
+CREATE TABLE GAME_ORIGINAL_IDS
(
- GAME_ID BIGINT not null,
+ GAME_ID BIGINT NOT NULL,
ORIGINAL_IDS CHARACTER VARYING(255),
- ORIGINAL_IDS_KEY CHARACTER VARYING(255) not null,
- primary key (GAME_ID, ORIGINAL_IDS_KEY),
- constraint FK1CSD5QD7VJT7BTTA3G7HGYBUX
- foreign key (GAME_ID) references GAME,
- constraint FKMT0XWLPWPU9NP0Q289JBAHJRY
- foreign key (ORIGINAL_IDS_KEY) references PLUGIN_MANAGEMENT_ENTRY
+ ORIGINAL_IDS_KEY CHARACTER VARYING(255) NOT NULL,
+ PRIMARY KEY (GAME_ID, ORIGINAL_IDS_KEY),
+ CONSTRAINT FK1CSD5QD7VJT7BTTA3G7HGYBUX
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CONSTRAINT FKMT0XWLPWPU9NP0Q289JBAHJRY
+ FOREIGN KEY (ORIGINAL_IDS_KEY) REFERENCES PLUGIN_MANAGEMENT_ENTRY
);
-create table USERS
+CREATE TABLE USERS
(
- ID BIGINT not null
- primary key,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
EMAIL CHARACTER VARYING(255)
- constraint UK6DOTKOTT2KJSP8VW4D0M25FB7
- unique,
- EMAIL_CONFIRMED BOOLEAN not null,
- ENABLED BOOLEAN not null,
+ CONSTRAINT UK6DOTKOTT2KJSP8VW4D0M25FB7
+ UNIQUE,
+ EMAIL_CONFIRMED BOOLEAN NOT NULL,
+ ENABLED BOOLEAN NOT NULL,
OIDC_PROVIDER_ID CHARACTER VARYING(255),
PASSWORD CHARACTER VARYING(255),
USERNAME CHARACTER VARYING(255)
- constraint UKR43AF9AP4EDM43MMTQ01ODDJ6
- unique,
+ CONSTRAINT UKR43AF9AP4EDM43MMTQ01ODDJ6
+ UNIQUE,
AVATAR_ID BIGINT
- constraint UKRSULCN2GYNJY3CDDPWMOSV881
- unique,
- constraint FK19LFLPG5SEIS4DWRM2LVJLXFV
- foreign key (AVATAR_ID) references IMAGE
+ CONSTRAINT UKRSULCN2GYNJY3CDDPWMOSV881
+ UNIQUE,
+ CONSTRAINT FK19LFLPG5SEIS4DWRM2LVJLXFV
+ FOREIGN KEY (AVATAR_ID) REFERENCES IMAGE
);
-create table GAME_FIELD_SOURCE
+CREATE TABLE GAME_FIELD_SOURCE
(
- DTYPE CHARACTER VARYING(31) not null,
- ID BIGINT not null
- primary key,
+ DTYPE CHARACTER VARYING(31) NOT NULL,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
PLUGIN_PLUGIN_ID CHARACTER VARYING(255),
USER_ID BIGINT,
- constraint FKNJC4QSS5APFHTPWP42OAEAL5G
- foreign key (PLUGIN_PLUGIN_ID) references PLUGIN_MANAGEMENT_ENTRY,
- constraint FKSR1BGTX5XJVMAL7FEFGL982TP
- foreign key (USER_ID) references USERS
+ CONSTRAINT FKNJC4QSS5APFHTPWP42OAEAL5G
+ FOREIGN KEY (PLUGIN_PLUGIN_ID) REFERENCES PLUGIN_MANAGEMENT_ENTRY,
+ CONSTRAINT FKSR1BGTX5XJVMAL7FEFGL982TP
+ FOREIGN KEY (USER_ID) REFERENCES USERS
);
-create table GAME_FIELD_METADATA
+CREATE TABLE GAME_FIELD_METADATA
(
- ID BIGINT not null
- primary key,
+ ID BIGINT NOT NULL
+ PRIMARY KEY,
UPDATED_AT TIMESTAMP WITH TIME ZONE,
SOURCE_ID BIGINT
- constraint UKHW6U2Y9FLWPTI57QB7K0P27BL
- unique,
- constraint FKQ4RC409TP8FUBTTM733PMJD8F
- foreign key (SOURCE_ID) references GAME_FIELD_SOURCE
+ CONSTRAINT UKHW6U2Y9FLWPTI57QB7K0P27BL
+ UNIQUE,
+ CONSTRAINT FKQ4RC409TP8FUBTTM733PMJD8F
+ FOREIGN KEY (SOURCE_ID) REFERENCES GAME_FIELD_SOURCE
);
-create table GAME_FIELDS
+CREATE TABLE GAME_FIELDS
(
- GAME_ID BIGINT not null,
- FIELDS_ID BIGINT not null
- constraint UK1L5OAH0UOOUV4V5A9P0PAK77X
- unique,
- FIELDS_KEY CHARACTER VARYING(255) not null,
- primary key (GAME_ID, FIELDS_KEY),
- constraint FKLNEPI7YWCI86YH21KO9WD9PYF
- foreign key (GAME_ID) references GAME,
- constraint FKT8FLOFDAPX5M746S5LW54C5B3
- foreign key (FIELDS_ID) references GAME_FIELD_METADATA
+ GAME_ID BIGINT NOT NULL,
+ FIELDS_ID BIGINT NOT NULL
+ CONSTRAINT UK1L5OAH0UOOUV4V5A9P0PAK77X
+ UNIQUE,
+ FIELDS_KEY CHARACTER VARYING(255) NOT NULL,
+ PRIMARY KEY (GAME_ID, FIELDS_KEY),
+ CONSTRAINT FKLNEPI7YWCI86YH21KO9WD9PYF
+ FOREIGN KEY (GAME_ID) REFERENCES GAME,
+ CONSTRAINT FKT8FLOFDAPX5M746S5LW54C5B3
+ FOREIGN KEY (FIELDS_ID) REFERENCES GAME_FIELD_METADATA
);
-create table TOKEN
+CREATE TABLE TOKEN
(
- SECRET CHARACTER VARYING(255) not null
- primary key,
+ SECRET CHARACTER VARYING(255) NOT NULL
+ PRIMARY KEY,
CREATED_ON TIMESTAMP WITH TIME ZONE,
PAYLOAD CHARACTER VARYING(255),
TYPE CHARACTER VARYING(255),
CREATOR_ID BIGINT,
- constraint FKGHOIALAPTI5JFEJ506JBB1O8Y
- foreign key (CREATOR_ID) references USERS
- on delete cascade
+ CONSTRAINT FKGHOIALAPTI5JFEJ506JBB1O8Y
+ FOREIGN KEY (CREATOR_ID) REFERENCES USERS
+ ON DELETE CASCADE
);
-create table USER_PREFERENCE
+CREATE TABLE USER_PREFERENCE
(
- "key" CHARACTER VARYING(255) not null,
- USER_ID BIGINT not null,
+ "key" CHARACTER VARYING(255) NOT NULL,
+ USER_ID BIGINT NOT NULL,
"value" CHARACTER VARYING(255),
- primary key ("key", USER_ID)
+ PRIMARY KEY ("key", USER_ID)
);
-create table USER_ROLES
+CREATE TABLE USER_ROLES
(
- USER_ID BIGINT not null,
+ USER_ID BIGINT NOT NULL,
ROLES ENUM ('ADMIN', 'SUPERADMIN', 'USER'),
- constraint FKHFH9DX7W3UBF1CO1VDEV94G3F
- foreign key (USER_ID) references USERS
+ CONSTRAINT FKHFH9DX7W3UBF1CO1VDEV94G3F
+ FOREIGN KEY (USER_ID) REFERENCES USERS
);
+
+CREATE TABLE JOB_RUN_RESULT
+(
+ ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY,
+ JOB_NAME VARCHAR(255),
+ STARTED_AT TIMESTAMP,
+ FINISHED_AT TIMESTAMP,
+ STATUS VARCHAR(255),
+ MESSAGE VARCHAR(255)
+);
\ No newline at end of file
diff --git a/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql b/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
similarity index 99%
rename from app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
rename to app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
index 9710062..910e969 100644
--- a/app/src/main/resources/db/migration/V2.1.0__Drop_game_image_uniques_and_rename_constraints.sql
+++ b/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
@@ -1,4 +1,4 @@
--- Flyway Migration: V2.1.0
+-- Flyway Migration: V2.1.0 (Part 1)
-- Purpose:
-- 1. Drop unique constraints on GAME.COVER_IMAGE_ID and GAME.HEADER_IMAGE_ID
-- 2. Rename all remaining UK*/FK* constraints to human-readable names
diff --git a/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql b/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql
new file mode 100644
index 0000000..970cb67
--- /dev/null
+++ b/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql
@@ -0,0 +1,42 @@
+-- Flyway Migration: V2.1.0 (Part 2)
+-- Purpose:
+-- 1. Create tables for the game requests feature
+
+/******************************************************************************************
+ * 1. Create new sequence
+ ******************************************************************************************/
+CREATE SEQUENCE GAME_REQUEST_SEQ
+ INCREMENT BY 50;
+
+/******************************************************************************************
+ * 2. Create new tables
+ ******************************************************************************************/
+CREATE TABLE GAME_REQUEST
+(
+ ID BIGINT NOT NULL PRIMARY KEY,
+ TITLE VARCHAR(255) NOT NULL,
+ RELEASE TIMESTAMP NOT NULL,
+ STATUS VARCHAR(255) NOT NULL,
+ REQUESTER_ID BIGINT,
+ LINKED_GAME_ID BIGINT,
+ CREATED_AT TIMESTAMP NOT NULL,
+ UPDATED_AT TIMESTAMP NOT NULL
+);
+
+CREATE TABLE GAME_REQUEST_VOTERS
+(
+ GAME_REQUEST_ID BIGINT NOT NULL,
+ VOTERS_ID BIGINT NOT NULL,
+ PRIMARY KEY (GAME_REQUEST_ID, VOTERS_ID)
+);
+
+ALTER TABLE GAME_REQUEST
+ ADD CONSTRAINT FK_GAMEREQUEST_ON_REQUESTER FOREIGN KEY (REQUESTER_ID) REFERENCES USERS (ID) ON DELETE SET NULL;
+
+ALTER TABLE GAME_REQUEST_VOTERS
+ ADD CONSTRAINT FK_GAMREQVOT_ON_GAME_REQUEST FOREIGN KEY (GAME_REQUEST_ID) REFERENCES GAME_REQUEST (ID) ON DELETE CASCADE;
+
+ALTER TABLE GAME_REQUEST_VOTERS
+ ADD CONSTRAINT FK_GAMREQVOT_ON_USER FOREIGN KEY (VOTERS_ID) REFERENCES USERS (ID);
+
+-- End of migration
From 8c2cedefe40d089aaa5001515656629c440359ea Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 17:05:36 +0200
Subject: [PATCH 31/32] Refactor migrations to account for 2.1-preview users
---
...e_image_uniques_and_rename_constraints.sql | 160 ++++++++----------
.../V2.1.0.2__Create_game_requests_table.sql | 27 ++-
2 files changed, 88 insertions(+), 99 deletions(-)
diff --git a/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql b/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
index 910e969..f1e776b 100644
--- a/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
+++ b/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
@@ -1,7 +1,32 @@
-- Flyway Migration: V2.1.0 (Part 1)
-- Purpose:
-- 1. Drop unique constraints on GAME.COVER_IMAGE_ID and GAME.HEADER_IMAGE_ID
--- 2. Rename all remaining UK*/FK* constraints to human-readable names
+-- 2. Rename all remaining UK*/FK* constraints to human-readable names (idempotent)
+-- This version has been updated to be resilient: if a hashed constraint name
+-- does not exist (e.g. on legacy schemas where Hibernate never created it, or
+-- on future fresh installs where the friendly name may already be present),
+-- the rename attempt is ignored instead of failing the migration.
+
+/******************************************************************************************
+ * Helper: Idempotent constraint rename for H2
+ * H2 does not support RENAME CONSTRAINT IF EXISTS, so we create a Java alias that
+ * executes a rename and suppresses error 90057 (constraint not found).
+ * NOTE: Do not declare method as public static; H2 wraps it and adds modifiers itself.
+ ******************************************************************************************/
+CREATE ALIAS IF NOT EXISTS RENAME_CONSTRAINT_IF_EXISTS AS
+$$
+import java.sql.*;
+@CODE
+void run(Connection conn, String table, String oldName, String newName) throws SQLException {
+ try (Statement st = conn.createStatement()) {
+ st.execute("ALTER TABLE " + table + " RENAME CONSTRAINT " + oldName + " TO " + newName);
+ } catch (SQLException e) {
+ if (e.getErrorCode() != 90057) { // ignore only 'constraint not found'
+ throw e;
+ }
+ }
+}
+$$;
/******************************************************************************************
* 1. Drop the two unwanted unique constraints on GAME
@@ -13,109 +38,74 @@ ALTER TABLE GAME
-- HEADER_IMAGE_ID unique
/******************************************************************************************
- * 2. Rename remaining UNIQUE constraints (UK*)
+ * 2. Rename remaining UNIQUE constraints (UK*) (tolerant)
******************************************************************************************/
-ALTER TABLE COMPANY
- RENAME CONSTRAINT UK4UCNYHR8I0URHWDUDFAHKOB9E TO UQ_COMPANY_NAME_TYPE;
-ALTER TABLE DIRECTORY_MAPPING
- RENAME CONSTRAINT UKJ3GSATFAHEWFOLSEAJ29O3KYT TO UQ_DIRECTORY_MAPPING_INTERNAL_PATH;
-ALTER TABLE GAME
- RENAME CONSTRAINT UK4WXN9FPXFQ8QXPSB7FY0O3NOA TO UQ_GAME_PATH;
-ALTER TABLE GAME_IMAGES
- RENAME CONSTRAINT UKBDE7M3TKHIEEYBINM2ED0B6X1 TO UQ_GAME_IMAGES_IMAGE_ID;
-ALTER TABLE LIBRARY_DIRECTORIES
- RENAME CONSTRAINT UKB5UM4CADBNC6UC8DVOMO81N5F TO UQ_LIBRARY_DIRECTORIES_DIRECTORY_ID;
-ALTER TABLE LIBRARY_GAMES
- RENAME CONSTRAINT UK3E4VB9NQXPY27VMTA27GU5FY8 TO UQ_LIBRARY_GAMES_GAME_ID;
-ALTER TABLE USERS
- RENAME CONSTRAINT UK6DOTKOTT2KJSP8VW4D0M25FB7 TO UQ_USERS_EMAIL;
-ALTER TABLE USERS
- RENAME CONSTRAINT UKR43AF9AP4EDM43MMTQ01ODDJ6 TO UQ_USERS_USERNAME;
-ALTER TABLE USERS
- RENAME CONSTRAINT UKRSULCN2GYNJY3CDDPWMOSV881 TO UQ_USERS_AVATAR_ID;
-ALTER TABLE GAME_FIELD_METADATA
- RENAME CONSTRAINT UKHW6U2Y9FLWPTI57QB7K0P27BL TO UQ_GAME_FIELD_METADATA_SOURCE_ID;
-ALTER TABLE GAME_FIELDS
- RENAME CONSTRAINT UK1L5OAH0UOOUV4V5A9P0PAK77X TO UQ_GAME_FIELDS_FIELD_METADATA_ID;
+CALL RENAME_CONSTRAINT_IF_EXISTS('COMPANY', 'UK4UCNYHR8I0URHWDUDFAHKOB9E', 'UQ_COMPANY_NAME_TYPE');
+CALL RENAME_CONSTRAINT_IF_EXISTS('DIRECTORY_MAPPING', 'UKJ3GSATFAHEWFOLSEAJ29O3KYT',
+ 'UQ_DIRECTORY_MAPPING_INTERNAL_PATH');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME', 'UK4WXN9FPXFQ8QXPSB7FY0O3NOA', 'UQ_GAME_PATH');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_IMAGES', 'UKBDE7M3TKHIEEYBINM2ED0B6X1', 'UQ_GAME_IMAGES_IMAGE_ID');
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_DIRECTORIES', 'UKB5UM4CADBNC6UC8DVOMO81N5F',
+ 'UQ_LIBRARY_DIRECTORIES_DIRECTORY_ID');
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_GAMES', 'UK3E4VB9NQXPY27VMTA27GU5FY8', 'UQ_LIBRARY_GAMES_GAME_ID');
+CALL RENAME_CONSTRAINT_IF_EXISTS('USERS', 'UK6DOTKOTT2KJSP8VW4D0M25FB7', 'UQ_USERS_EMAIL');
+CALL RENAME_CONSTRAINT_IF_EXISTS('USERS', 'UKR43AF9AP4EDM43MMTQ01ODDJ6', 'UQ_USERS_USERNAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('USERS', 'UKRSULCN2GYNJY3CDDPWMOSV881', 'UQ_USERS_AVATAR_ID');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELD_METADATA', 'UKHW6U2Y9FLWPTI57QB7K0P27BL',
+ 'UQ_GAME_FIELD_METADATA_SOURCE_ID');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELDS', 'UK1L5OAH0UOOUV4V5A9P0PAK77X', 'UQ_GAME_FIELDS_FIELD_METADATA_ID');
/******************************************************************************************
- * 3. Rename FOREIGN KEY constraints (FK*)
+ * 3. Rename FOREIGN KEY constraints (FK*) (tolerant)
******************************************************************************************/
-ALTER TABLE GAME
- RENAME CONSTRAINT FK6CVB43REAYSNYPI0XDY6HQTVF TO FK_GAME_COVER_IMAGE;
-ALTER TABLE GAME
- RENAME CONSTRAINT FK8N86NDPGKMOO7YOLX6HL8N84G TO FK_GAME_HEADER_IMAGE;
-ALTER TABLE GAME
- RENAME CONSTRAINT FKIUVR8XFB63T1K6T43EYYXVO2C TO FK_GAME_LIBRARY;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME', 'FK6CVB43REAYSNYPI0XDY6HQTVF', 'FK_GAME_COVER_IMAGE');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME', 'FK8N86NDPGKMOO7YOLX6HL8N84G', 'FK_GAME_HEADER_IMAGE');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME', 'FKIUVR8XFB63T1K6T43EYYXVO2C', 'FK_GAME_LIBRARY');
-ALTER TABLE GAME_DEVELOPERS
- RENAME CONSTRAINT FKB12PO9L2B9OJBAIHC82MM2QXB TO FK_GAME_DEVELOPERS_COMPANY;
-ALTER TABLE GAME_DEVELOPERS
- RENAME CONSTRAINT FKS4IJSVPIJ53DSL143XVRGBS09 TO FK_GAME_DEVELOPERS_GAME;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_DEVELOPERS', 'FKB12PO9L2B9OJBAIHC82MM2QXB', 'FK_GAME_DEVELOPERS_COMPANY');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_DEVELOPERS', 'FKS4IJSVPIJ53DSL143XVRGBS09', 'FK_GAME_DEVELOPERS_GAME');
-ALTER TABLE GAME_FEATURES
- RENAME CONSTRAINT FK63XLTCT60SCIMPM06K8BHBE4A TO FK_GAME_FEATURES_GAME;
-ALTER TABLE GAME_GENRES
- RENAME CONSTRAINT FKDTSX09YOPD98E0LUEWRUSJD9E TO FK_GAME_GENRES_GAME;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FEATURES', 'FK63XLTCT60SCIMPM06K8BHBE4A', 'FK_GAME_FEATURES_GAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_GENRES', 'FKDTSX09YOPD98E0LUEWRUSJD9E', 'FK_GAME_GENRES_GAME');
-ALTER TABLE GAME_IMAGES
- RENAME CONSTRAINT FK5YWV1DMXCM2VSQUEB7RHQ3JK9 TO FK_GAME_IMAGES_IMAGE;
-ALTER TABLE GAME_IMAGES
- RENAME CONSTRAINT FKOWCPUCV45OX8GT28TXGVHF1AA TO FK_GAME_IMAGES_GAME;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_IMAGES', 'FK5YWV1DMXCM2VSQUEB7RHQ3JK9', 'FK_GAME_IMAGES_IMAGE');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_IMAGES', 'FKOWCPUCV45OX8GT28TXGVHF1AA', 'FK_GAME_IMAGES_GAME');
-ALTER TABLE GAME_KEYWORDS
- RENAME CONSTRAINT FKMVF6HNJ7ROMQQM2EX70A9NVAC TO FK_GAME_KEYWORDS_GAME;
-ALTER TABLE GAME_PERSPECTIVES
- RENAME CONSTRAINT FKHUEENG29Y1GHBRDI5QHGUXH6E TO FK_GAME_PERSPECTIVES_GAME;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_KEYWORDS', 'FKMVF6HNJ7ROMQQM2EX70A9NVAC', 'FK_GAME_KEYWORDS_GAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_PERSPECTIVES', 'FKHUEENG29Y1GHBRDI5QHGUXH6E', 'FK_GAME_PERSPECTIVES_GAME');
-ALTER TABLE GAME_PUBLISHERS
- RENAME CONSTRAINT FK49R2KB61LIJ54BQB4VNTST97N TO FK_GAME_PUBLISHERS_GAME;
-ALTER TABLE GAME_PUBLISHERS
- RENAME CONSTRAINT FKNGLD5ESGRBRH95J5BJF0HEF85 TO FK_GAME_PUBLISHERS_COMPANY;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_PUBLISHERS', 'FK49R2KB61LIJ54BQB4VNTST97N', 'FK_GAME_PUBLISHERS_GAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_PUBLISHERS', 'FKNGLD5ESGRBRH95J5BJF0HEF85', 'FK_GAME_PUBLISHERS_COMPANY');
-ALTER TABLE GAME_THEMES
- RENAME CONSTRAINT FKRV351JXLIOY0A17Y5BBJJ6FW4 TO FK_GAME_THEMES_GAME;
-ALTER TABLE GAME_VIDEO_URLS
- RENAME CONSTRAINT FKJKKWO8WDS086AS7B2KSLSVKM6 TO FK_GAME_VIDEO_URLS_GAME;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_THEMES', 'FKRV351JXLIOY0A17Y5BBJJ6FW4', 'FK_GAME_THEMES_GAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_VIDEO_URLS', 'FKJKKWO8WDS086AS7B2KSLSVKM6', 'FK_GAME_VIDEO_URLS_GAME');
-ALTER TABLE LIBRARY_DIRECTORIES
- RENAME CONSTRAINT FKFNCKIU58I9L89MLXV388DY13B TO FK_LIBRARY_DIRECTORIES_LIBRARY;
-ALTER TABLE LIBRARY_DIRECTORIES
- RENAME CONSTRAINT FKJDXS58Q1IRTU0IDP6DXJHWAPM TO FK_LIBRARY_DIRECTORIES_DIRECTORY_MAPPING;
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_DIRECTORIES', 'FKFNCKIU58I9L89MLXV388DY13B',
+ 'FK_LIBRARY_DIRECTORIES_LIBRARY');
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_DIRECTORIES', 'FKJDXS58Q1IRTU0IDP6DXJHWAPM',
+ 'FK_LIBRARY_DIRECTORIES_DIRECTORY_MAPPING');
-ALTER TABLE LIBRARY_GAMES
- RENAME CONSTRAINT FK6C71EEDM0I2N1JXDE9BOBWG5M TO FK_LIBRARY_GAMES_LIBRARY;
-ALTER TABLE LIBRARY_GAMES
- RENAME CONSTRAINT FKDKKKES3DAY0WJ1QMV42KMMFDK TO FK_LIBRARY_GAMES_GAME;
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_GAMES', 'FK6C71EEDM0I2N1JXDE9BOBWG5M', 'FK_LIBRARY_GAMES_LIBRARY');
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_GAMES', 'FKDKKKES3DAY0WJ1QMV42KMMFDK', 'FK_LIBRARY_GAMES_GAME');
-ALTER TABLE LIBRARY_UNMATCHED_PATHS
- RENAME CONSTRAINT FKSJ51WC2LBNNXY0LKLWELI6VSB TO FK_LIBRARY_UNMATCHED_PATHS_LIBRARY;
+CALL RENAME_CONSTRAINT_IF_EXISTS('LIBRARY_UNMATCHED_PATHS', 'FKSJ51WC2LBNNXY0LKLWELI6VSB',
+ 'FK_LIBRARY_UNMATCHED_PATHS_LIBRARY');
-ALTER TABLE GAME_ORIGINAL_IDS
- RENAME CONSTRAINT FK1CSD5QD7VJT7BTTA3G7HGYBUX TO FK_GAME_ORIGINAL_IDS_GAME;
-ALTER TABLE GAME_ORIGINAL_IDS
- RENAME CONSTRAINT FKMT0XWLPWPU9NP0Q289JBAHJRY TO FK_GAME_ORIGINAL_IDS_PLUGIN;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_ORIGINAL_IDS', 'FK1CSD5QD7VJT7BTTA3G7HGYBUX', 'FK_GAME_ORIGINAL_IDS_GAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_ORIGINAL_IDS', 'FKMT0XWLPWPU9NP0Q289JBAHJRY', 'FK_GAME_ORIGINAL_IDS_PLUGIN');
-ALTER TABLE USERS
- RENAME CONSTRAINT FK19LFLPG5SEIS4DWRM2LVJLXFV TO FK_USERS_AVATAR_IMAGE;
+CALL RENAME_CONSTRAINT_IF_EXISTS('USERS', 'FK19LFLPG5SEIS4DWRM2LVJLXFV', 'FK_USERS_AVATAR_IMAGE');
-ALTER TABLE GAME_FIELD_SOURCE
- RENAME CONSTRAINT FKNJC4QSS5APFHTPWP42OAEAL5G TO FK_GAME_FIELD_SOURCE_PLUGIN;
-ALTER TABLE GAME_FIELD_SOURCE
- RENAME CONSTRAINT FKSR1BGTX5XJVMAL7FEFGL982TP TO FK_GAME_FIELD_SOURCE_USER;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELD_SOURCE', 'FKNJC4QSS5APFHTPWP42OAEAL5G', 'FK_GAME_FIELD_SOURCE_PLUGIN');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELD_SOURCE', 'FKSR1BGTX5XJVMAL7FEFGL982TP', 'FK_GAME_FIELD_SOURCE_USER');
-ALTER TABLE GAME_FIELD_METADATA
- RENAME CONSTRAINT FKQ4RC409TP8FUBTTM733PMJD8F TO FK_GAME_FIELD_METADATA_SOURCE;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELD_METADATA', 'FKQ4RC409TP8FUBTTM733PMJD8F', 'FK_GAME_FIELD_METADATA_SOURCE');
-ALTER TABLE GAME_FIELDS
- RENAME CONSTRAINT FKLNEPI7YWCI86YH21KO9WD9PYF TO FK_GAME_FIELDS_GAME;
-ALTER TABLE GAME_FIELDS
- RENAME CONSTRAINT FKT8FLOFDAPX5M746S5LW54C5B3 TO FK_GAME_FIELDS_METADATA;
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELDS', 'FKLNEPI7YWCI86YH21KO9WD9PYF', 'FK_GAME_FIELDS_GAME');
+CALL RENAME_CONSTRAINT_IF_EXISTS('GAME_FIELDS', 'FKT8FLOFDAPX5M746S5LW54C5B3', 'FK_GAME_FIELDS_METADATA');
-ALTER TABLE TOKEN
- RENAME CONSTRAINT FKGHOIALAPTI5JFEJ506JBB1O8Y TO FK_TOKEN_CREATOR_USER;
+CALL RENAME_CONSTRAINT_IF_EXISTS('TOKEN', 'FKGHOIALAPTI5JFEJ506JBB1O8Y', 'FK_TOKEN_CREATOR_USER');
-ALTER TABLE USER_ROLES
- RENAME CONSTRAINT FKHFH9DX7W3UBF1CO1VDEV94G3F TO FK_USER_ROLES_USER;
+CALL RENAME_CONSTRAINT_IF_EXISTS('USER_ROLES', 'FKHFH9DX7W3UBF1CO1VDEV94G3F', 'FK_USER_ROLES_USER');
-- End of migration
diff --git a/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql b/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql
index 970cb67..4b0995d 100644
--- a/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql
+++ b/app/src/main/resources/db/migration/V2.1.0.2__Create_game_requests_table.sql
@@ -5,13 +5,13 @@
/******************************************************************************************
* 1. Create new sequence
******************************************************************************************/
-CREATE SEQUENCE GAME_REQUEST_SEQ
+CREATE SEQUENCE IF NOT EXISTS GAME_REQUEST_SEQ
INCREMENT BY 50;
/******************************************************************************************
* 2. Create new tables
******************************************************************************************/
-CREATE TABLE GAME_REQUEST
+CREATE TABLE IF NOT EXISTS GAME_REQUEST
(
ID BIGINT NOT NULL PRIMARY KEY,
TITLE VARCHAR(255) NOT NULL,
@@ -20,23 +20,22 @@ CREATE TABLE GAME_REQUEST
REQUESTER_ID BIGINT,
LINKED_GAME_ID BIGINT,
CREATED_AT TIMESTAMP NOT NULL,
- UPDATED_AT TIMESTAMP NOT NULL
+ UPDATED_AT TIMESTAMP NOT NULL,
+ CONSTRAINT FK_GAMEREQUEST_ON_REQUESTER
+ FOREIGN KEY (REQUESTER_ID) REFERENCES USERS
+ ON DELETE SET NULL
);
-CREATE TABLE GAME_REQUEST_VOTERS
+CREATE TABLE IF NOT EXISTS GAME_REQUEST_VOTERS
(
GAME_REQUEST_ID BIGINT NOT NULL,
VOTERS_ID BIGINT NOT NULL,
- PRIMARY KEY (GAME_REQUEST_ID, VOTERS_ID)
+ PRIMARY KEY (GAME_REQUEST_ID, VOTERS_ID),
+ CONSTRAINT FK_GAMREQVOT_ON_GAME_REQUEST
+ FOREIGN KEY (GAME_REQUEST_ID) REFERENCES GAME_REQUEST
+ ON DELETE CASCADE,
+ CONSTRAINT FK_GAMREQVOT_ON_USER
+ FOREIGN KEY (VOTERS_ID) REFERENCES USERS
);
-ALTER TABLE GAME_REQUEST
- ADD CONSTRAINT FK_GAMEREQUEST_ON_REQUESTER FOREIGN KEY (REQUESTER_ID) REFERENCES USERS (ID) ON DELETE SET NULL;
-
-ALTER TABLE GAME_REQUEST_VOTERS
- ADD CONSTRAINT FK_GAMREQVOT_ON_GAME_REQUEST FOREIGN KEY (GAME_REQUEST_ID) REFERENCES GAME_REQUEST (ID) ON DELETE CASCADE;
-
-ALTER TABLE GAME_REQUEST_VOTERS
- ADD CONSTRAINT FK_GAMREQVOT_ON_USER FOREIGN KEY (VOTERS_ID) REFERENCES USERS (ID);
-
-- End of migration
From 90a9e8ba207cec90cb9789d20bd750fef42e1d55 Mon Sep 17 00:00:00 2001
From: grimsi <9295182+grimsi@users.noreply.github.com>
Date: Mon, 29 Sep 2025 17:27:50 +0200
Subject: [PATCH 32/32] Precompile H2 helper functions
---
.../kotlin/org/gameyfin/db/h2/H2Aliases.kt | 30 +++++++++++++++++++
...e_image_uniques_and_rename_constraints.sql | 20 ++-----------
2 files changed, 33 insertions(+), 17 deletions(-)
create mode 100644 app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt
diff --git a/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt b/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt
new file mode 100644
index 0000000..f74ade9
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/db/h2/H2Aliases.kt
@@ -0,0 +1,30 @@
+package org.gameyfin.db.h2
+
+import java.sql.Connection
+import java.sql.SQLException
+
+/**
+ * H2 helper methods exposed as SQL ALIASes.
+ *
+ * Kotlin implementation replacing the former Java version so a JDK (javac) is not
+ * required at runtime for defining aliases in migration scripts.
+ */
+object H2Aliases {
+ /**
+ * Renames a constraint if it exists, swallowing only H2 error code 90057 (constraint not found).
+ */
+ @JvmStatic
+ @Throws(SQLException::class)
+ fun renameConstraintIfExists(conn: Connection, table: String, oldName: String, newName: String) {
+ conn.createStatement().use { st ->
+ try {
+ st.execute("ALTER TABLE $table RENAME CONSTRAINT $oldName TO $newName")
+ } catch (e: SQLException) {
+ if (e.errorCode != 90057) { // ignore only 'constraint not found'
+ throw e
+ }
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql b/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
index f1e776b..24d5d25 100644
--- a/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
+++ b/app/src/main/resources/db/migration/V2.1.0.1__Drop_game_image_uniques_and_rename_constraints.sql
@@ -9,24 +9,10 @@
/******************************************************************************************
* Helper: Idempotent constraint rename for H2
- * H2 does not support RENAME CONSTRAINT IF EXISTS, so we create a Java alias that
- * executes a rename and suppresses error 90057 (constraint not found).
- * NOTE: Do not declare method as public static; H2 wraps it and adds modifiers itself.
+ * (Updated) Use precompiled Java class instead of inline Java to avoid needing 'javac' at runtime.
+ * Class: org.gameyfin.db.h2.H2Aliases.renameConstraintIfExists(Connection,String,String,String)
******************************************************************************************/
-CREATE ALIAS IF NOT EXISTS RENAME_CONSTRAINT_IF_EXISTS AS
-$$
-import java.sql.*;
-@CODE
-void run(Connection conn, String table, String oldName, String newName) throws SQLException {
- try (Statement st = conn.createStatement()) {
- st.execute("ALTER TABLE " + table + " RENAME CONSTRAINT " + oldName + " TO " + newName);
- } catch (SQLException e) {
- if (e.getErrorCode() != 90057) { // ignore only 'constraint not found'
- throw e;
- }
- }
-}
-$$;
+CREATE ALIAS IF NOT EXISTS RENAME_CONSTRAINT_IF_EXISTS FOR "org.gameyfin.db.h2.H2Aliases.renameConstraintIfExists";
/******************************************************************************************
* 1. Drop the two unwanted unique constraints on GAME