BE implementation of request system

This commit is contained in:
grimsi
2025-09-01 13:01:17 +02:00
parent 6b81d3904c
commit 0f2b45f5e9
29 changed files with 233 additions and 87 deletions
@@ -1,10 +1,10 @@
package org.gameyfin.app.core.filesystem package org.gameyfin.app.core.filesystem
import org.gameyfin.app.config.ConfigService
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.apache.commons.io.FilenameUtils import org.apache.commons.io.FilenameUtils
import org.gameyfin.app.config.ConfigProperties 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 org.springframework.stereotype.Service
import java.io.File import java.io.File
import java.nio.file.FileSystems import java.nio.file.FileSystems
@@ -22,7 +22,7 @@ import org.gameyfin.app.games.dto.*
import org.gameyfin.app.games.entities.* import org.gameyfin.app.games.entities.*
import org.gameyfin.app.games.extensions.toDtos import org.gameyfin.app.games.extensions.toDtos
import org.gameyfin.app.games.repositories.GameRepository 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.media.ImageService
import org.gameyfin.app.users.UserService import org.gameyfin.app.users.UserService
import org.gameyfin.pluginapi.gamemetadata.* import org.gameyfin.pluginapi.gamemetadata.*
@@ -1,7 +1,7 @@
package org.gameyfin.app.games.entities package org.gameyfin.app.games.entities
import jakarta.persistence.* 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.GameFeature
import org.gameyfin.pluginapi.gamemetadata.Genre import org.gameyfin.pluginapi.gamemetadata.Genre
import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective import org.gameyfin.pluginapi.gamemetadata.PlayerPerspective
@@ -12,19 +12,19 @@ import org.gameyfin.app.games.extensions.toUserDto
class GameEntityListener { class GameEntityListener {
@PostPersist @PostPersist
fun created(game: Game) { fun created(game: Game) {
GameService.Companion.emitUser(GameUserEvent.Created(game.toUserDto())) GameService.emitUser(GameUserEvent.Created(game.toUserDto()))
GameService.Companion.emitAdmin(GameAdminEvent.Created(game.toAdminDto())) GameService.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
} }
@PostUpdate @PostUpdate
fun updated(game: Game) { fun updated(game: Game) {
GameService.Companion.emitUser(GameUserEvent.Updated(game.toUserDto())) GameService.emitUser(GameUserEvent.Updated(game.toUserDto()))
GameService.Companion.emitAdmin(GameAdminEvent.Updated(game.toAdminDto())) GameService.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
} }
@PostRemove @PostRemove
fun deleted(game: Game) { fun deleted(game: Game) {
GameService.Companion.emitUser(GameUserEvent.Deleted(game.id!!)) GameService.emitUser(GameUserEvent.Deleted(game.id!!))
GameService.Companion.emitAdmin(GameAdminEvent.Deleted(game.id!!)) GameService.emitAdmin(GameAdminEvent.Deleted(game.id!!))
} }
} }
@@ -2,6 +2,7 @@ package org.gameyfin.app.libraries
import org.gameyfin.app.games.GameService import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.entities.Game import org.gameyfin.app.games.entities.Game
import org.gameyfin.app.libraries.entities.Library
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.time.Instant import java.time.Instant
@@ -1,5 +1,6 @@
package org.gameyfin.app.libraries 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.JpaRepository
import org.springframework.data.jpa.repository.Query import org.springframework.data.jpa.repository.Query
@@ -4,9 +4,8 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.core.filesystem.FilesystemService import org.gameyfin.app.core.filesystem.FilesystemService
import org.gameyfin.app.games.GameService import org.gameyfin.app.games.GameService
import org.gameyfin.app.games.entities.Game import org.gameyfin.app.games.entities.Game
import org.gameyfin.app.libraries.dto.LibraryScanProgress import org.gameyfin.app.libraries.dto.*
import org.gameyfin.app.libraries.dto.LibraryScanStatus import org.gameyfin.app.libraries.entities.Library
import org.gameyfin.app.libraries.dto.LibraryScanStep
import org.gameyfin.app.libraries.enums.ScanType import org.gameyfin.app.libraries.enums.ScanType
import org.gameyfin.app.libraries.scan.* import org.gameyfin.app.libraries.scan.*
import org.gameyfin.app.media.ImageService import org.gameyfin.app.media.ImageService
@@ -3,6 +3,8 @@ package org.gameyfin.app.libraries
import com.vaadin.hilla.exception.EndpointException import com.vaadin.hilla.exception.EndpointException
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.libraries.dto.* 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.enums.ScanType
import org.gameyfin.app.libraries.extensions.toDtos import org.gameyfin.app.libraries.extensions.toDtos
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
@@ -1,6 +1,5 @@
package org.gameyfin.app.libraries.dto package org.gameyfin.app.libraries.dto
import org.gameyfin.app.libraries.LibraryScanResult
import org.gameyfin.app.libraries.enums.ScanType import org.gameyfin.app.libraries.enums.ScanType
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@@ -1,4 +1,4 @@
package org.gameyfin.app.libraries package org.gameyfin.app.libraries.dto
interface LibraryScanResult { interface LibraryScanResult {
/** /**
@@ -1,4 +1,4 @@
package org.gameyfin.app.libraries package org.gameyfin.app.libraries.entities
import jakarta.persistence.* import jakarta.persistence.*
@@ -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 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.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp import org.hibernate.annotations.UpdateTimestamp
import java.time.Instant import java.time.Instant
@@ -1,9 +1,8 @@
package org.gameyfin.app.games.entities package org.gameyfin.app.libraries.entities
import jakarta.persistence.PostPersist import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate import jakarta.persistence.PostUpdate
import org.gameyfin.app.libraries.Library
import org.gameyfin.app.libraries.LibraryService import org.gameyfin.app.libraries.LibraryService
import org.gameyfin.app.libraries.dto.LibraryAdminEvent import org.gameyfin.app.libraries.dto.LibraryAdminEvent
import org.gameyfin.app.libraries.dto.LibraryUserEvent import org.gameyfin.app.libraries.dto.LibraryUserEvent
@@ -1,8 +1,8 @@
package org.gameyfin.app.libraries.extensions package org.gameyfin.app.libraries.extensions
import org.gameyfin.app.core.security.isCurrentUserAdmin import org.gameyfin.app.core.security.isCurrentUserAdmin
import org.gameyfin.app.libraries.Library
import org.gameyfin.app.libraries.dto.* import org.gameyfin.app.libraries.dto.*
import org.gameyfin.app.libraries.entities.Library
fun Library.toDto(): LibraryDto { fun Library.toDto(): LibraryDto {
@@ -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<List<GameRequestEvent>> {
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)
}
}
@@ -1,5 +1,6 @@
package org.gameyfin.app.requests 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.JpaRepository
interface GameRequestRepository : JpaRepository<GameRequest, Long> interface GameRequestRepository : JpaRepository<GameRequest, Long>
@@ -2,8 +2,11 @@ package org.gameyfin.app.requests
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.gameyfin.app.core.security.getCurrentAuth 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.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.requests.status.GameRequestStatus
import org.gameyfin.app.users.UserService import org.gameyfin.app.users.UserService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -22,9 +25,9 @@ class GameRequestService(
private val log = KotlinLogging.logger {} private val log = KotlinLogging.logger {}
/* Websockets */ /* Websockets */
private val gameRequestEvents = Sinks.many().multicast().onBackpressureBuffer<GameUserEvent>(1024, false) private val gameRequestEvents = Sinks.many().multicast().onBackpressureBuffer<GameRequestEvent>(1024, false)
fun subscribe(): Flux<List<GameUserEvent>> { fun subscribe(): Flux<List<GameRequestEvent>> {
log.debug { "New user subscription for gameRequestEvents" } log.debug { "New user subscription for gameRequestEvents" }
return gameRequestEvents.asFlux() return gameRequestEvents.asFlux()
.buffer(100.milliseconds.toJavaDuration()) .buffer(100.milliseconds.toJavaDuration())
@@ -36,11 +39,16 @@ class GameRequestService(
} }
} }
fun emit(event: GameUserEvent) { fun emit(event: GameRequestEvent) {
gameRequestEvents.tryEmitNext(event) gameRequestEvents.tryEmitNext(event)
} }
} }
fun getAll(): List<GameRequestDto> {
val entities = gameRequestRepository.findAll()
return entities.toDtos()
}
fun createRequest(gameRequest: GameRequestCreationDto) { fun createRequest(gameRequest: GameRequestCreationDto) {
val currentUser = userService.getByUsername(getCurrentAuth().name) val currentUser = userService.getByUsername(getCurrentAuth().name)
@@ -54,4 +62,36 @@ class GameRequestService(
gameRequestRepository.save(gameRequest) 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)
}
} }
@@ -1,13 +1,18 @@
package org.gameyfin.app.requests.dto package org.gameyfin.app.requests.dto
import org.gameyfin.app.requests.entities.ExternalProviderIds
import org.gameyfin.app.requests.status.GameRequestStatus 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 import java.time.Instant
class GameRequestDto( class GameRequestDto(
val id: Long, val id: Long,
val title: String, val title: String,
val release: Instant, val release: Instant,
val externalProviderIds: ExternalProviderIds,
val status: GameRequestStatus, val status: GameRequestStatus,
val requester: UserInfoAdminDto val requester: UserInfoDto?,
val voters: List<UserInfoDto>,
val createdAt: Instant?,
val updatedAt: Instant?
) )
@@ -1,42 +1,45 @@
package org.gameyfin.app.requests package org.gameyfin.app.requests.entities
import jakarta.persistence.* import jakarta.persistence.*
import org.gameyfin.app.requests.status.GameRequestStatus import org.gameyfin.app.requests.status.GameRequestStatus
import org.gameyfin.app.users.entities.User import org.gameyfin.app.users.entities.User
import org.hibernate.annotations.CreationTimestamp import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp import org.hibernate.annotations.UpdateTimestamp
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.Instant import java.time.Instant
typealias ExternalProviderIds = Map<String, String> typealias ExternalProviderIds = Map<String, String>
@Entity @Entity
@EntityListeners(GameRequestEntityListener::class, AuditingEntityListener::class)
class GameRequest( class GameRequest(
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null, 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) @Column(nullable = false)
val title: String, val title: String,
@Column(nullable = false) @Column(nullable = false)
val release: Instant, val release: Instant,
@ElementCollection
val externalProviderIds: ExternalProviderIds,
@Column(nullable = false) @Column(nullable = false)
var status: GameRequestStatus, var status: GameRequestStatus,
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
var requester: User? = null, var requester: User? = null,
@OneToMany
var voters: MutableList<User> = mutableListOf(), var voters: MutableList<User> = mutableListOf(),
@ElementCollection @CreationTimestamp
val externalProviderIds: ExternalProviderIds @Column(nullable = false, updatable = false)
var createdAt: Instant? = null,
@UpdateTimestamp
@Column(nullable = false)
var updatedAt: Instant? = null
) )
@@ -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!!))
}
}
@@ -1,7 +1,8 @@
package org.gameyfin.app.requests.extensions package org.gameyfin.app.requests.extensions
import org.gameyfin.app.requests.GameRequest
import org.gameyfin.app.requests.dto.GameRequestDto 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 { fun GameRequest.toDto(): GameRequestDto {
return GameRequestDto( return GameRequestDto(
@@ -10,6 +11,13 @@ fun GameRequest.toDto(): GameRequestDto {
release = this.release, release = this.release,
externalProviderIds = this.externalProviderIds, externalProviderIds = this.externalProviderIds,
status = this.status, 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<GameRequest>.toDtos(): List<GameRequestDto> {
return this.map { it.toDto() }
}
@@ -3,7 +3,7 @@ package org.gameyfin.app.setup
import com.vaadin.flow.server.auth.AnonymousAllowed import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint import com.vaadin.hilla.Endpoint
import com.vaadin.hilla.exception.EndpointException 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 import org.gameyfin.app.users.dto.UserRegistrationDto
@Endpoint @Endpoint
@@ -16,7 +16,7 @@ class SetupEndpoint(
} }
@AnonymousAllowed @AnonymousAllowed
fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoAdminDto { fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): ExtendedUserInfoDto {
if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed") if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed")
return setupService.createInitialAdminUser(superAdminRegistration) return setupService.createInitialAdminUser(superAdminRegistration)
} }
@@ -3,9 +3,10 @@ package org.gameyfin.app.setup
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.users.RoleService import org.gameyfin.app.users.RoleService
import org.gameyfin.app.users.UserService 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.dto.UserRegistrationDto
import org.gameyfin.app.users.entities.User import org.gameyfin.app.users.entities.User
import org.gameyfin.app.users.extensions.toExtendedUserInfoDto
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
@@ -26,7 +27,7 @@ class SetupService(
/** /**
* Creates the initial user with Super-Admin permissions * Creates the initial user with Super-Admin permissions
*/ */
fun createInitialAdminUser(registration: UserRegistrationDto): UserInfoAdminDto { fun createInitialAdminUser(registration: UserRegistrationDto): ExtendedUserInfoDto {
val superAdmin = User( val superAdmin = User(
username = registration.username, username = registration.username,
password = registration.password, password = registration.password,
@@ -36,6 +37,6 @@ class SetupService(
) )
val user = userService.registerOrUpdateUser(superAdmin) val user = userService.registerOrUpdateUser(superAdmin)
return userService.toUserInfo(user) return user.toExtendedUserInfoDto()
} }
} }
@@ -6,7 +6,7 @@ import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed import jakarta.annotation.security.RolesAllowed
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
import org.gameyfin.app.core.security.getCurrentAuth 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.dto.UserUpdateDto
import org.gameyfin.app.users.enums.RoleAssignmentResult import org.gameyfin.app.users.enums.RoleAssignmentResult
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
@@ -18,7 +18,7 @@ class UserEndpoint(
private val roleService: RoleService private val roleService: RoleService
) { ) {
@AnonymousAllowed @AnonymousAllowed
fun getUserInfo(): UserInfoAdminDto? { fun getUserInfo(): ExtendedUserInfoDto? {
val auth = getCurrentAuth() val auth = getCurrentAuth()
if (!auth.isAuthenticated || auth.principal == "anonymousUser") return null if (!auth.isAuthenticated || auth.principal == "anonymousUser") return null
return userService.getUserInfo() return userService.getUserInfo()
@@ -36,7 +36,7 @@ class UserEndpoint(
} }
@RolesAllowed(Role.Names.ADMIN) @RolesAllowed(Role.Names.ADMIN)
fun getAllUsers(): List<UserInfoAdminDto> { fun getAllUsers(): List<ExtendedUserInfoDto> {
return userService.getAllUsers() return userService.getAllUsers()
} }
@@ -9,15 +9,15 @@ import org.gameyfin.app.core.events.*
import org.gameyfin.app.core.security.getCurrentAuth import org.gameyfin.app.core.security.getCurrentAuth
import org.gameyfin.app.games.entities.Image import org.gameyfin.app.games.entities.Image
import org.gameyfin.app.media.ImageService 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.UserRegistrationDto
import org.gameyfin.app.users.dto.UserUpdateDto import org.gameyfin.app.users.dto.UserUpdateDto
import org.gameyfin.app.users.emailconfirmation.EmailConfirmationService import org.gameyfin.app.users.emailconfirmation.EmailConfirmationService
import org.gameyfin.app.users.enums.RoleAssignmentResult 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.gameyfin.app.users.persistence.UserRepository
import org.springframework.context.ApplicationEventPublisher 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.User
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UserDetailsService
@@ -56,7 +56,7 @@ class UserService(
true, true,
true, true,
true, true,
toAuthorities(user.roles) user.roles.toAuthorities()
) )
} }
@@ -67,7 +67,7 @@ class UserService(
true, true,
true, 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? = fun findByOidcProviderId(oidcProviderId: String): org.gameyfin.app.users.entities.User? =
userRepository.findByOidcProviderId(oidcProviderId) userRepository.findByOidcProviderId(oidcProviderId)
fun getAllUsers(): List<UserInfoAdminDto> { fun getAllUsers(): List<ExtendedUserInfoDto> {
return userRepository.findAll().map { u -> toUserInfo(u) } return userRepository.findAll().map { it.toExtendedUserInfoDto() }
} }
fun getByEmail(email: String): org.gameyfin.app.users.entities.User? { 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'") return userRepository.findByUsername(username) ?: throw UsernameNotFoundException("Unknown user '$username'")
} }
fun getUserInfo(): UserInfoAdminDto { fun getUserInfo(): ExtendedUserInfoDto {
val auth = getCurrentAuth() val auth = getCurrentAuth()
val principal = auth.principal val principal = auth.principal
if (principal is OidcUser) { if (principal is OidcUser) {
val oidcUser = org.gameyfin.app.users.entities.User(principal) val oidcUser = org.gameyfin.app.users.entities.User(principal)
val userInfoDto = toUserInfo(oidcUser) val userInfoDto = oidcUser.toExtendedUserInfoDto()
userInfoDto.roles = roleService.extractGrantedAuthorities(principal.authorities) userInfoDto.roles = roleService.extractGrantedAuthorities(principal.authorities)
.mapNotNull { Role.Companion.safeValueOf(it.authority) } .mapNotNull { Role.safeValueOf(it.authority) }
return userInfoDto return userInfoDto
} }
val user = getByUsernameNonNull(auth.name) val user = getByUsernameNonNull(auth.name)
return toUserInfo(user) return user.toExtendedUserInfoDto()
} }
fun getAvatar(username: String): Image? { fun getAvatar(username: String): Image? {
@@ -158,7 +158,7 @@ class UserService(
RegistrationAttemptWithExistingEmailEvent( RegistrationAttemptWithExistingEmailEvent(
this, this,
it, it,
Utils.Companion.getBaseUrl() Utils.getBaseUrl()
) )
) )
return return
@@ -179,12 +179,12 @@ class UserService(
if (adminNeedsToApprove) { if (adminNeedsToApprove) {
eventPublisher.publishEvent(UserRegistrationWaitingForApprovalEvent(this, user)) eventPublisher.publishEvent(UserRegistrationWaitingForApprovalEvent(this, user))
} else { } else {
eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.Companion.getBaseUrl())) eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.getBaseUrl()))
} }
if (!user.emailConfirmed) { if (!user.emailConfirmed) {
val token = emailConfirmationService.generate(user) 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.email = it
user.emailConfirmed = false user.emailConfirmed = false
val token = emailConfirmationService.generate(user) val token = emailConfirmationService.generate(user)
eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.Companion.getBaseUrl())) eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.getBaseUrl()))
} }
userRepository.save(user) userRepository.save(user)
@@ -246,7 +246,7 @@ class UserService(
return RoleAssignmentResult.TARGET_POWER_LEVEL_TOO_HIGH 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 newAssignedRolesLevel = roleService.getHighestRole(newAssignedRoles).powerLevel
val currentUserLevel = roleService.getHighestRoleFromAuthorities(currentUser.authorities).powerLevel val currentUserLevel = roleService.getHighestRoleFromAuthorities(currentUser.authorities).powerLevel
@@ -276,29 +276,12 @@ class UserService(
val user = getByUsernameNonNull(username) val user = getByUsernameNonNull(username)
user.enabled = enabled user.enabled = enabled
userRepository.save(user) userRepository.save(user)
eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.Companion.getBaseUrl())) eventPublisher.publishEvent(AccountStatusChangedEvent(this, user, Utils.getBaseUrl()))
} }
fun deleteUser(username: String) { fun deleteUser(username: String) {
val user = getByUsernameNonNull(username) val user = getByUsernameNonNull(username)
userRepository.delete(user) userRepository.delete(user)
eventPublisher.publishEvent(AccountDeletedEvent(this, user, Utils.Companion.getBaseUrl())) eventPublisher.publishEvent(AccountDeletedEvent(this, user, Utils.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<Role>): List<GrantedAuthority> {
return roles.map { r -> SimpleGrantedAuthority(r.roleName) }
} }
} }
@@ -2,7 +2,7 @@ package org.gameyfin.app.users.dto
import org.gameyfin.app.core.Role import org.gameyfin.app.core.Role
data class UserInfoAdminDto( data class ExtendedUserInfoDto(
val username: String, val username: String,
val managedBySso: Boolean, val managedBySso: Boolean,
val email: String, val email: String,
@@ -1,6 +1,6 @@
package org.gameyfin.app.users.dto package org.gameyfin.app.users.dto
data class UserInfoUserDto( data class UserInfoDto(
val username: String, val username: String,
val hasAvatar: Boolean, val hasAvatar: Boolean,
val avatarId: Long? = null, val avatarId: Long? = null,
@@ -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<Role>.toAuthorities(): List<GrantedAuthority> {
return this.map { r -> SimpleGrantedAuthority(r.roleName) }
}