>();
@@ -49,13 +49,11 @@ export default function GameView() {
}, []);
useEffect(() => {
- initializeGameState().then((state) => {
- if (!gameId || !state.state[parseInt(gameId)]) {
- navigate("/", {replace: true});
- }
- document.title = game ? game.title : "Gameyfin";
- });
- }, [gameId]);
+ if (state.isLoaded && (!gameId || !state.state[parseInt(gameId)])) {
+ navigate("/", {replace: true});
+ }
+ document.title = game ? game.title : "Gameyfin";
+ }, [gameId, state]);
async function toggleMatchConfirmed() {
if (!game) return;
diff --git a/app/src/main/frontend/views/LibraryManagementView.tsx b/app/src/main/frontend/views/LibraryManagementView.tsx
index 288ffc9..78acac2 100644
--- a/app/src/main/frontend/views/LibraryManagementView.tsx
+++ b/app/src/main/frontend/views/LibraryManagementView.tsx
@@ -6,8 +6,9 @@ import {ArrowLeft} from "@phosphor-icons/react";
import LibraryManagementDetails from "Frontend/components/general/library/LibraryManagementDetails";
import LibraryManagementGames from "Frontend/components/general/library/LibraryManagementGames";
import {useSnapshot} from "valtio/react";
-import {initializeLibraryState, libraryState} from "Frontend/state/LibraryState";
+import {libraryState} from "Frontend/state/LibraryState";
import LibraryManagementUnmatchedPaths from "Frontend/components/general/library/LibraryManagementUnmatchedPaths";
+import LibraryAdminDto from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryAdminDto";
export default function LibraryManagementView() {
@@ -17,12 +18,10 @@ export default function LibraryManagementView() {
const state = useSnapshot(libraryState);
useEffect(() => {
- initializeLibraryState().then((state) => {
- if (!libraryId || !state.state[parseInt(libraryId)]) {
- navigate("/administration/libraries");
- }
- });
- }, [libraryId]);
+ if (state.isLoaded && (!libraryId || !state.state[parseInt(libraryId)])) {
+ navigate("/administration/libraries");
+ }
+ }, [state, libraryId]);
return libraryId && state.state[parseInt(libraryId)] &&
@@ -31,23 +30,18 @@ export default function LibraryManagementView() {
Manage library
- {/* @ts-ignore */}
-
- {/* @ts-ignore */}
+
0 ? hash : "#details"}
onSelectionChange={(newKey) => navigate(newKey.toString(), {replace: true})}>
- {/* @ts-ignore */}
-
+
- {/* @ts-ignore */}
-
+
- {/* @ts-ignore */}
-
+
;
diff --git a/app/src/main/frontend/views/LibraryView.tsx b/app/src/main/frontend/views/LibraryView.tsx
index 9943fa8..597d7e1 100644
--- a/app/src/main/frontend/views/LibraryView.tsx
+++ b/app/src/main/frontend/views/LibraryView.tsx
@@ -1,5 +1,5 @@
import {useSnapshot} from "valtio/react";
-import {initializeLibraryState, libraryState} from "Frontend/state/LibraryState";
+import {libraryState} from "Frontend/state/LibraryState";
import {gameState} from "Frontend/state/GameState";
import React, {useEffect} from "react";
import {useNavigate, useParams} from "react-router";
@@ -13,13 +13,11 @@ export default function LibraryView() {
const games = (libraryId ? useSnapshot(gameState).gamesByLibraryId[parseInt(libraryId!!)] || [] : []) as GameDto[];
useEffect(() => {
- initializeLibraryState().then((state) => {
- if (!libraryId || !state.state[parseInt(libraryId)]) {
- navigate("/", {replace: true});
- }
- document.title = state.state[parseInt(libraryId!!)]?.name || "Gameyfin";
- });
- }, [libraryId]);
+ if (libraries.isLoaded && (!libraryId || !libraries.state[parseInt(libraryId)])) {
+ navigate("/", {replace: true});
+ }
+ document.title = libraries.state[parseInt(libraryId!!)]?.name || "Gameyfin";
+ }, [libraryId, libraries]);
return (
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 7666814..50ddede 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigEndpoint.kt
@@ -9,8 +9,7 @@ import org.gameyfin.app.config.dto.ConfigEntryDto
import org.gameyfin.app.config.dto.ConfigUpdateDto
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.annotations.DynamicPublicAccess
-import org.gameyfin.app.users.UserService
-import org.gameyfin.app.users.util.isAdmin
+import org.gameyfin.app.core.security.isCurrentUserAdmin
import org.springframework.scheduling.support.CronExpression
import reactor.core.publisher.Flux
@@ -18,7 +17,6 @@ import reactor.core.publisher.Flux
@RolesAllowed(Role.Names.ADMIN)
class ConfigEndpoint(
private val configService: ConfigService,
- private val userService: UserService,
) {
companion object {
val log = KotlinLogging.logger { }
@@ -28,8 +26,7 @@ class ConfigEndpoint(
@PermitAll
fun subscribe(): Flux> {
- val user = userService.getCurrentUser()
- return if (user.isAdmin()) ConfigService.subscribe()
+ return if (isCurrentUserAdmin()) ConfigService.subscribe()
else Flux.empty()
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt b/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt
index 2caa883..fc74e31 100644
--- a/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/config/ConfigService.kt
@@ -46,7 +46,7 @@ class ConfigService(
*/
fun get(configProperty: ConfigProperties): T? {
- log.debug { "Getting config value '${configProperty.key}'" }
+ log.trace { "Getting config value '${configProperty.key}'" }
val appConfig = appConfigRepository.findByIdOrNull(configProperty.key)
return if (appConfig != null) {
@@ -65,7 +65,7 @@ class ConfigService(
*/
fun get(key: String): Serializable? {
- log.debug { "Getting config value '$key'" }
+ log.trace { "Getting config value '$key'" }
val configProperty = findConfigProperty(key)
diff --git a/app/src/main/kotlin/org/gameyfin/app/core/logging/LogEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/core/logging/LogEndpoint.kt
index c161243..98b0ca1 100644
--- a/app/src/main/kotlin/org/gameyfin/app/core/logging/LogEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/core/logging/LogEndpoint.kt
@@ -4,15 +4,13 @@ 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.UserService
-import org.gameyfin.app.users.util.isAdmin
+import org.gameyfin.app.core.security.isCurrentUserAdmin
import reactor.core.publisher.Flux
@Endpoint
@RolesAllowed(Role.Names.ADMIN)
class LogEndpoint(
private val logService: LogService,
- private val userService: UserService,
) {
fun reloadLogConfig() {
@@ -21,8 +19,7 @@ class LogEndpoint(
@PermitAll
fun getApplicationLogs(): Flux {
- val user = userService.getCurrentUser()
- return if (user.isAdmin()) logService.streamLogs()
+ return if (isCurrentUserAdmin()) logService.streamLogs()
else Flux.empty()
}
}
\ No newline at end of file
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 6f25573..94be2f4 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
@@ -5,8 +5,7 @@ import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role
import org.gameyfin.app.core.plugins.dto.PluginUpdateDto
-import org.gameyfin.app.users.UserService
-import org.gameyfin.app.users.util.isAdmin
+import org.gameyfin.app.core.security.isCurrentUserAdmin
import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import reactor.core.publisher.Flux
@@ -14,13 +13,11 @@ import reactor.core.publisher.Flux
@RolesAllowed(Role.Names.ADMIN)
class PluginEndpoint(
private val pluginService: PluginService,
- private val userService: UserService,
) {
@PermitAll
fun subscribe(): Flux> {
- val user = userService.getCurrentUser()
- return if (user.isAdmin()) PluginService.subscribe()
+ return if (isCurrentUserAdmin()) PluginService.subscribe()
else Flux.empty()
}
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
new file mode 100644
index 0000000..1b0adf1
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/core/security/SecurityUtils.kt
@@ -0,0 +1,9 @@
+package org.gameyfin.app.core.security
+
+import org.gameyfin.app.core.Role
+import org.springframework.security.core.context.SecurityContextHolder
+
+fun isCurrentUserAdmin(): Boolean {
+ return SecurityContextHolder.getContext().authentication?.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 61a043c..5ecd8dd 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameEndpoint.kt
@@ -5,6 +5,7 @@ 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.security.isCurrentUserAdmin
import org.gameyfin.app.games.dto.*
import org.gameyfin.app.libraries.LibraryCoreService
import org.gameyfin.app.libraries.LibraryService
@@ -19,8 +20,12 @@ class GameEndpoint(
private val libraryService: LibraryService,
private val libraryCoreService: LibraryCoreService
) {
- fun subscribe(): Flux> {
- return GameService.subscribe()
+ fun subscribe(): Flux> {
+ return if (isCurrentUserAdmin()) {
+ GameService.subscribeAdmin()
+ } else {
+ GameService.subscribeUser()
+ }
}
fun getAll(): List = gameService.getAll()
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 3ac7fe6..751082e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/GameService.kt
@@ -18,7 +18,7 @@ import org.gameyfin.app.core.plugins.management.PluginManagementEntry
import org.gameyfin.app.core.replaceRomanNumerals
import org.gameyfin.app.games.dto.*
import org.gameyfin.app.games.entities.*
-import org.gameyfin.app.games.entities.GameMetadata
+import org.gameyfin.app.games.extensions.toDtos
import org.gameyfin.app.games.repositories.GameRepository
import org.gameyfin.app.libraries.Library
import org.gameyfin.app.media.ImageService
@@ -57,22 +57,39 @@ class GameService(
private val log = KotlinLogging.logger {}
/* Websockets */
- private val gameEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
+ private val gameUserEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
+ private val gameAdminEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
- fun subscribe(): Flux> {
- log.debug { "New subscription for gameUpdates" }
- return gameEvents.asFlux()
+ fun subscribeUser(): Flux> {
+ log.debug { "New user subscription for gameUpdates" }
+ return gameUserEvents.asFlux()
.buffer(100.milliseconds.toJavaDuration())
.doOnSubscribe {
- log.debug { "Subscriber added to gameEvents [${gameEvents.currentSubscriberCount()}]" }
+ log.debug { "Subscriber added to gameUserEvents [${gameUserEvents.currentSubscriberCount()}]" }
}
.doFinally {
- log.debug { "Subscriber removed from gameEvents with signal type $it [${gameEvents.currentSubscriberCount()}]" }
+ log.debug { "Subscriber removed from gameUserEvents with signal type $it [${gameUserEvents.currentSubscriberCount()}]" }
}
}
- fun emit(event: GameEvent) {
- gameEvents.tryEmitNext(event)
+ fun subscribeAdmin(): Flux> {
+ log.debug { "New admin subscription for gameUpdates" }
+ return gameAdminEvents.asFlux()
+ .buffer(100.milliseconds.toJavaDuration())
+ .doOnSubscribe {
+ log.debug { "Subscriber added to gameAdminEvents [${gameAdminEvents.currentSubscriberCount()}]" }
+ }
+ .doFinally {
+ log.debug { "Subscriber removed from gameAdminEvents with signal type $it [${gameAdminEvents.currentSubscriberCount()}]" }
+ }
+ }
+
+ fun emitUser(event: GameUserEvent) {
+ gameUserEvents.tryEmitNext(event)
+ }
+
+ fun emitAdmin(event: GameAdminEvent) {
+ gameAdminEvents.tryEmitNext(event)
}
private val executor = Executors.newVirtualThreadPerTaskExecutor()
@@ -84,7 +101,7 @@ class GameService(
fun getAll(): List {
val entities = gameRepository.findAll()
- return entities.map { it.toDto() }
+ return entities.toDtos()
}
@Transactional
@@ -853,74 +870,4 @@ class GameService(
}
fun String.normalizeGameTitle(): String = this.alphaNumeric().replaceRomanNumerals()
-}
-
-
-fun Game.toDto(): GameDto {
- // Helper functions
- fun toDto(fieldMetadata: GameFieldMetadata): GameFieldMetadataDto {
- val source = fieldMetadata.source
-
- return when (source) {
- is GameFieldPluginSource -> {
- GameFieldMetadataDto(
- type = GameFieldMetadataType.PLUGIN,
- source = source.plugin.pluginId,
- updatedAt = fieldMetadata.updatedAt!!
- )
- }
-
- is GameFieldUserSource -> {
- GameFieldMetadataDto(
- type = GameFieldMetadataType.USER,
- source = source.user.username,
- updatedAt = fieldMetadata.updatedAt!!
- )
- }
-
- else -> {
- GameFieldMetadataDto(
- type = GameFieldMetadataType.UNKNOWN,
- source = "unknown source",
- updatedAt = fieldMetadata.updatedAt!!
- )
- }
- }
- }
-
- fun toDto(metadata: GameMetadata): GameMetadataDto {
- return GameMetadataDto(
- fileSize = metadata.fileSize ?: 0L,
- downloadCount = metadata.downloadCount,
- path = metadata.path,
- fields = metadata.fields.mapValues { toDto(it.value) },
- originalIds = metadata.originalIds.mapKeys { it.key.pluginId },
- matchConfirmed = metadata.matchConfirmed
- )
- }
-
- return GameDto(
- id = id!!,
- createdAt = createdAt!!,
- updatedAt = updatedAt!!,
- libraryId = this.library.id!!,
- title = title!!,
- coverId = this.coverImage?.id,
- headerId = this.headerImage?.id,
- comment = this.comment,
- summary = this.summary,
- release = this.release?.atZone(ZoneOffset.UTC)?.toLocalDate(),
- userRating = this.userRating,
- criticRating = this.criticRating,
- publishers = this.publishers.map { it.name },
- developers = this.developers.map { it.name },
- genres = this.genres.map { it.name },
- themes = this.themes.map { it.name },
- keywords = this.keywords.toList(),
- features = this.features.map { it.name },
- perspectives = this.perspectives?.map { it.name },
- imageIds = this.images.mapNotNull { it.id },
- videoUrls = this.videoUrls.map { it.toString() },
- metadata = toDto(this.metadata)
- )
}
\ 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 70fab71..8e0fa66 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
@@ -4,28 +4,79 @@ import com.fasterxml.jackson.annotation.JsonInclude
import java.time.Instant
import java.time.LocalDate
-@JsonInclude(JsonInclude.Include.NON_NULL)
-class GameDto(
- val id: Long,
- val createdAt: Instant,
- val updatedAt: Instant,
- val libraryId: Long,
- val title: String,
- val coverId: Long?,
- val headerId: Long?,
- val comment: String?,
- val summary: String?,
- val release: LocalDate?,
- val userRating: Int?,
- val criticRating: Int?,
- val publishers: List?,
- val developers: List?,
- val genres: List?,
- val themes: List?,
- val keywords: List?,
- val features: List?,
- val perspectives: List?,
- val imageIds: List?,
- val videoUrls: List?,
+sealed interface GameDto {
+ val id: Long
+ val createdAt: Instant
+ val updatedAt: Instant
+ val libraryId: Long
+ val title: String
+ val coverId: Long?
+ val headerId: Long?
+ val comment: String?
+ val summary: String?
+ val release: LocalDate?
+ val userRating: Int?
+ val criticRating: Int?
+ val publishers: List?
+ val developers: List?
+ val genres: List?
+ val themes: List?
+ val keywords: List?
+ val features: List?
+ val perspectives: List?
+ val imageIds: List?
+ val videoUrls: List?
val metadata: GameMetadataDto
-)
\ No newline at end of file
+}
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class GameUserDto(
+ override val id: Long,
+ override val createdAt: Instant,
+ override val updatedAt: Instant,
+ override val libraryId: Long,
+ override val title: String,
+ override val coverId: Long?,
+ override val headerId: Long?,
+ override val comment: String?,
+ override val summary: String?,
+ override val release: LocalDate?,
+ override val userRating: Int?,
+ override val criticRating: Int?,
+ override val publishers: List?,
+ override val developers: List?,
+ override val genres: List?,
+ override val themes: List?,
+ override val keywords: List?,
+ override val features: List?,
+ override val perspectives: List?,
+ override val imageIds: List?,
+ override val videoUrls: List?,
+ override val metadata: GameMetadataUserDto
+) : GameDto
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class GameAdminDto(
+ override val id: Long,
+ override val createdAt: Instant,
+ override val updatedAt: Instant,
+ override val libraryId: Long,
+ override val title: String,
+ override val coverId: Long?,
+ override val headerId: Long?,
+ override val comment: String?,
+ override val summary: String?,
+ override val release: LocalDate?,
+ override val userRating: Int?,
+ override val criticRating: Int?,
+ override val publishers: List?,
+ override val developers: List?,
+ override val genres: List?,
+ override val themes: List?,
+ override val keywords: List?,
+ override val features: List?,
+ override val perspectives: List?,
+ override val imageIds: List?,
+ override val videoUrls: List?,
+ override val metadata: GameMetadataAdminDto
+) : GameDto
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameEvent.kt b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameEvent.kt
deleted file mode 100644
index 8186ff3..0000000
--- a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameEvent.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.gameyfin.app.games.dto
-
-sealed class GameEvent {
- abstract val type: String
-
- data class Created(val game: GameDto, override val type: String = "created") : GameEvent()
- data class Updated(val game: GameDto, override val type: String = "updated") : GameEvent()
- data class Deleted(val gameId: Long, override val type: String = "deleted") : GameEvent()
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameEvents.kt b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameEvents.kt
new file mode 100644
index 0000000..de47aeb
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameEvents.kt
@@ -0,0 +1,18 @@
+package org.gameyfin.app.games.dto
+
+
+sealed interface GameEvent {
+ val type: String
+}
+
+sealed class GameUserEvent : GameEvent {
+ data class Created(val game: GameUserDto, override val type: String = "created") : GameUserEvent()
+ data class Updated(val game: GameUserDto, override val type: String = "updated") : GameUserEvent()
+ data class Deleted(val gameId: Long, override val type: String = "deleted") : GameUserEvent()
+}
+
+sealed class GameAdminEvent : GameEvent {
+ data class Created(val game: GameAdminDto, override val type: String = "created") : GameAdminEvent()
+ data class Updated(val game: GameAdminDto, override val type: String = "updated") : GameAdminEvent()
+ data class Deleted(val gameId: Long, override val type: String = "deleted") : GameAdminEvent()
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameMetadataDto.kt b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameMetadataDto.kt
index 2caf243..efd5ecf 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/dto/GameMetadataDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/dto/GameMetadataDto.kt
@@ -2,12 +2,21 @@ package org.gameyfin.app.games.dto
import com.fasterxml.jackson.annotation.JsonInclude
+interface GameMetadataDto {
+ val fileSize: Long
+}
+
@JsonInclude(JsonInclude.Include.NON_NULL)
-class GameMetadataDto(
+data class GameMetadataUserDto(
+ override val fileSize: Long
+) : GameMetadataDto
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class GameMetadataAdminDto(
val path: String?,
- val fileSize: Long,
+ override val fileSize: Long,
val fields: Map?,
val originalIds: Map?,
val downloadCount: Int,
val matchConfirmed: Boolean
-)
\ No newline at end of file
+) : GameMetadataDto
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 33c9902..efe500b 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,25 +4,27 @@ import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate
import org.gameyfin.app.games.GameService
-import org.gameyfin.app.games.dto.GameEvent
-import org.gameyfin.app.games.toDto
+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
class GameEntityListener {
@PostPersist
fun created(game: Game) {
- val event = GameEvent.Created(game.toDto())
- GameService.Companion.emit(event)
+ GameService.Companion.emitUser(GameUserEvent.Created(game.toUserDto()))
+ GameService.Companion.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
}
@PostUpdate
fun updated(game: Game) {
- val event = GameEvent.Updated(game.toDto())
- GameService.Companion.emit(event)
+ GameService.Companion.emitUser(GameUserEvent.Updated(game.toUserDto()))
+ GameService.Companion.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
}
@PostRemove
fun deleted(game: Game) {
- val event = GameEvent.Deleted(game.id!!)
- GameService.Companion.emit(event)
+ GameService.Companion.emitUser(GameUserEvent.Deleted(game.id!!))
+ GameService.Companion.emitAdmin(GameAdminEvent.Deleted(game.id!!))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt b/app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt
index 2e49038..c035ea2 100644
--- a/app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/games/entities/LibraryEntityListener.kt
@@ -1,29 +1,31 @@
package org.gameyfin.app.games.entities
-import org.gameyfin.app.libraries.dto.LibraryEvent
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.toDto
+import org.gameyfin.app.libraries.dto.LibraryAdminEvent
+import org.gameyfin.app.libraries.dto.LibraryUserEvent
+import org.gameyfin.app.libraries.extensions.toAdminDto
+import org.gameyfin.app.libraries.extensions.toUserDto
class LibraryEntityListener {
@PostPersist
fun created(library: Library) {
- val event = LibraryEvent.Created(library.toDto())
- LibraryService.Companion.emit(event)
+ LibraryService.emitUser(LibraryUserEvent.Created(library.toUserDto()))
+ LibraryService.emitAdmin(LibraryAdminEvent.Created(library.toAdminDto()))
}
@PostUpdate
fun updated(library: Library) {
- val event = LibraryEvent.Updated(library.toDto())
- LibraryService.Companion.emit(event)
+ LibraryService.emitUser(LibraryUserEvent.Updated(library.toUserDto()))
+ LibraryService.emitAdmin(LibraryAdminEvent.Updated(library.toAdminDto()))
}
@PostRemove
fun deleted(library: Library) {
- val event = LibraryEvent.Deleted(library.id!!)
- LibraryService.Companion.emit(event)
+ LibraryService.emitUser(LibraryUserEvent.Deleted(library.id!!))
+ LibraryService.emitAdmin(LibraryAdminEvent.Deleted(library.id!!))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/games/extensions/GameExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/games/extensions/GameExtensions.kt
new file mode 100644
index 0000000..5ee6558
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/games/extensions/GameExtensions.kt
@@ -0,0 +1,124 @@
+package org.gameyfin.app.games.extensions
+
+import org.gameyfin.app.core.security.isCurrentUserAdmin
+import org.gameyfin.app.games.dto.*
+import org.gameyfin.app.games.entities.*
+import java.time.ZoneOffset
+
+
+fun Game.toDto(): GameDto {
+ return if (isCurrentUserAdmin()) {
+ this.toAdminDto()
+ } else {
+ this.toUserDto()
+ }
+}
+
+fun Collection.toDtos(): List {
+ return if (isCurrentUserAdmin()) {
+ this.map { it.toAdminDto() }
+ } else {
+ this.map { it.toUserDto() }
+ }
+}
+
+fun Game.toAdminDto(): GameAdminDto {
+ return GameAdminDto(
+ id = id!!,
+ createdAt = createdAt!!,
+ updatedAt = updatedAt!!,
+ libraryId = this.library.id!!,
+ title = title!!,
+ coverId = this.coverImage?.id,
+ headerId = this.headerImage?.id,
+ comment = this.comment,
+ summary = this.summary,
+ release = this.release?.atZone(ZoneOffset.UTC)?.toLocalDate(),
+ userRating = this.userRating,
+ criticRating = this.criticRating,
+ publishers = this.publishers.map { it.name },
+ developers = this.developers.map { it.name },
+ genres = this.genres.map { it.name },
+ themes = this.themes.map { it.name },
+ keywords = this.keywords.toList(),
+ features = this.features.map { it.name },
+ perspectives = this.perspectives.map { it.name },
+ imageIds = this.images.mapNotNull { it.id },
+ videoUrls = this.videoUrls.map { it.toString() },
+ metadata = this.metadata.toAdminDto()
+ )
+}
+
+fun Game.toUserDto(): GameUserDto {
+ return GameUserDto(
+ id = id!!,
+ createdAt = createdAt!!,
+ updatedAt = updatedAt!!,
+ libraryId = this.library.id!!,
+ title = title!!,
+ coverId = this.coverImage?.id,
+ headerId = this.headerImage?.id,
+ comment = this.comment,
+ summary = this.summary,
+ release = this.release?.atZone(ZoneOffset.UTC)?.toLocalDate(),
+ userRating = this.userRating,
+ criticRating = this.criticRating,
+ publishers = this.publishers.map { it.name },
+ developers = this.developers.map { it.name },
+ genres = this.genres.map { it.name },
+ themes = this.themes.map { it.name },
+ keywords = this.keywords.toList(),
+ features = this.features.map { it.name },
+ perspectives = this.perspectives.map { it.name },
+ imageIds = this.images.mapNotNull { it.id },
+ videoUrls = this.videoUrls.map { it.toString() },
+ metadata = this.metadata.toUserDto()
+ )
+}
+
+private fun GameMetadata.toAdminDto(): GameMetadataAdminDto {
+ return GameMetadataAdminDto(
+ fileSize = this.fileSize ?: 0L,
+ downloadCount = this.downloadCount,
+ path = this.path,
+ fields = this.fields.mapValues { it.value.toDto() },
+ originalIds = this.originalIds.mapKeys { it.key.pluginId },
+ matchConfirmed = this.matchConfirmed
+ )
+}
+
+private fun GameMetadata.toUserDto(): GameMetadataUserDto {
+ return GameMetadataUserDto(
+ fileSize = this.fileSize ?: 0L
+ )
+}
+
+private fun GameFieldMetadata.toDto(): GameFieldMetadataDto {
+ val source = this.source
+
+ return when (source) {
+ is GameFieldPluginSource -> {
+ GameFieldMetadataDto(
+ type = GameFieldMetadataType.PLUGIN,
+ source = source.plugin.pluginId,
+ updatedAt = this.updatedAt!!
+ )
+ }
+
+ is GameFieldUserSource -> {
+ GameFieldMetadataDto(
+ type = GameFieldMetadataType.USER,
+ source = source.user.username,
+ updatedAt = this.updatedAt!!
+ )
+ }
+
+ else -> {
+ GameFieldMetadataDto(
+ type = GameFieldMetadataType.UNKNOWN,
+ source = "unknown source",
+ updatedAt = this.updatedAt!!
+ )
+ }
+ }
+}
\ 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 f80e3b6..6fc27eb 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryCoreService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryCoreService.kt
@@ -2,10 +2,6 @@ package org.gameyfin.app.libraries
import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.entities.Game
-import org.gameyfin.app.libraries.dto.DirectoryMappingDto
-import org.gameyfin.app.libraries.dto.LibraryDto
-import org.gameyfin.app.libraries.dto.LibraryStatsDto
-import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import java.time.Instant
@@ -54,36 +50,4 @@ class LibraryCoreService(
library.updatedAt = Instant.now() // Force the EntityListener to trigger an update and update the timestamp
libraryRepository.save(library)
}
-
-
- /**
- * Converts a LibraryDto to a Library entity.
- *
- * @param library: The LibraryDto to convert.
- * @return The converted Library entity.
- */
- fun toEntity(library: LibraryDto): Library {
- return libraryRepository.findByIdOrNull(library.id) ?: Library(
- name = library.name,
- directories = library.directories.distinctBy { it.internalPath }.map {
- DirectoryMapping(internalPath = it.internalPath, externalPath = it.externalPath)
- }.toMutableList(),
- )
- }
-}
-
-fun Library.toDto(): LibraryDto {
- val statsDto = LibraryStatsDto(
- gamesCount = this.games.size,
- downloadedGamesCount = this.games.sumOf { it.metadata.downloadCount }
- )
-
- return LibraryDto(
- id = this.id!!,
- name = this.name,
- directories = this.directories.map { DirectoryMappingDto(it.internalPath, it.externalPath) },
- games = this.games.mapNotNull { it.id },
- stats = statsDto,
- unmatchedPaths = this.unmatchedPaths
- )
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt
index 7a91293..ae211d5 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryEndpoint.kt
@@ -5,13 +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.libraries.dto.LibraryDto
+import org.gameyfin.app.core.security.isCurrentUserAdmin
+import org.gameyfin.app.libraries.dto.LibraryAdminDto
import org.gameyfin.app.libraries.dto.LibraryEvent
import org.gameyfin.app.libraries.dto.LibraryScanProgress
import org.gameyfin.app.libraries.dto.LibraryUpdateDto
import org.gameyfin.app.libraries.enums.ScanType
-import org.gameyfin.app.users.UserService
-import org.gameyfin.app.users.util.isAdmin
import reactor.core.publisher.Flux
@Endpoint
@@ -19,27 +18,29 @@ import reactor.core.publisher.Flux
@AnonymousAllowed
class LibraryEndpoint(
private val libraryService: LibraryService,
- private val userService: UserService,
private val libraryScanService: LibraryScanService,
) {
- fun subscribeToLibraryEvents(): Flux> {
- return LibraryService.subscribeToLibraryEvents()
+ fun subscribeToLibraryEvents(): Flux> {
+ return if (isCurrentUserAdmin()) {
+ LibraryService.subscribeAdmin()
+ } else {
+ LibraryService.subscribeUser()
+ }
}
fun getAll() = libraryService.getAll()
fun subscribeToScanProgressEvents(): Flux> {
- val user = userService.getCurrentUser()
- return if (user.isAdmin()) LibraryScanService.subscribeToScanProgressEvents()
+ return if (isCurrentUserAdmin()) LibraryScanService.subscribeToScanProgressEvents()
else Flux.empty()
}
@RolesAllowed(Role.Names.ADMIN)
- fun triggerScan(scanType: ScanType = ScanType.QUICK, libraries: Collection?) =
- libraryScanService.triggerScan(scanType, libraries)
+ fun triggerScan(scanType: ScanType = ScanType.QUICK, libraryIds: Collection?) =
+ libraryScanService.triggerScan(scanType, libraryIds)
@RolesAllowed(Role.Names.ADMIN)
- fun createLibrary(library: LibraryDto, scanAfterCreation: Boolean = true) =
+ fun createLibrary(library: LibraryAdminDto, scanAfterCreation: Boolean = true) =
libraryService.create(library, scanAfterCreation)
@RolesAllowed(Role.Names.ADMIN)
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 3b64568..e66d23e 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt
@@ -4,7 +4,6 @@ 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.LibraryDto
import org.gameyfin.app.libraries.dto.LibraryScanProgress
import org.gameyfin.app.libraries.dto.LibraryScanStatus
import org.gameyfin.app.libraries.dto.LibraryScanStep
@@ -62,8 +61,8 @@ class LibraryScanService(
/**
* Wrapper function to trigger a scan for a list of libraries.
*/
- fun triggerScan(scanType: ScanType, libraryDtos: Collection?) {
- val libraries = libraryDtos?.map { libraryCoreService.toEntity(it) } ?: libraryRepository.findAll()
+ fun triggerScan(scanType: ScanType, libraryIds: Collection?) {
+ val libraries = libraryIds?.let { libraryRepository.findAllById(libraryIds) } ?: libraryRepository.findAll()
libraries.forEach { library ->
val libraryId = library.id!!
if (scansInProgress.putIfAbsent(libraryId, true) == null) {
@@ -84,30 +83,6 @@ class LibraryScanService(
}
}
- /**
- * Triggers a quick scan for a list of libraries.
- * A quick scan will only scan for new games and deleted games, but will not touch existing games.
- * If no list is provided, all libraries will be scanned.
- *
- * @param libraryDtos: List of LibraryDto objects to scan.
- */
- fun quickScan(libraryDtos: Collection?) {
- val libraries = libraryDtos?.map { libraryCoreService.toEntity(it) } ?: libraryRepository.findAll()
- libraries.forEach { executor.submit { quickScan(it) } }
- }
-
- /**
- * Triggers a full scan for a list of libraries.
- * A full scan will rescan all games in the library, including metadata and images.
- * If no list is provided, all libraries will be scanned.
- *
- * @param libraryDtos: List of LibraryDto objects to scan.
- */
- fun fullScan(libraryDtos: Collection?) {
- val libraries = libraryDtos?.map { libraryCoreService.toEntity(it) } ?: libraryRepository.findAll()
- libraries.forEach { executor.submit { fullScan(it, false) } }
- }
-
private fun quickScan(library: Library) {
val progress = LibraryScanProgress(
libraryId = library.id!!,
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 6b5f343..f9c61f5 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryService.kt
@@ -2,11 +2,9 @@ package org.gameyfin.app.libraries
import com.vaadin.hilla.exception.EndpointException
import io.github.oshai.kotlinlogging.KotlinLogging
-import org.gameyfin.app.games.GameService
-import org.gameyfin.app.libraries.dto.LibraryDto
-import org.gameyfin.app.libraries.dto.LibraryEvent
-import org.gameyfin.app.libraries.dto.LibraryUpdateDto
+import org.gameyfin.app.libraries.dto.*
import org.gameyfin.app.libraries.enums.ScanType
+import org.gameyfin.app.libraries.extensions.toDtos
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
@@ -18,31 +16,46 @@ import kotlin.time.toJavaDuration
@Service
class LibraryService(
private val libraryRepository: LibraryRepository,
- private val libraryCoreService: LibraryCoreService,
private val libraryScanService: LibraryScanService,
- private val gameService: GameService
) {
companion object {
private val log = KotlinLogging.logger {}
/* Websockets */
- private val libraryEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
+ private val libraryUserEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
+ private val libraryAdminEvents = Sinks.many().multicast().onBackpressureBuffer(1024, false)
- fun subscribeToLibraryEvents(): Flux> {
- log.debug { "New subscription for libraryEvents" }
- return libraryEvents.asFlux()
+ fun subscribeUser(): Flux> {
+ log.debug { "New user subscription for libraryEvents" }
+ return libraryUserEvents.asFlux()
.buffer(100.milliseconds.toJavaDuration())
.doOnSubscribe {
- log.debug { "Subscriber added to libraryEvents [${libraryEvents.currentSubscriberCount()}]" }
+ log.debug { "Subscriber added to user libraryUserEvents [${libraryUserEvents.currentSubscriberCount()}]" }
}
.doFinally {
- log.debug { "Subscriber removed from libraryEvents with signal type $it [${libraryEvents.currentSubscriberCount()}]" }
+ log.debug { "Subscriber removed from user libraryUserEvents with signal type $it [${libraryUserEvents.currentSubscriberCount()}]" }
}
}
- fun emit(event: LibraryEvent) {
- libraryEvents.tryEmitNext(event)
+ fun subscribeAdmin(): Flux> {
+ log.debug { "New admin subscription for libraryEvents" }
+ return libraryAdminEvents.asFlux()
+ .buffer(100.milliseconds.toJavaDuration())
+ .doOnSubscribe {
+ log.debug { "Subscriber added to admin libraryAdminEvents [${libraryAdminEvents.currentSubscriberCount()}]" }
+ }
+ .doFinally {
+ log.debug { "Subscriber removed from admin libraryAdminEvents with signal type $it [${libraryAdminEvents.currentSubscriberCount()}]" }
+ }
+ }
+
+ fun emitUser(event: LibraryUserEvent) {
+ libraryUserEvents.tryEmitNext(event)
+ }
+
+ fun emitAdmin(event: LibraryAdminEvent) {
+ libraryAdminEvents.tryEmitNext(event)
}
}
@@ -52,7 +65,7 @@ class LibraryService(
*/
fun getAll(): List {
val entities = libraryRepository.findAll()
- return entities.map { it.toDto() }
+ return entities.toDtos()
}
/**
@@ -70,14 +83,21 @@ class LibraryService(
* @param library: The library to create or update.
* @return The created or updated LibraryDto object.
*/
- fun create(library: LibraryDto, scanAfterCreation: Boolean) {
+ fun create(library: LibraryAdminDto, scanAfterCreation: Boolean) {
// Check for duplicate directories before creating a new library
checkForDuplicateDirectories(library.directories.map { it.internalPath })
- val newLibrary = libraryRepository.save(libraryCoreService.toEntity(library))
+ var newLibrary = Library(
+ name = library.name,
+ directories = library.directories.distinctBy { it.internalPath }.map {
+ DirectoryMapping(internalPath = it.internalPath, externalPath = it.externalPath)
+ }.toMutableList(),
+ )
+
+ newLibrary = libraryRepository.save(newLibrary)
if (scanAfterCreation) {
- libraryScanService.triggerScan(ScanType.QUICK, listOf(newLibrary.toDto()))
+ libraryScanService.triggerScan(ScanType.QUICK, listOf(newLibrary.id!!))
}
}
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryDto.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryDto.kt
index 4f77a09..77d565b 100644
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryDto.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryDto.kt
@@ -1,10 +1,26 @@
package org.gameyfin.app.libraries.dto
-data class LibraryDto(
- val id: Long,
- val name: String,
+import com.fasterxml.jackson.annotation.JsonInclude
+
+interface LibraryDto {
+ val id: Long
+ val name: String
+ val games: List?
+}
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class LibraryUserDto(
+ override val id: Long,
+ override val name: String,
+ override val games: List?
+) : LibraryDto
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+data class LibraryAdminDto(
+ override val id: Long,
+ override val name: String,
val directories: List,
- val games: List?,
+ override val games: List?,
val stats: LibraryStatsDto?,
- val unmatchedPaths: List? = emptyList()
-)
\ No newline at end of file
+ val unmatchedPaths: List = emptyList()
+) : LibraryDto
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryEvent.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryEvent.kt
deleted file mode 100644
index 930c908..0000000
--- a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryEvent.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.gameyfin.app.libraries.dto
-
-sealed class LibraryEvent {
- abstract val type: String
-
- data class Created(val library: LibraryDto, override val type: String = "created") : LibraryEvent()
- data class Updated(val library: LibraryDto, override val type: String = "updated") : LibraryEvent()
- data class Deleted(val libraryId: Long, override val type: String = "deleted") : LibraryEvent()
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryEvents.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryEvents.kt
new file mode 100644
index 0000000..512f2d2
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/dto/LibraryEvents.kt
@@ -0,0 +1,17 @@
+package org.gameyfin.app.libraries.dto
+
+sealed interface LibraryEvent {
+ val type: String
+}
+
+sealed class LibraryUserEvent : LibraryEvent {
+ data class Created(val library: LibraryUserDto, override val type: String = "created") : LibraryUserEvent()
+ data class Updated(val library: LibraryUserDto, override val type: String = "updated") : LibraryUserEvent()
+ data class Deleted(val libraryId: Long, override val type: String = "deleted") : LibraryUserEvent()
+}
+
+sealed class LibraryAdminEvent : LibraryEvent {
+ data class Created(val library: LibraryAdminDto, override val type: String = "created") : LibraryAdminEvent()
+ data class Updated(val library: LibraryAdminDto, override val type: String = "updated") : LibraryAdminEvent()
+ data class Deleted(val libraryId: Long, override val type: String = "deleted") : LibraryAdminEvent()
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..a73f0d7
--- /dev/null
+++ b/app/src/main/kotlin/org/gameyfin/app/libraries/extensions/LibraryExtensions.kt
@@ -0,0 +1,44 @@
+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.*
+
+
+fun Library.toDto(): LibraryDto {
+ return if (isCurrentUserAdmin()) {
+ this.toAdminDto()
+ } else {
+ this.toUserDto()
+ }
+}
+
+fun Collection.toDtos(): List {
+ return if (isCurrentUserAdmin()) {
+ this.map { it.toAdminDto() }
+ } else {
+ this.map { it.toUserDto() }
+ }
+}
+
+fun Library.toUserDto(): LibraryUserDto {
+ return LibraryUserDto(
+ id = this.id!!,
+ name = this.name,
+ games = this.games.mapNotNull { it.id }
+ )
+}
+
+fun Library.toAdminDto(): LibraryAdminDto {
+ return LibraryAdminDto(
+ id = this.id!!,
+ name = this.name,
+ directories = this.directories.map { DirectoryMappingDto(it.internalPath, it.externalPath) },
+ games = this.games.mapNotNull { it.id },
+ stats = LibraryStatsDto(
+ gamesCount = this.games.size,
+ downloadedGamesCount = this.games.sumOf { it.metadata.downloadCount }
+ ),
+ unmatchedPaths = this.unmatchedPaths
+ )
+}
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 7a84315..82ec4ab 100644
--- a/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
+++ b/app/src/main/kotlin/org/gameyfin/app/users/UserService.kt
@@ -109,15 +109,6 @@ class UserService(
return toUserInfo(user)
}
- fun getCurrentUser(): org.gameyfin.app.users.entities.User {
- val auth = SecurityContextHolder.getContext().authentication
- if (auth.principal is OidcUser) {
- return userRepository.findByOidcProviderId((auth.principal as OidcUser).subject)
- ?: throw UsernameNotFoundException("OIDC user not found")
- }
- return getByUsernameNonNull(auth.name)
- }
-
fun getAvatar(username: String): Image? {
val user = getByUsernameNonNull(username)
return user.avatar
diff --git a/app/src/main/kotlin/org/gameyfin/app/users/util/UserDetailsExtensions.kt b/app/src/main/kotlin/org/gameyfin/app/users/util/UserDetailsExtensions.kt
deleted file mode 100644
index 6f17d81..0000000
--- a/app/src/main/kotlin/org/gameyfin/app/users/util/UserDetailsExtensions.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.gameyfin.app.users.util
-
-import org.gameyfin.app.core.Role
-import org.gameyfin.app.users.entities.User
-import org.springframework.security.core.userdetails.UserDetails
-
-fun User.hasRole(role: Role): Boolean {
- return role.roleName in this.roles.map { r -> r.roleName }
-}
-
-fun UserDetails.hasRole(role: Role): Boolean {
- return role.roleName in this.authorities.map { a -> a.authority }
-}
-
-fun UserDetails.isAdmin(): Boolean {
- return hasRole(Role.SUPERADMIN) || hasRole(Role.ADMIN)
-}
-
-fun User.isAdmin(): Boolean {
- return hasRole(Role.SUPERADMIN) || hasRole(Role.ADMIN)
-}
\ No newline at end of file