mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 00:30:02 +00:00
Migrate to ImageEndpoint
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="UI debug" type="JavascriptDebugType" uri="http://localhost:8080">
|
<configuration default="false" name="UI debug" type="JavascriptDebugType" engineId="37cae5b9-e8b2-4949-9172-aafa37fbc09c" uri="http://localhost:8080">
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.grimsi.gameyfin.core.annotations
|
package de.grimsi.gameyfin.core.annotations
|
||||||
|
|
||||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||||
|
import kotlin.annotation.AnnotationTarget.CLASS
|
||||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,6 +10,6 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
|
|||||||
* One example would be the main library view.
|
* One example would be the main library view.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Target(FUNCTION)
|
@Target(FUNCTION, CLASS)
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
annotation class DynamicPublicAccess
|
annotation class DynamicPublicAccess
|
||||||
@@ -66,7 +66,7 @@ class GameService(
|
|||||||
|
|
||||||
var game = toEntity(metadata, path, plugin)
|
var game = toEntity(metadata, path, plugin)
|
||||||
game = createOrUpdate(game)
|
game = createOrUpdate(game)
|
||||||
|
|
||||||
return toDto(game)
|
return toDto(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ class GameService(
|
|||||||
keywords = game.keywords.toList(),
|
keywords = game.keywords.toList(),
|
||||||
features = game.features.map { it.name },
|
features = game.features.map { it.name },
|
||||||
perspectives = game.perspectives.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() },
|
videoUrls = game.videoUrls.map { it.toString() },
|
||||||
source = game.source.pluginId
|
source = game.source.pluginId
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class GameDto(
|
|||||||
val keywords: List<String>?,
|
val keywords: List<String>?,
|
||||||
val features: List<String>?,
|
val features: List<String>?,
|
||||||
val perspectives: List<String>?,
|
val perspectives: List<String>?,
|
||||||
val images: List<String>?,
|
val images: List<Long>?,
|
||||||
val videoUrls: List<String>?,
|
val videoUrls: List<String>?,
|
||||||
val source: String?
|
val source: String?
|
||||||
)
|
)
|
||||||
@@ -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<InputStreamResource>? {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<InputStreamResource>? {
|
||||||
|
return getImageContent(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/cover/{id}")
|
||||||
|
fun getCover(@PathVariable("id") id: Long): ResponseEntity<InputStreamResource>? {
|
||||||
|
return getImageContent(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/avatar")
|
||||||
|
fun getAvatarByUsername(@RequestParam username: String): ResponseEntity<InputStreamResource>? {
|
||||||
|
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<InputStreamResource>? {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import de.grimsi.gameyfin.games.entities.Image
|
|||||||
import de.grimsi.gameyfin.games.entities.ImageType
|
import de.grimsi.gameyfin.games.entities.ImageType
|
||||||
import de.grimsi.gameyfin.games.repositories.ImageContentStore
|
import de.grimsi.gameyfin.games.repositories.ImageContentStore
|
||||||
import de.grimsi.gameyfin.games.repositories.ImageRepository
|
import de.grimsi.gameyfin.games.repositories.ImageRepository
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@@ -13,14 +14,24 @@ class ImageService(
|
|||||||
private val imageContentStore: ImageContentStore
|
private val imageContentStore: ImageContentStore
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
fun getImage(id: Long): Image? {
|
||||||
|
return imageRepository.findByIdOrNull(id)
|
||||||
|
}
|
||||||
|
|
||||||
fun createFile(type: ImageType, content: InputStream, mimeType: String): Image {
|
fun createFile(type: ImageType, content: InputStream, mimeType: String): Image {
|
||||||
val image = Image(type = type, mimeType = mimeType)
|
val image = Image(type = type, mimeType = mimeType)
|
||||||
imageRepository.save(image)
|
imageRepository.save(image)
|
||||||
return imageContentStore.setContent(image, content)
|
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)
|
return imageContentStore.getContent(image)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteFile(image: Image) {
|
fun deleteFile(image: Image) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import de.grimsi.gameyfin.core.Role
|
|||||||
import de.grimsi.gameyfin.core.Utils
|
import de.grimsi.gameyfin.core.Utils
|
||||||
import de.grimsi.gameyfin.core.events.*
|
import de.grimsi.gameyfin.core.events.*
|
||||||
import de.grimsi.gameyfin.games.entities.Image
|
import de.grimsi.gameyfin.games.entities.Image
|
||||||
import de.grimsi.gameyfin.games.entities.ImageType
|
|
||||||
import de.grimsi.gameyfin.media.ImageService
|
import de.grimsi.gameyfin.media.ImageService
|
||||||
import de.grimsi.gameyfin.users.dto.UserInfoDto
|
import de.grimsi.gameyfin.users.dto.UserInfoDto
|
||||||
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
|
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.crypto.password.PasswordEncoder
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.multipart.MultipartFile
|
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -105,15 +103,9 @@ class UserService(
|
|||||||
return user.avatar
|
return user.avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAvatar(username: String, file: MultipartFile) {
|
fun updateAvatar(username: String, newAvatar: Image) {
|
||||||
val user = getByUsernameNonNull(username)
|
val user = getByUsernameNonNull(username)
|
||||||
|
user.avatar = newAvatar
|
||||||
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!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
userRepository.save(user)
|
userRepository.save(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +119,11 @@ class UserService(
|
|||||||
userRepository.save(user)
|
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 {
|
fun registerOrUpdateUser(user: User): User {
|
||||||
user.password = passwordEncoder.encode(user.password)
|
user.password = passwordEncoder.encode(user.password)
|
||||||
return userRepository.save(user)
|
return userRepository.save(user)
|
||||||
@@ -271,7 +268,7 @@ class UserService(
|
|||||||
emailConfirmed = user.emailConfirmed,
|
emailConfirmed = user.emailConfirmed,
|
||||||
isEnabled = user.enabled,
|
isEnabled = user.enabled,
|
||||||
hasAvatar = user.avatar != null,
|
hasAvatar = user.avatar != null,
|
||||||
avatarId = user.avatar?.contentId,
|
avatarId = user.avatar?.id,
|
||||||
managedBySso = user.oidcProviderId != null,
|
managedBySso = user.oidcProviderId != null,
|
||||||
roles = user.roles
|
roles = user.roles
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ data class UserInfoDto(
|
|||||||
val emailConfirmed: Boolean,
|
val emailConfirmed: Boolean,
|
||||||
val isEnabled: Boolean,
|
val isEnabled: Boolean,
|
||||||
val hasAvatar: Boolean,
|
val hasAvatar: Boolean,
|
||||||
val avatarId: String? = null,
|
val avatarId: Long? = null,
|
||||||
var roles: Set<Role>
|
var roles: Set<Role>
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user