diff --git a/.run/UI debug.run.xml b/.run/UI debug.run.xml
index 1a2afd9..ccfbe55 100644
--- a/.run/UI debug.run.xml
+++ b/.run/UI debug.run.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicPublicAccess.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicPublicAccess.kt
index 2c4b6b7..92076a3 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicPublicAccess.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/annotations/DynamicPublicAccess.kt
@@ -1,6 +1,7 @@
package de.grimsi.gameyfin.core.annotations
import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
/**
@@ -9,6 +10,6 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
* One example would be the main library view.
*/
-@Target(FUNCTION)
+@Target(FUNCTION, CLASS)
@Retention(RUNTIME)
annotation class DynamicPublicAccess
\ No newline at end of file
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt
index c0063f7..6784c34 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt
@@ -66,7 +66,7 @@ class GameService(
var game = toEntity(metadata, path, plugin)
game = createOrUpdate(game)
-
+
return toDto(game)
}
@@ -100,7 +100,7 @@ class GameService(
keywords = game.keywords.toList(),
features = game.features.map { it.name },
perspectives = game.perspectives.map { it.name },
- images = game.images.mapNotNull { it.contentId },
+ images = game.images.mapNotNull { it.id },
videoUrls = game.videoUrls.map { it.toString() },
source = game.source.pluginId
)
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt
index e4e8905..3e5d52f 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt
@@ -16,7 +16,7 @@ class GameDto(
val keywords: List?,
val features: List?,
val perspectives: List?,
- val images: List?,
+ val images: List?,
val videoUrls: List?,
val source: String?
)
\ No newline at end of file
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageController.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageController.kt
deleted file mode 100644
index 2b6f979..0000000
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageController.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package de.grimsi.gameyfin.media
-
-import de.grimsi.gameyfin.core.Role
-import de.grimsi.gameyfin.users.UserService
-import jakarta.annotation.security.PermitAll
-import jakarta.annotation.security.RolesAllowed
-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
-
-
-@RestController
-@RequestMapping("/images")
-class ImageController(
- private val userService: UserService,
- private val imageService: ImageService
-) {
-
- @PostMapping("/avatar/upload")
- fun uploadAvatar(@RequestParam("file") file: MultipartFile) {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
- userService.setAvatar(auth.name, file)
- }
-
- @PostMapping("/avatar/delete")
- fun deleteAvatar() {
- val auth: Authentication = SecurityContextHolder.getContext().authentication
- userService.deleteAvatar(auth.name)
- }
-
- @RolesAllowed(Role.Names.ADMIN)
- @PostMapping("/avatar/deleteByName")
- fun deleteAvatarByName(@RequestParam("name") name: String) {
- userService.deleteAvatar(name)
- }
-
- @PermitAll
- @GetMapping("/avatar")
- fun getAvatar(
- @RequestParam("username") username: String
- ): ResponseEntity? {
- val avatar = userService.getAvatar(username) ?: return ResponseEntity.notFound().build()
-
- val file = avatar.let { imageService.getFileContent(it) }
-
- val inputStreamResource = InputStreamResource(file)
- val headers = HttpHeaders()
- headers.contentLength = avatar.contentLength!!
- headers.contentType = MediaType.parseMediaType(avatar.mimeType!!)
-
- return ResponseEntity.ok()
- .headers(headers)
- .body(inputStreamResource)
- }
-}
\ No newline at end of file
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageEndpoint.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageEndpoint.kt
new file mode 100644
index 0000000..38aef29
--- /dev/null
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageEndpoint.kt
@@ -0,0 +1,86 @@
+package de.grimsi.gameyfin.media
+
+import de.grimsi.gameyfin.core.Role
+import de.grimsi.gameyfin.core.annotations.DynamicPublicAccess
+import de.grimsi.gameyfin.games.entities.Image
+import de.grimsi.gameyfin.games.entities.ImageType
+import de.grimsi.gameyfin.users.UserService
+import jakarta.annotation.security.RolesAllowed
+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
+
+@DynamicPublicAccess
+@RestController
+@RequestMapping("/images")
+class ImageEndpoint(
+ private val imageService: ImageService,
+ private val userService: UserService
+) {
+
+ @GetMapping("/screenshot/{id}")
+ fun getScreenshot(@PathVariable("id") id: Long): ResponseEntity? {
+ return getImageContent(id)
+ }
+
+ @GetMapping("/cover/{id}")
+ fun getCover(@PathVariable("id") id: Long): ResponseEntity? {
+ return getImageContent(id)
+ }
+
+ @GetMapping("/avatar")
+ fun getAvatarByUsername(@RequestParam username: String): ResponseEntity? {
+ val avatar = userService.getAvatar(username) ?: return ResponseEntity.notFound().build()
+ if (avatar.id == null) return ResponseEntity.notFound().build()
+ return getImageContent(avatar.id!!)
+ }
+
+ @PostMapping("/avatar/upload")
+ fun uploadAvatar(@RequestParam("file") file: MultipartFile) {
+ val auth: Authentication = SecurityContextHolder.getContext().authentication
+
+ val image: Image = if (userService.hasAvatar(auth.name)) {
+ imageService.createFile(ImageType.AVATAR, file.inputStream, file.contentType!!)
+ } else {
+ val existingAvatar = userService.getAvatar(auth.name)!!
+ imageService.updateFileContent(existingAvatar, file.inputStream, file.contentType!!)
+ }
+
+ userService.updateAvatar(auth.name, image)
+ }
+
+ @PostMapping("/avatar/delete")
+ fun deleteAvatar() {
+ val auth: Authentication = SecurityContextHolder.getContext().authentication
+ userService.deleteAvatar(auth.name)
+ }
+
+ @RolesAllowed(Role.Names.ADMIN)
+ @PostMapping("/avatar/deleteByName")
+ fun deleteAvatarByName(@RequestParam("name") name: String) {
+ userService.deleteAvatar(name)
+ }
+
+ private fun getImageContent(id: Long): ResponseEntity? {
+ val image = imageService.getImage(id) ?: return ResponseEntity.notFound().build()
+
+ val file = image.let { imageService.getFileContent(it) }
+
+ if (file == null) return ResponseEntity.notFound().build()
+
+ val inputStreamResource = InputStreamResource(file)
+
+ val headers = HttpHeaders()
+ headers.contentLength = image.contentLength!!
+ headers.contentType = MediaType.parseMediaType(image.mimeType!!)
+
+ return ResponseEntity.ok()
+ .headers(headers)
+ .body(inputStreamResource)
+ }
+}
\ No newline at end of file
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageService.kt
index 61c9f6f..c6977a9 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageService.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/media/ImageService.kt
@@ -4,6 +4,7 @@ import de.grimsi.gameyfin.games.entities.Image
import de.grimsi.gameyfin.games.entities.ImageType
import de.grimsi.gameyfin.games.repositories.ImageContentStore
import de.grimsi.gameyfin.games.repositories.ImageRepository
+import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import java.io.InputStream
@@ -13,14 +14,24 @@ class ImageService(
private val imageContentStore: ImageContentStore
) {
+ fun getImage(id: Long): Image? {
+ return imageRepository.findByIdOrNull(id)
+ }
+
fun createFile(type: ImageType, content: InputStream, mimeType: String): Image {
val image = Image(type = type, mimeType = mimeType)
imageRepository.save(image)
return imageContentStore.setContent(image, content)
}
- fun getFileContent(image: Image): InputStream {
+ 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) {
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt
index 14a36b8..5f5ede5 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt
@@ -6,7 +6,6 @@ import de.grimsi.gameyfin.core.Role
import de.grimsi.gameyfin.core.Utils
import de.grimsi.gameyfin.core.events.*
import de.grimsi.gameyfin.games.entities.Image
-import de.grimsi.gameyfin.games.entities.ImageType
import de.grimsi.gameyfin.media.ImageService
import de.grimsi.gameyfin.users.dto.UserInfoDto
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
@@ -28,7 +27,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Service
-import org.springframework.web.multipart.MultipartFile
@Service
@@ -105,15 +103,9 @@ class UserService(
return user.avatar
}
- fun setAvatar(username: String, file: MultipartFile) {
+ fun updateAvatar(username: String, newAvatar: Image) {
val user = getByUsernameNonNull(username)
-
- if (user.avatar == null) {
- user.avatar = imageService.createFile(ImageType.AVATAR, file.inputStream, file.contentType!!)
- } else {
- user.avatar = imageService.updateFileContent(user.avatar!!, file.inputStream, file.contentType!!)
- }
-
+ user.avatar = newAvatar
userRepository.save(user)
}
@@ -127,6 +119,11 @@ class UserService(
userRepository.save(user)
}
+ fun hasAvatar(username: String): Boolean {
+ val user = getByUsernameNonNull(username)
+ return user.avatar != null && user.avatar!!.id != null
+ }
+
fun registerOrUpdateUser(user: User): User {
user.password = passwordEncoder.encode(user.password)
return userRepository.save(user)
@@ -271,7 +268,7 @@ class UserService(
emailConfirmed = user.emailConfirmed,
isEnabled = user.enabled,
hasAvatar = user.avatar != null,
- avatarId = user.avatar?.contentId,
+ avatarId = user.avatar?.id,
managedBySso = user.oidcProviderId != null,
roles = user.roles
)
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfoDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfoDto.kt
index 4e9d120..3e68a5c 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfoDto.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/users/dto/UserInfoDto.kt
@@ -9,6 +9,6 @@ data class UserInfoDto(
val emailConfirmed: Boolean,
val isEnabled: Boolean,
val hasAvatar: Boolean,
- val avatarId: String? = null,
+ val avatarId: Long? = null,
var roles: Set
)
\ No newline at end of file