Add FileUpload; Fix Mappers
This commit is contained in:
parent
afd2345cac
commit
31a8532411
|
|
@ -1,11 +1,11 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("org.springframework.boot") version "3.3.0"
|
id("org.springframework.boot") version "3.5.12"
|
||||||
id("io.spring.dependency-management") version "1.1.5"
|
id("io.spring.dependency-management") version "1.1.7"
|
||||||
kotlin("plugin.jpa") version "1.9.24"
|
kotlin("plugin.jpa") version "2.3.20"
|
||||||
kotlin("jvm") version "1.9.24"
|
kotlin("jvm") version "2.3.20"
|
||||||
kotlin("plugin.spring") version "1.9.24"
|
kotlin("plugin.spring") version "2.3.20"
|
||||||
kotlin("plugin.allopen") version "1.9.22"
|
kotlin("plugin.allopen") version "2.3.20"
|
||||||
kotlin("kapt") version "1.9.10"
|
kotlin("kapt") version "2.3.20"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "at.eisibaer"
|
group = "at.eisibaer"
|
||||||
|
|
@ -28,20 +28,19 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
val bcVersion: String = "1.78.1"
|
val bcVersion: String = "1.78.1"
|
||||||
val mapstructVersion: String = "1.6.0"
|
val mapstructVersion: String = "1.7.0.Beta1"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-websocket")
|
implementation("org.springframework.boot:spring-boot-starter-websocket")
|
||||||
implementation("org.springframework.boot:spring-boot-docker-compose")
|
implementation("org.springframework.session:spring-session-jdbc")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-redis")
|
|
||||||
implementation("org.springframework.session:spring-session-data-redis")
|
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.bouncycastle:bcprov-jdk18on:$bcVersion")
|
implementation("org.bouncycastle:bcprov-jdk18on:$bcVersion")
|
||||||
implementation("org.mapstruct:mapstruct:$mapstructVersion")
|
implementation("org.mapstruct:mapstruct:$mapstructVersion")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-metadata-jvm:2.3.20")
|
||||||
compileOnly("org.projectlombok:lombok")
|
compileOnly("org.projectlombok:lombok")
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
runtimeOnly("org.postgresql:postgresql")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
version: "3.1"
|
|
||||||
|
|
||||||
services:
|
|
||||||
redis:
|
|
||||||
image: "redis:alpine"
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
|
|
@ -11,10 +11,7 @@ import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
import org.springframework.security.authentication.BadCredentialsException
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
import org.springframework.security.core.Authentication
|
|
||||||
import org.springframework.security.core.AuthenticationException
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
|
@ -31,24 +28,24 @@ class AuthEndpoint(
|
||||||
val encoder: PasswordEncoder,
|
val encoder: PasswordEncoder,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(AuthEndpoint::class.java);
|
private val log: Logger = LoggerFactory.getLogger(AuthEndpoint::class.java)
|
||||||
|
|
||||||
val strResponseSuccess: String = "Sending back success response";
|
val strResponseSuccess: String = "Sending back success response"
|
||||||
val strAlreadyLoggedIn: String = "User already logged in";
|
val strAlreadyLoggedIn: String = "User already logged in"
|
||||||
|
|
||||||
@PostMapping("/signup")
|
@PostMapping("/signup")
|
||||||
fun signupUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
fun signupUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
||||||
log.info("Endpoint signupUser called");
|
log.info("Endpoint signupUser called")
|
||||||
log.debug("signup Request with username: {}", loginDto.username);
|
log.debug("signup Request with username: {}", loginDto.username)
|
||||||
|
|
||||||
if( userRepository.existsByUsername(loginDto.username)){
|
if( userRepository.existsByUsername(loginDto.username)){
|
||||||
log.info("Username was already taken");
|
log.info("Username was already taken")
|
||||||
return ResponseEntity.badRequest().body("Username already taken");
|
return ResponseEntity.badRequest().body("Username already taken")
|
||||||
}
|
}
|
||||||
|
|
||||||
val user = User(loginDto.username, encoder.encode( loginDto.password), ArrayList(), null, null );
|
val user = User(loginDto.username, encoder.encode( loginDto.password), ArrayList(), null, null )
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user)
|
||||||
|
|
||||||
val authentication = authenticationManager.authenticate(
|
val authentication = authenticationManager.authenticate(
|
||||||
UsernamePasswordAuthenticationToken(
|
UsernamePasswordAuthenticationToken(
|
||||||
|
|
@ -57,81 +54,69 @@ class AuthEndpoint(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
SecurityContextHolder.getContext().authentication = authentication;
|
SecurityContextHolder.getContext().authentication = authentication
|
||||||
|
|
||||||
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl
|
||||||
|
|
||||||
session.setAttribute(STR_SESSION_USER_KEY, userDetails);
|
session.setAttribute(STR_SESSION_USER_KEY, userDetails)
|
||||||
|
|
||||||
log.info(strResponseSuccess);
|
log.info(strResponseSuccess)
|
||||||
return ResponseEntity.ok().body(LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()));
|
return ResponseEntity.ok().body(LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
fun loginUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
fun loginUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
||||||
log.info("Endpoint loginUser called");
|
log.info("Endpoint loginUser called")
|
||||||
log.debug("login Request with username: {}", loginDto.username);
|
log.debug("login Request with username: {}", loginDto.username)
|
||||||
|
|
||||||
if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
||||||
log.info(strAlreadyLoggedIn);
|
log.info(strAlreadyLoggedIn)
|
||||||
return ResponseEntity.badRequest().body(strAlreadyLoggedIn);
|
return ResponseEntity.badRequest().body(strAlreadyLoggedIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
val authentication: Authentication;
|
val authentication = authenticationManager.authenticate(
|
||||||
try{
|
UsernamePasswordAuthenticationToken(
|
||||||
authentication = authenticationManager.authenticate(
|
loginDto.username,
|
||||||
UsernamePasswordAuthenticationToken(
|
loginDto.password
|
||||||
loginDto.username,
|
|
||||||
loginDto.password
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} catch (authenticationException: AuthenticationException ){
|
)
|
||||||
if( authenticationException is BadCredentialsException ){
|
|
||||||
log.debug("Login attempt with bad credentials for username: {}", loginDto.username);
|
|
||||||
return ResponseEntity.status(401).body(4011);
|
|
||||||
}
|
|
||||||
log.error("Error during authentication", authenticationException);
|
|
||||||
return ResponseEntity.status(401).body(4010);
|
|
||||||
}
|
|
||||||
|
|
||||||
SecurityContextHolder.getContext().authentication = authentication;
|
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl
|
||||||
|
|
||||||
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
session.setAttribute(STR_SESSION_USER_KEY, userDetails)
|
||||||
|
|
||||||
session.setAttribute(STR_SESSION_USER_KEY, userDetails);
|
log.info(strResponseSuccess)
|
||||||
|
|
||||||
log.info(strResponseSuccess);
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.body( LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()))
|
.body( LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
fun logoutUser(session: HttpSession): ResponseEntity<String>{
|
fun logoutUser(session: HttpSession): ResponseEntity<String>{
|
||||||
log.info("Endpoint logoutUser called");
|
log.info("Endpoint logoutUser called")
|
||||||
|
|
||||||
session.invalidate();
|
session.invalidate()
|
||||||
|
|
||||||
log.info(strResponseSuccess);
|
log.info(strResponseSuccess)
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.body("Logged out");
|
.body("Logged out")
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/status")
|
@GetMapping("/status")
|
||||||
fun checkStatus(session: HttpSession): ResponseEntity<*>{
|
fun checkStatus(session: HttpSession): ResponseEntity<*>{
|
||||||
log.info("Endpoint checkStatus called");
|
log.info("Endpoint checkStatus called")
|
||||||
|
|
||||||
return if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
return if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
||||||
log.info(strAlreadyLoggedIn);
|
log.info(strAlreadyLoggedIn)
|
||||||
val sessionUser: UserDetailsImpl = session.getAttribute(STR_SESSION_USER_KEY) as UserDetailsImpl;
|
val sessionUser: UserDetailsImpl = session.getAttribute(STR_SESSION_USER_KEY) as UserDetailsImpl
|
||||||
ResponseEntity
|
ResponseEntity
|
||||||
.ok()
|
.ok()
|
||||||
.body( LoginResponseDto( sessionUser.username, sessionUser.getProfilePictureFilename() ) );
|
.body( LoginResponseDto( sessionUser.username, sessionUser.getProfilePictureFilename() ) )
|
||||||
} else {
|
} else {
|
||||||
log.debug("No user logged in");
|
log.debug("No user logged in")
|
||||||
log.info(strResponseSuccess);
|
log.info(strResponseSuccess)
|
||||||
ResponseEntity
|
ResponseEntity
|
||||||
.status(401)
|
.status(401)
|
||||||
.body("No user logged in");
|
.body("No user logged in")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package at.eisibaer.jbear2.endpoint
|
|
||||||
|
|
||||||
import at.eisibaer.jbear2.exception.StorageFileNotFoundException
|
|
||||||
import at.eisibaer.jbear2.service.FileService
|
|
||||||
import at.eisibaer.jbear2.service.StorageService
|
|
||||||
import org.springframework.http.ResponseEntity
|
|
||||||
import org.springframework.stereotype.Controller
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
|
||||||
import org.springframework.web.multipart.MultipartFile
|
|
||||||
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
class FileEndpoint(
|
|
||||||
val storageService: StorageService,
|
|
||||||
val fileService: FileService,
|
|
||||||
) {
|
|
||||||
|
|
||||||
@PostMapping("/")
|
|
||||||
fun handleFileUpload( @RequestParam("files") files: List<MultipartFile> ): ResponseEntity<*> {
|
|
||||||
fileService.saveFiles(files);
|
|
||||||
|
|
||||||
return ResponseEntity.ok().build<Any>()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(StorageFileNotFoundException::class)
|
|
||||||
fun handleStorageFileNotFound(exc: StorageFileNotFoundException?): ResponseEntity<*> {
|
|
||||||
return ResponseEntity.notFound().build<Any>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/user")
|
@RequestMapping("/api/user")
|
||||||
|
|
@ -21,65 +22,65 @@ class UserEndpoint(
|
||||||
val fileRepository: FileRepository,
|
val fileRepository: FileRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val log: Logger = LoggerFactory.getLogger(UserEndpoint::class.java);
|
val log: Logger = LoggerFactory.getLogger(UserEndpoint::class.java)
|
||||||
|
|
||||||
@GetMapping("/test/{pathVar}")
|
@GetMapping("/test/{pathVar}")
|
||||||
fun testEndpoint(@PathVariable pathVar: String, @RequestParam param1: String): ResponseEntity<String>{
|
fun testEndpoint(@PathVariable pathVar: String, @RequestParam param1: String): ResponseEntity<String>{
|
||||||
log.info("test Endpoint!");
|
log.info("test Endpoint!")
|
||||||
return ResponseEntity.ok(param1);
|
return ResponseEntity.ok(param1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/boards")
|
@GetMapping("/boards")
|
||||||
fun getBoards(): ResponseEntity<List<BoardDto>?>{
|
fun getBoards(): ResponseEntity<List<BoardDto>?>{
|
||||||
log.info("Get all Board Endpoint");
|
log.info("Get all Board Endpoint")
|
||||||
val userDetails = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl;
|
val userDetails = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
||||||
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB");
|
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB")
|
||||||
|
|
||||||
return boardService.getBoardsByUser( user );
|
return boardService.getBoardsByUser( user )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/boards/{boardId}")
|
@GetMapping("/boards/{boardId}")
|
||||||
fun getBoardById(@PathVariable boardId: Long): ResponseEntity<BoardDto?>{
|
fun getBoardById(@PathVariable boardId: Long): ResponseEntity<BoardDto?>{
|
||||||
log.info("Get Board Endpoint with id {}", boardId);
|
log.info("Get Board Endpoint with id {}", boardId)
|
||||||
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
||||||
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB");
|
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB")
|
||||||
|
|
||||||
return boardService.getBoardByUserAndId( user, boardId)
|
return boardService.getBoardByUserAndId( user, boardId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/boards")
|
@PostMapping("/boards")
|
||||||
fun saveNewBoard(@RequestBody boardDto: BoardDto): ResponseEntity<*> {
|
fun saveNewBoard(@RequestPart("files") files: List<MultipartFile>, @RequestPart("board") boardDto: BoardDto): ResponseEntity<*> {
|
||||||
log.info("Post Board Endpoint");
|
log.info("Post Board Endpoint")
|
||||||
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
||||||
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB");
|
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB")
|
||||||
|
|
||||||
if( boardDto.id != null ){
|
if( boardDto.id != null ){
|
||||||
return ResponseEntity.badRequest().body(Unit);
|
return ResponseEntity.badRequest().body(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
return boardService.saveBoardToUser( user, boardDto );
|
return boardService.saveBoardToUser( user, boardDto, files )
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/boards/{boardId}")
|
@PutMapping("/boards/{boardId}")
|
||||||
fun saveExistingBoard(@RequestBody boardDto: BoardDto, @PathVariable boardId: Long): ResponseEntity<*> {
|
fun saveExistingBoard(@RequestPart("files") files: List<MultipartFile>, @RequestPart("board") boardDto: BoardDto, @PathVariable boardId: Long): ResponseEntity<*> {
|
||||||
log.info("Put Board Endpoint with id {}", boardId);
|
log.info("Put Board Endpoint with id {}", boardId)
|
||||||
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
||||||
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB");
|
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB")
|
||||||
|
|
||||||
if( boardDto.id == null || boardDto.id != boardId ){
|
if( boardDto.id == null || boardDto.id != boardId ){
|
||||||
return ResponseEntity.badRequest().body("ID is either null or does not match")
|
return ResponseEntity.badRequest().body("ID is either null or does not match")
|
||||||
}
|
}
|
||||||
|
|
||||||
return boardService.saveBoardToUser( user, boardDto );
|
return boardService.saveBoardToUser( user, boardDto, files )
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/boards/{boardId}")
|
@DeleteMapping("/boards/{boardId}")
|
||||||
fun saveNewBoard(@PathVariable boardId: Long): ResponseEntity<*> {
|
fun saveNewBoard(@PathVariable boardId: Long): ResponseEntity<*> {
|
||||||
log.info("Delete Board Endpoint with id {}", boardId);
|
log.info("Delete Board Endpoint with id {}", boardId)
|
||||||
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
val userDetails: UserDetailsImpl = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
||||||
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB");
|
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB")
|
||||||
|
|
||||||
return boardService.deleteBoardOfUser( user, boardId );
|
return boardService.deleteBoardOfUser( user, boardId )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package at.eisibaer.jbear2.endpoint.ws
|
package at.eisibaer.jbear2.endpoint.ws
|
||||||
|
|
||||||
import at.eisibaer.jbear2.dto.board.QuestionDto
|
|
||||||
import at.eisibaer.jbear2.dto.message.*
|
import at.eisibaer.jbear2.dto.message.*
|
||||||
import at.eisibaer.jbear2.exception.NoMessageException
|
|
||||||
import at.eisibaer.jbear2.model.BoardEntry
|
|
||||||
import at.eisibaer.jbear2.model.Game
|
import at.eisibaer.jbear2.model.Game
|
||||||
import at.eisibaer.jbear2.model.Player
|
import at.eisibaer.jbear2.model.Player
|
||||||
import at.eisibaer.jbear2.model.User
|
import at.eisibaer.jbear2.model.User
|
||||||
|
|
@ -20,18 +17,13 @@ import at.eisibaer.jbear2.util.RandomString
|
||||||
import jakarta.transaction.Transactional
|
import jakarta.transaction.Transactional
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.dao.DataIntegrityViolationException
|
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.messaging.handler.annotation.DestinationVariable
|
import org.springframework.messaging.handler.annotation.DestinationVariable
|
||||||
import org.springframework.messaging.handler.annotation.Header
|
|
||||||
import org.springframework.messaging.handler.annotation.Headers
|
|
||||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler
|
import org.springframework.messaging.handler.annotation.MessageExceptionHandler
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping
|
import org.springframework.messaging.handler.annotation.MessageMapping
|
||||||
import org.springframework.messaging.handler.annotation.Payload
|
import org.springframework.messaging.handler.annotation.Payload
|
||||||
import org.springframework.messaging.handler.annotation.SendTo
|
|
||||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||||
import org.springframework.messaging.simp.annotation.SendToUser
|
import org.springframework.messaging.simp.annotation.SendToUser
|
||||||
import org.springframework.messaging.simp.annotation.SubscribeMapping
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
|
@ -40,10 +32,8 @@ import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
import java.security.Principal
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.NoSuchElementException
|
import kotlin.NoSuchElementException
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class GameEndpoint(
|
class GameEndpoint(
|
||||||
|
|
@ -59,16 +49,16 @@ class GameEndpoint(
|
||||||
val messagingTemplate: SimpMessagingTemplate,
|
val messagingTemplate: SimpMessagingTemplate,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(GameEndpoint::class.java);
|
private val log: Logger = LoggerFactory.getLogger(GameEndpoint::class.java)
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@GetMapping("/api/games/{id}")
|
@GetMapping("/api/games/{id}")
|
||||||
fun getGameByInviteCode(@PathVariable id: String): ResponseEntity<*>{
|
fun getGameByInviteCode(@PathVariable id: String): ResponseEntity<*>{
|
||||||
val game: Game;
|
val game: Game
|
||||||
if( id.length == INVITE_CODE_LENGTH){
|
if( id.length == INVITE_CODE_LENGTH){
|
||||||
game = gameRepository.findByInviteCode(id) ?: return ResponseEntity.status(404).body("Game with invide code $id not found")
|
game = gameRepository.findByInviteCode(id) ?: return ResponseEntity.status(404).body("Game with invide code $id not found")
|
||||||
} else {
|
} else {
|
||||||
var uuid: UUID;
|
var uuid: UUID
|
||||||
try{
|
try{
|
||||||
uuid = UUID.fromString(id)
|
uuid = UUID.fromString(id)
|
||||||
}catch (exception: IllegalArgumentException){
|
}catch (exception: IllegalArgumentException){
|
||||||
|
|
@ -76,24 +66,24 @@ class GameEndpoint(
|
||||||
}
|
}
|
||||||
game = gameRepository.findByUuid(uuid) ?: return ResponseEntity.status(404).body("Game with UUID $uuid not found")
|
game = gameRepository.findByUuid(uuid) ?: return ResponseEntity.status(404).body("Game with UUID $uuid not found")
|
||||||
}
|
}
|
||||||
return ResponseEntity.ok().body(gameMapper.toDto(game));
|
return ResponseEntity.ok().body(gameMapper.toDto(game))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@PostMapping("/api/games")
|
@PostMapping("/api/games")
|
||||||
fun hostNewGame(@RequestBody body: String?): ResponseEntity<*>{
|
fun hostNewGame(@RequestBody body: String?): ResponseEntity<*>{
|
||||||
log.info("Start new game");
|
log.info("Start new game")
|
||||||
val userDetails = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl;
|
val userDetails = SecurityContextHolder.getContext().authentication.principal as UserDetailsImpl
|
||||||
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB");
|
val user: User = userRepository.findUserByUsername(userDetails.username) ?: throw UsernameNotFoundException("Username ${userDetails.username} not found in DB")
|
||||||
|
|
||||||
val firstAvailableBoard = boardRepository.findFirstByOwnerOrderByBoardName(user) ?: return ResponseEntity.status(404).body("No board found for user")
|
val firstAvailableBoard = boardRepository.findFirstByOwnerOrderByBoardName(user) ?: return ResponseEntity.status(404).body("No board found for user")
|
||||||
|
|
||||||
gameRepository.deleteAll();
|
gameRepository.deleteAll()
|
||||||
gameRepository.flush()
|
gameRepository.flush()
|
||||||
|
|
||||||
val game = Game(RandomString.ofLength(8), UUID.randomUUID(), firstAvailableBoard, user, emptyList(), emptyList(), null )
|
val game = Game(RandomString.ofLength(8), UUID.randomUUID(), firstAvailableBoard, user, emptyList(), emptyList(), null )
|
||||||
val savedGame = gameRepository.save(game);
|
val savedGame = gameRepository.save(game)
|
||||||
return ResponseEntity.ok().body(gameMapper.toDto(savedGame));
|
return ResponseEntity.ok().body(gameMapper.toDto(savedGame))
|
||||||
}
|
}
|
||||||
|
|
||||||
// @MessageMapping("/join")
|
// @MessageMapping("/join")
|
||||||
|
|
@ -138,11 +128,11 @@ class GameEndpoint(
|
||||||
|
|
||||||
@MessageMapping("/join/{id}")
|
@MessageMapping("/join/{id}")
|
||||||
fun playerJoining(@Payload joinMessage: GenericMessage, @DestinationVariable id: String){
|
fun playerJoining(@Payload joinMessage: GenericMessage, @DestinationVariable id: String){
|
||||||
val game: Game;
|
val game: Game
|
||||||
if( id.length == INVITE_CODE_LENGTH){
|
if( id.length == INVITE_CODE_LENGTH){
|
||||||
game = gameRepository.findByInviteCode(id) ?: throw NoSuchElementException("Game with invide code $id not found")
|
game = gameRepository.findByInviteCode(id) ?: throw NoSuchElementException("Game with invide code $id not found")
|
||||||
} else {
|
} else {
|
||||||
var uuid: UUID;
|
var uuid: UUID
|
||||||
try{
|
try{
|
||||||
uuid = UUID.fromString(id)
|
uuid = UUID.fromString(id)
|
||||||
}catch (exception: IllegalArgumentException){
|
}catch (exception: IllegalArgumentException){
|
||||||
|
|
@ -150,22 +140,22 @@ class GameEndpoint(
|
||||||
}
|
}
|
||||||
game = gameRepository.findByUuid(uuid) ?: throw NoSuchElementException("Game with UUID $uuid not found")
|
game = gameRepository.findByUuid(uuid) ?: throw NoSuchElementException("Game with UUID $uuid not found")
|
||||||
}
|
}
|
||||||
val player = Player(joinMessage.content, game);
|
val player = Player(joinMessage.content, game)
|
||||||
val savedPlayer = playerRepository.save(player)
|
val savedPlayer = playerRepository.save(player)
|
||||||
val payload = playerMapper.toDto(savedPlayer);
|
val payload = playerMapper.toDto(savedPlayer)
|
||||||
messagingTemplate.convertAndSend("/topic/game/${game.uuid}/joined", payload);
|
messagingTemplate.convertAndSend("/topic/game/${game.uuid}/joined", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/start")
|
@MessageMapping("/host/game/{uuid}/start")
|
||||||
fun startGame(@Payload startMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
fun startGame(@Payload startMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
||||||
// val game = gameRepository.findByUuid(uuid) ?: throw NoSuchElementException("Game with UUID $uuid not found")
|
// val game = gameRepository.findByUuid(uuid) ?: throw NoSuchElementException("Game with UUID $uuid not found")
|
||||||
|
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/started", GameMessage(uuid, "The game has been started"));
|
messagingTemplate.convertAndSend("/topic/game/$uuid/started", GameMessage(uuid, "The game has been started"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/board/select")
|
@MessageMapping("/host/game/{uuid}/board/select")
|
||||||
fun selectBoard(@Payload boardMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
fun selectBoard(@Payload boardMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/board/selected", GameMessage(uuid, "The board is being shown"));
|
messagingTemplate.convertAndSend("/topic/game/$uuid/board/selected", GameMessage(uuid, "The board is being shown"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/boardentry/select")
|
@MessageMapping("/host/game/{uuid}/boardentry/select")
|
||||||
|
|
@ -176,9 +166,9 @@ class GameEndpoint(
|
||||||
targetGameState.boardEntryIndex,
|
targetGameState.boardEntryIndex,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
);
|
)
|
||||||
|
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/boardentry/selected", gameState);
|
messagingTemplate.convertAndSend("/topic/game/$uuid/boardentry/selected", gameState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/question/layer/select")
|
@MessageMapping("/host/game/{uuid}/question/layer/select")
|
||||||
|
|
@ -189,19 +179,19 @@ class GameEndpoint(
|
||||||
targetGameState.boardEntryIndex,
|
targetGameState.boardEntryIndex,
|
||||||
targetGameState.questionIndex,
|
targetGameState.questionIndex,
|
||||||
targetGameState.questionLayerIndex,
|
targetGameState.questionLayerIndex,
|
||||||
);
|
)
|
||||||
|
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/questionlayer/selected", gameState);
|
messagingTemplate.convertAndSend("/topic/game/$uuid/questionlayer/selected", gameState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/question/audio/play")
|
@MessageMapping("/host/game/{uuid}/question/audio/play")
|
||||||
fun playAudio(@Payload audioMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
fun playAudio(@Payload audioMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/question/audio/playing", GenericMessage("Playing audio"));
|
messagingTemplate.convertAndSend("/topic/game/$uuid/question/audio/playing", GenericMessage("Playing audio"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/question/audio/stop")
|
@MessageMapping("/host/game/{uuid}/question/audio/stop")
|
||||||
fun stopAudio(@Payload audioMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
fun stopAudio(@Payload audioMessage: GenericMessage, @DestinationVariable uuid: UUID){
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/question/audio/stopped", GenericMessage("Playing audio"));
|
messagingTemplate.convertAndSend("/topic/game/$uuid/question/audio/stopped", GenericMessage("Playing audio"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/host/game/{uuid}/question/reveal")
|
@MessageMapping("/host/game/{uuid}/question/reveal")
|
||||||
|
|
@ -211,21 +201,21 @@ class GameEndpoint(
|
||||||
gameState.boardEntryIndex == null ||
|
gameState.boardEntryIndex == null ||
|
||||||
gameState.questionIndex == null
|
gameState.questionIndex == null
|
||||||
){
|
){
|
||||||
throw InvalidPropertiesFormatException("Invalid gameSate to reveal question");
|
throw InvalidPropertiesFormatException("Invalid gameSate to reveal question")
|
||||||
}
|
}
|
||||||
val game = gameRepository.findByUuid(uuid) ?: throw NoSuchElementException("Game with UUID $uuid not found")
|
val game = gameRepository.findByUuid(uuid) ?: throw NoSuchElementException("Game with UUID $uuid not found")
|
||||||
val board = game.board;
|
val board = game.board
|
||||||
|
|
||||||
val question = board.categories[gameState.categoryIndex].boardEntries[gameState.boardEntryIndex].questions[gameState.questionIndex];
|
val question = board.categories[gameState.categoryIndex].boardEntries[gameState.boardEntryIndex].questions[gameState.questionIndex]
|
||||||
|
|
||||||
val questionDto = questionMapper.toDto(question);
|
val questionDto = questionMapper.toDto(question)
|
||||||
messagingTemplate.convertAndSend("/topic/game/$uuid/question/revealed", questionDto);
|
messagingTemplate.convertAndSend("/topic/game/$uuid/question/revealed", questionDto)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageExceptionHandler
|
@MessageExceptionHandler
|
||||||
@SendToUser("/error")
|
@SendToUser("/error")
|
||||||
fun handleException(exception: Exception): GenericMessage{
|
fun handleException(exception: Exception): GenericMessage{
|
||||||
log.error("Exception in GameEndpoint", exception)
|
log.error("Exception in GameEndpoint", exception)
|
||||||
return GenericMessage(exception.message ?: "Unknown error");
|
return GenericMessage(exception.message ?: "Unknown error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ data class Answer(
|
||||||
|
|
||||||
@OneToOne
|
@OneToOne
|
||||||
@JoinColumn(name = "fk_image_file", referencedColumnName = "id")
|
@JoinColumn(name = "fk_image_file", referencedColumnName = "id")
|
||||||
val image: File?,
|
var image: File?,
|
||||||
|
|
||||||
@OneToOne
|
@OneToOne
|
||||||
@JoinColumn(name="fk_board_entry", referencedColumnName = "id")
|
@JoinColumn(name="fk_board_entry", referencedColumnName = "id")
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,19 @@ import jakarta.persistence.*
|
||||||
data class Board(
|
data class Board(
|
||||||
|
|
||||||
@OneToMany(mappedBy = "board", cascade = [CascadeType.ALL], orphanRemoval = true)
|
@OneToMany(mappedBy = "board", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||||
val categories: List<Category>,
|
val categories: List<Category> = ArrayList(),
|
||||||
|
|
||||||
@Column(name = "board_name", nullable = false, unique = false)
|
@Column(name = "board_name", nullable = false, unique = false)
|
||||||
val boardName: String,
|
var boardName: String = "New Board",
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name="fk_owned_by", referencedColumnName = "id")
|
@JoinColumn(name="fk_owned_by", referencedColumnName = "id")
|
||||||
val owner: User,
|
var owner: User,
|
||||||
|
|
||||||
@Column(name = "points_are_title", nullable = false, unique = false)
|
@Column(name = "points_are_title", nullable = false, unique = false)
|
||||||
val pointsAreTitle: Boolean = false,
|
var pointsAreTitle: Boolean = false,
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
val id: Long? = null,
|
var id: Long? = null,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ import jakarta.persistence.*
|
||||||
data class BoardEntry(
|
data class BoardEntry(
|
||||||
|
|
||||||
@Column(name = "name", nullable = false, unique = false)
|
@Column(name = "name", nullable = false, unique = false)
|
||||||
val name: String,
|
var name: String,
|
||||||
|
|
||||||
@Column(name = "points", nullable = false, unique = false)
|
@Column(name = "points", nullable = false, unique = false)
|
||||||
val points: Long,
|
var points: Long,
|
||||||
|
|
||||||
@OneToMany(mappedBy = "boardEntry", cascade = [CascadeType.ALL], orphanRemoval = true)
|
@OneToMany(mappedBy = "boardEntry", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||||
val questions: List<Question>,
|
val questions: List<Question> = ArrayList(),
|
||||||
|
|
||||||
@OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
|
@OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||||
val answer: Answer,
|
var answer: Answer? = null,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "fk_category", referencedColumnName = "id")
|
@JoinColumn(name = "fk_category", referencedColumnName = "id")
|
||||||
var category: Category?,
|
var category: Category? = null,
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ import jakarta.persistence.*
|
||||||
data class Category (
|
data class Category (
|
||||||
|
|
||||||
@Column(name = "name", nullable = false, unique = false)
|
@Column(name = "name", nullable = false, unique = false)
|
||||||
val name: String,
|
var name: String,
|
||||||
|
|
||||||
@Column(name = "description", nullable = false, unique = false)
|
@Column(name = "description", nullable = false, unique = false)
|
||||||
val description: String,
|
var description: String,
|
||||||
|
|
||||||
@OneToMany(mappedBy = "category", cascade = [CascadeType.ALL], orphanRemoval = true)
|
@OneToMany(mappedBy = "category", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||||
val boardEntries: List<BoardEntry>,
|
val boardEntries: List<BoardEntry> = ArrayList(),
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "fk_board", referencedColumnName = "id")
|
@JoinColumn(name = "fk_board", referencedColumnName = "id")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package at.eisibaer.jbear2.model
|
package at.eisibaer.jbear2.model
|
||||||
|
|
||||||
|
import at.eisibaer.jbear2.model.enums.FileType
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
@ -34,10 +35,4 @@ data class File (
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
val id: Long? = null,
|
val id: Long? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class FileType{
|
|
||||||
IMAGE,
|
|
||||||
AUDIO,
|
|
||||||
PROFILE_PICTURE,
|
|
||||||
}
|
|
||||||
|
|
@ -12,18 +12,18 @@ import jakarta.persistence.*
|
||||||
data class Question(
|
data class Question(
|
||||||
|
|
||||||
@Column(name = "text", nullable = false, unique = false)
|
@Column(name = "text", nullable = false, unique = false)
|
||||||
val text: String,
|
var text: String,
|
||||||
|
|
||||||
@Enumerated
|
@Enumerated
|
||||||
@Column(name = "question_type", nullable = false, unique = false)
|
@Column(name = "question_type", nullable = false, unique = false)
|
||||||
val questionType: QuestionType = QuestionType.TEXT,
|
var questionType: QuestionType = QuestionType.TEXT,
|
||||||
|
|
||||||
@Column(name = "font_scaling", nullable = false, unique = false)
|
@Column(name = "font_scaling", nullable = false, unique = false)
|
||||||
val fontScaling: Int,
|
var fontScaling: Int,
|
||||||
|
|
||||||
@OneToOne
|
@OneToOne
|
||||||
@JoinColumn(name = "fk_image", referencedColumnName = "id")
|
@JoinColumn(name = "fk_image", referencedColumnName = "id")
|
||||||
val image: File?,
|
var image: File?,
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "fk_board_entry", referencedColumnName = "id")
|
@JoinColumn(name = "fk_board_entry", referencedColumnName = "id")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package at.eisibaer.jbear2.model.enums
|
||||||
|
|
||||||
|
enum class FileType{
|
||||||
|
IMAGE,
|
||||||
|
AUDIO,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package at.eisibaer.jbear2.repository
|
||||||
|
|
||||||
|
import at.eisibaer.jbear2.model.Answer
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
|
interface AnswerRepository : JpaRepository<Answer, Long>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package at.eisibaer.jbear2.repository
|
||||||
|
|
||||||
|
import at.eisibaer.jbear2.model.Question
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
|
interface QuestionRepository : JpaRepository<Question, Long>
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package at.eisibaer.jbear2.security
|
package at.eisibaer.jbear2.security
|
||||||
|
|
||||||
import at.eisibaer.jbear2.config.ApplicationProperties
|
import at.eisibaer.jbear2.config.ApplicationProperties
|
||||||
|
import at.eisibaer.jbear2.repository.UserRepository
|
||||||
import jakarta.servlet.FilterChain
|
import jakarta.servlet.FilterChain
|
||||||
import jakarta.servlet.ServletException
|
import jakarta.servlet.ServletException
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
|
@ -10,8 +11,8 @@ import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
|
import org.springframework.security.authentication.ProviderManager
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
|
|
@ -29,12 +30,12 @@ import java.util.function.Supplier
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
class SecurityConfiguration(
|
class SecurityConfiguration(
|
||||||
private val userDetailService: UserDetailsService,
|
private val userRepository: UserRepository,
|
||||||
private val unauthorizedHandler: AuthFilter,
|
private val unauthorizedHandler: AuthFilter,
|
||||||
private val applicationProperties: ApplicationProperties
|
private val applicationProperties: ApplicationProperties
|
||||||
) {
|
) {
|
||||||
|
|
||||||
final val log: Logger = LoggerFactory.getLogger(SecurityConfiguration::class.java);
|
final val log: Logger = LoggerFactory.getLogger(SecurityConfiguration::class.java)
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
|
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
|
||||||
|
|
@ -44,14 +45,13 @@ class SecurityConfiguration(
|
||||||
.requestMatchers("/api/user/**").authenticated()
|
.requestMatchers("/api/user/**").authenticated()
|
||||||
.requestMatchers("/**").permitAll()
|
.requestMatchers("/**").permitAll()
|
||||||
}
|
}
|
||||||
.authenticationProvider(authenticationProvider())
|
|
||||||
.addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java)
|
.addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCsrfConfig(httpSecurity: HttpSecurity): HttpSecurity{
|
private fun addCsrfConfig(httpSecurity: HttpSecurity): HttpSecurity{
|
||||||
if( applicationProperties.test ){
|
if( applicationProperties.test ){
|
||||||
httpSecurity.csrf{ config -> config.disable()};
|
httpSecurity.csrf{ config -> config.disable()}
|
||||||
} else {
|
} else {
|
||||||
httpSecurity.csrf { config ->
|
httpSecurity.csrf { config ->
|
||||||
config
|
config
|
||||||
|
|
@ -60,7 +60,7 @@ class SecurityConfiguration(
|
||||||
}
|
}
|
||||||
.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java)
|
.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java)
|
||||||
}
|
}
|
||||||
return httpSecurity;
|
return httpSecurity
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
||||||
|
|
@ -107,21 +107,18 @@ class SecurityConfiguration(
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun passwordEncoder() : PasswordEncoder {
|
fun passwordEncoder() : PasswordEncoder {
|
||||||
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
|
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun authenticationProvider(): DaoAuthenticationProvider{
|
fun authenticationProvider(): AuthenticationManager {
|
||||||
val authProvider: DaoAuthenticationProvider = DaoAuthenticationProvider()
|
val authProvider = DaoAuthenticationProvider(userDetailService())
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder())
|
||||||
authProvider.setUserDetailsService(userDetailService)
|
return ProviderManager(authProvider)
|
||||||
authProvider.setPasswordEncoder(passwordEncoder());
|
|
||||||
|
|
||||||
return authProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun authenticationManager(authConfig: AuthenticationConfiguration): AuthenticationManager{
|
fun userDetailService(): UserDetailsService {
|
||||||
return authConfig.authenticationManager;
|
return UserDetailServiceImpl(userRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,17 +16,17 @@ class UserDetailServiceImpl(
|
||||||
): UserDetailsService {
|
): UserDetailsService {
|
||||||
|
|
||||||
override fun loadUserByUsername(username: String?): UserDetailsImpl {
|
override fun loadUserByUsername(username: String?): UserDetailsImpl {
|
||||||
val user: User? = userRepository.findUserByUsername( username ?: "" )
|
if( username == null ){
|
||||||
|
throw UsernameNotFoundException("User not found by username \"$username\"")
|
||||||
if( user == null ){
|
|
||||||
throw UsernameNotFoundException("User not found by username \"$username\"");
|
|
||||||
}
|
}
|
||||||
|
val user: User = userRepository.findUserByUsername( username )
|
||||||
|
?: throw UsernameNotFoundException("User not found by username \"$username\"")
|
||||||
|
|
||||||
return UserDetailsImpl(
|
return UserDetailsImpl(
|
||||||
user.id!!,
|
user.id!!,
|
||||||
user.username,
|
user.username,
|
||||||
user.password,
|
user.password,
|
||||||
user.profilePicture?.filename,
|
user.profilePicture?.filename,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package at.eisibaer.jbear2.service
|
||||||
import at.eisibaer.jbear2.dto.board.BoardDto
|
import at.eisibaer.jbear2.dto.board.BoardDto
|
||||||
import at.eisibaer.jbear2.model.User
|
import at.eisibaer.jbear2.model.User
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
interface BoardService {
|
interface BoardService {
|
||||||
|
|
||||||
|
|
@ -10,6 +11,6 @@ interface BoardService {
|
||||||
|
|
||||||
fun getBoardByUserAndId( user: User, boardId: Long ): ResponseEntity<BoardDto?>
|
fun getBoardByUserAndId( user: User, boardId: Long ): ResponseEntity<BoardDto?>
|
||||||
|
|
||||||
fun saveBoardToUser( user: User, boardDto: BoardDto ): ResponseEntity<BoardDto>
|
fun saveBoardToUser( user: User, boardDto: BoardDto, files: List<MultipartFile> ): ResponseEntity<BoardDto>
|
||||||
fun deleteBoardOfUser( user: User, boardId: Long ): ResponseEntity<Unit>
|
fun deleteBoardOfUser( user: User, boardId: Long ): ResponseEntity<Unit>
|
||||||
}
|
}
|
||||||
|
|
@ -9,54 +9,92 @@ import at.eisibaer.jbear2.repository.UserRepository
|
||||||
import at.eisibaer.jbear2.service.mapper.BoardMapper
|
import at.eisibaer.jbear2.service.mapper.BoardMapper
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class BoardServiceImpl (
|
class BoardServiceImpl (
|
||||||
|
private val fileService: FileService,
|
||||||
private val boardRepository: BoardRepository,
|
private val boardRepository: BoardRepository,
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val fileRepository: FileRepository,
|
private val fileRepository: FileRepository,
|
||||||
private val boardMapper: BoardMapper,
|
private val boardMapper: BoardMapper,
|
||||||
) : BoardService {
|
) : BoardService {
|
||||||
|
|
||||||
val log: Logger = LoggerFactory.getLogger(BoardServiceImpl::class.java);
|
val log: Logger = LoggerFactory.getLogger(BoardServiceImpl::class.java)
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun getBoardsByUser(user: User): ResponseEntity<List<BoardDto>?> {
|
override fun getBoardsByUser(user: User): ResponseEntity<List<BoardDto>?> {
|
||||||
val boards = boardRepository.findAllByOwner(user)
|
val boards = boardRepository.findAllByOwner(user)
|
||||||
return ResponseEntity.ok(boardMapper.toDto(boards));
|
return ResponseEntity.ok(boardMapper.toDto(boards))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun getBoardByUserAndId(user: User, boardId: Long): ResponseEntity<BoardDto?> {
|
override fun getBoardByUserAndId(user: User, boardId: Long): ResponseEntity<BoardDto?> {
|
||||||
val board = boardRepository.findByIdAndOwner(boardId, user) ?: return ResponseEntity.status(404).build();
|
val board = boardRepository.findByIdAndOwner(boardId, user) ?: return ResponseEntity.status(404).build()
|
||||||
|
|
||||||
return ResponseEntity.ok(boardMapper.toDto(board))
|
return ResponseEntity.ok(boardMapper.toDto(board))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun saveBoardToUser( user: User, boardDto: BoardDto): ResponseEntity<BoardDto> {
|
override fun saveBoardToUser( user: User, boardDto: BoardDto, files: List<MultipartFile>): ResponseEntity<BoardDto> {
|
||||||
val board: Board = boardMapper.toEntity(boardDto, user, userRepository, fileRepository);
|
val board = if( boardDto.id == null ){
|
||||||
|
Board(owner = user)
|
||||||
|
} else {
|
||||||
|
boardRepository.findByIdAndOwner(boardDto.id, user) ?: throw NoSuchElementException("Board not found for user " + user.username)
|
||||||
|
}
|
||||||
|
boardMapper.mapToEntity(board, boardDto, user, userRepository, fileRepository)
|
||||||
|
|
||||||
val savedBoard: Board;
|
val savedBoard: Board
|
||||||
try {
|
try {
|
||||||
savedBoard = boardRepository.save(board);
|
savedBoard = boardRepository.save(board)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
log.error(ex.message, ex);
|
log.error(ex.message, ex)
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
fileService.saveFiles(files, user, savedBoard)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
log.error(ex.message, ex)
|
||||||
|
return ResponseEntity.badRequest().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(boardMapper.toDto(savedBoard))
|
return ResponseEntity.ok(boardMapper.toDto(savedBoard))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteBoardOfUser(user: User, boardId: Long): ResponseEntity<Unit> {
|
override fun deleteBoardOfUser(user: User, boardId: Long): ResponseEntity<Unit> {
|
||||||
boardRepository.deleteById( boardId );
|
val board = boardRepository.findByIdAndOwner(boardId, user) ?: return ResponseEntity.status(HttpStatus.NOT_FOUND).build()
|
||||||
|
|
||||||
|
|
||||||
|
boardRepository.deleteById( boardId )
|
||||||
|
|
||||||
|
|
||||||
//TODO Delete Images
|
//TODO Delete Images
|
||||||
|
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteFilesOfBoard(board: Board) {
|
||||||
|
try {
|
||||||
|
for (category in board.categories) {
|
||||||
|
for (boardEntry in category.boardEntries) {
|
||||||
|
if (boardEntry.answer != null && boardEntry.answer!!.image != null) {
|
||||||
|
fileService.deleteFile(boardEntry.answer!!.image!!)
|
||||||
|
}
|
||||||
|
for (question in boardEntry.questions) {
|
||||||
|
if (question.image != null) {
|
||||||
|
fileService.deleteFile(question.image!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
log.error("Error occured",exception) //TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
package at.eisibaer.jbear2.service
|
package at.eisibaer.jbear2.service
|
||||||
|
|
||||||
|
import at.eisibaer.jbear2.model.Board
|
||||||
import at.eisibaer.jbear2.model.File
|
import at.eisibaer.jbear2.model.File
|
||||||
|
import at.eisibaer.jbear2.model.User
|
||||||
import org.springframework.core.io.Resource
|
import org.springframework.core.io.Resource
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface FileService {
|
interface FileService {
|
||||||
|
|
||||||
fun saveFiles(files: List<MultipartFile>): List<File>;
|
fun saveFiles(files: List<MultipartFile>, owner: User, board: Board)
|
||||||
fun saveFile(file: MultipartFile): File;
|
fun saveFile(file: MultipartFile, owner: User, board: Board): File
|
||||||
|
|
||||||
fun getFile(file: File): Resource;
|
fun getFile(file: File): Resource
|
||||||
fun getFile(fileUUID: UUID): Resource;
|
fun getFile(fileUUID: UUID): Resource
|
||||||
|
|
||||||
fun deleteFile(file: File): Resource;
|
fun deleteFile(file: File): Resource
|
||||||
fun deleteFile(fileUUID: UUID): Resource;
|
fun deleteFile(fileUUID: UUID): Resource
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,96 @@
|
||||||
package at.eisibaer.jbear2.service
|
package at.eisibaer.jbear2.service
|
||||||
|
|
||||||
import at.eisibaer.jbear2.model.File
|
import at.eisibaer.jbear2.model.*
|
||||||
|
import at.eisibaer.jbear2.model.enums.FileType
|
||||||
|
import at.eisibaer.jbear2.repository.AnswerRepository
|
||||||
import at.eisibaer.jbear2.repository.FileRepository
|
import at.eisibaer.jbear2.repository.FileRepository
|
||||||
|
import at.eisibaer.jbear2.repository.QuestionRepository
|
||||||
|
import lombok.extern.slf4j.Slf4j
|
||||||
|
import org.apache.tomcat.util.http.fileupload.InvalidFileNameException
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.core.io.Resource
|
import org.springframework.core.io.Resource
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.util.InvalidMimeTypeException
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.nio.file.Files
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
class FileServiceImpl(
|
class FileServiceImpl (
|
||||||
val fileRepository: FileRepository,
|
val fileRepository: FileRepository,
|
||||||
|
val answerRepository: AnswerRepository,
|
||||||
|
val questionRepository: QuestionRepository,
|
||||||
|
val storageService: StorageService,
|
||||||
) : FileService {
|
) : FileService {
|
||||||
|
|
||||||
override fun saveFiles(files: List<MultipartFile>): List<File> {
|
val log: Logger = LoggerFactory.getLogger(FileServiceImpl::class.java)
|
||||||
|
|
||||||
|
override fun saveFiles(files: List<MultipartFile>, owner: User, board: Board) {
|
||||||
|
val digest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
for(file in files) {
|
||||||
|
if( file.originalFilename == null ){
|
||||||
|
throw InvalidFileNameException(file.originalFilename, "No filename given")
|
||||||
|
}
|
||||||
|
val associatedEntries = file.originalFilename!!.split("-")
|
||||||
|
if( associatedEntries.size != 3) {
|
||||||
|
throw InvalidFileNameException(file.name, "Invalid index name")
|
||||||
|
}
|
||||||
|
val boardEntry = board.categories[associatedEntries[0].toInt()].boardEntries[associatedEntries[1].toInt()]
|
||||||
|
var question: Question? = null
|
||||||
|
var answer: Answer? = null
|
||||||
|
if(associatedEntries[2] == "answer"){
|
||||||
|
answer = boardEntry.answer
|
||||||
|
} else {
|
||||||
|
question = boardEntry.questions[associatedEntries[2].toInt()]
|
||||||
|
}
|
||||||
|
|
||||||
|
val uuid = UUID.randomUUID()
|
||||||
|
val filename = uuid.toString() + getExtension(file)
|
||||||
|
|
||||||
|
storageService.storeFile(file, filename)
|
||||||
|
log.debug("File stored")
|
||||||
|
|
||||||
|
val newFile = File(
|
||||||
|
uuid,
|
||||||
|
filename,
|
||||||
|
Base64.getEncoder().encodeToString(digest.digest(file.bytes)),
|
||||||
|
owner,
|
||||||
|
if (file.contentType.orEmpty().startsWith("image")) FileType.IMAGE else FileType.AUDIO,
|
||||||
|
question,
|
||||||
|
answer,
|
||||||
|
)
|
||||||
|
val savedFile: File = fileRepository.save(newFile)
|
||||||
|
if(associatedEntries[2] == "answer"){
|
||||||
|
if( answer != null ){
|
||||||
|
if( savedFile.fileType == FileType.IMAGE ) {
|
||||||
|
answer.image = savedFile
|
||||||
|
}
|
||||||
|
answerRepository.save(answer)
|
||||||
|
}
|
||||||
|
} else if( question != null ){
|
||||||
|
if( savedFile.fileType == FileType.IMAGE ) {
|
||||||
|
question.image = savedFile
|
||||||
|
}
|
||||||
|
questionRepository.save(question)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveFile(file: MultipartFile, owner: User, board: Board): File {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveFile(file: MultipartFile): File {
|
private fun getExtension(file: MultipartFile): String {
|
||||||
TODO("Not yet implemented")
|
return when (file.contentType) {
|
||||||
}
|
"image/jpeg" -> ".jpg"
|
||||||
|
"image/png" -> ".png"
|
||||||
private fun saveMultipartFile(file: MultipartFile): File{
|
"image/avif" -> ".avif"
|
||||||
val uuid = UUID.randomUUID();
|
"image/webp" -> ".webp"
|
||||||
val filename = file.originalFilename;
|
else -> throw InvalidMimeTypeException(file.contentType ?: "unknown", "MIME Type not accepted")
|
||||||
var hash: String;
|
}
|
||||||
// val file = File(
|
|
||||||
// UUID.randomUUID(),
|
|
||||||
// uuidextension,
|
|
||||||
// hash,
|
|
||||||
// )
|
|
||||||
TODO();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFile(file: File): Resource {
|
override fun getFile(file: File): Resource {
|
||||||
|
|
@ -42,6 +102,7 @@ class FileServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteFile(file: File): Resource {
|
override fun deleteFile(file: File): Resource {
|
||||||
|
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import at.eisibaer.jbear2.config.ApplicationProperties
|
||||||
import at.eisibaer.jbear2.exception.StorageException
|
import at.eisibaer.jbear2.exception.StorageException
|
||||||
import at.eisibaer.jbear2.exception.StorageFileNotFoundException
|
import at.eisibaer.jbear2.exception.StorageFileNotFoundException
|
||||||
import jakarta.annotation.PostConstruct
|
import jakarta.annotation.PostConstruct
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.core.io.Resource
|
import org.springframework.core.io.Resource
|
||||||
import org.springframework.core.io.UrlResource
|
import org.springframework.core.io.UrlResource
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Propagation
|
|
||||||
import org.springframework.transaction.annotation.Transactional
|
|
||||||
import org.springframework.util.FileSystemUtils
|
import org.springframework.util.FileSystemUtils
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
@ -20,29 +20,30 @@ import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
|
||||||
class FileSystemStorageService(
|
class FileSystemStorageService(
|
||||||
val applicationProperties: ApplicationProperties,
|
val applicationProperties: ApplicationProperties,
|
||||||
) : StorageService {
|
) : StorageService {
|
||||||
|
|
||||||
|
val log: Logger = LoggerFactory.getLogger(FileSystemStorageService::class.java)
|
||||||
|
|
||||||
lateinit var rootLocation: Path
|
lateinit var rootLocation: Path
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
fun init(){
|
fun init(){
|
||||||
val location = applicationProperties.storage.fs.location;
|
val location = applicationProperties.storage.fs.location
|
||||||
if( location.trim().isEmpty() ){
|
if( location.trim().isEmpty() ){
|
||||||
throw StorageException("File upload location can not be Empty", null)
|
throw StorageException("File upload location can not be Empty", null)
|
||||||
}
|
}
|
||||||
rootLocation = Paths.get(location);
|
rootLocation = Paths.get(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeFile(file: MultipartFile) {
|
override fun storeFile(file: MultipartFile, filename: String) {
|
||||||
storeMultipartFile(file);
|
storeMultipartFile(file, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun storeFiles(files: List<MultipartFile>) {
|
override fun storeFiles(files: List<Pair<MultipartFile, String>>) {
|
||||||
for( file in files ){
|
for( file in files ){
|
||||||
storeMultipartFile(file)
|
storeMultipartFile(file.first, file.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,16 +65,16 @@ class FileSystemStorageService(
|
||||||
FileSystemUtils.deleteRecursively(rootLocation.resolve(filename))
|
FileSystemUtils.deleteRecursively(rootLocation.resolve(filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun storeMultipartFile(file: MultipartFile){
|
private fun storeMultipartFile(file: MultipartFile, filename: String){
|
||||||
try {
|
try {
|
||||||
if (file.isEmpty) {
|
if (file.isEmpty) {
|
||||||
throw StorageException("Failed to store empty file.", null)
|
throw StorageException("Failed to store empty file.", null)
|
||||||
}
|
}
|
||||||
val destinationFile: Path = rootLocation
|
val destinationFile: Path = rootLocation
|
||||||
.resolve(Paths.get(file.originalFilename))
|
.resolve(Paths.get(filename))
|
||||||
.normalize()
|
.normalize()
|
||||||
.toAbsolutePath()
|
.toAbsolutePath()
|
||||||
if (destinationFile.getParent() != rootLocation.toAbsolutePath()) {
|
if (destinationFile.parent != rootLocation.toAbsolutePath()) {
|
||||||
// This is a security check
|
// This is a security check
|
||||||
throw StorageException("Cannot store file outside current directory.", null)
|
throw StorageException("Cannot store file outside current directory.", null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
interface StorageService {
|
interface StorageService {
|
||||||
|
|
||||||
fun storeFile(file: MultipartFile)
|
fun storeFile(file: MultipartFile, filename: String)
|
||||||
|
|
||||||
fun storeFiles(files: List<MultipartFile>)
|
fun storeFiles(files: List<Pair<MultipartFile, String>>)
|
||||||
|
|
||||||
fun getFile(filename: String): Resource
|
fun getFile(filename: String): Resource
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,13 @@ import java.util.*
|
||||||
@Mapper(uses = [QuestionMapper::class,AnswerMapper::class])
|
@Mapper(uses = [QuestionMapper::class,AnswerMapper::class])
|
||||||
abstract class BoardEntryMapper {
|
abstract class BoardEntryMapper {
|
||||||
|
|
||||||
abstract fun toDto(e: BoardEntry): BoardEntryDto;
|
abstract fun toDto(e: BoardEntry): BoardEntryDto
|
||||||
abstract fun toDto(e: List<BoardEntry>): List<BoardEntryDto>;
|
abstract fun toDto(e: List<BoardEntry>): List<BoardEntryDto>
|
||||||
|
|
||||||
abstract fun toEntity(d: BoardEntryDto, @Context fileRepository: FileRepository): BoardEntry;
|
abstract fun toEntity(d: BoardEntryDto, @Context fileRepository: FileRepository): BoardEntry
|
||||||
abstract fun toEntity(d: List<BoardEntryDto>, @Context fileRepository: FileRepository): List<BoardEntry>;
|
abstract fun toEntity(d: List<BoardEntryDto>, @Context fileRepository: FileRepository): List<BoardEntry>
|
||||||
|
|
||||||
|
abstract fun mapToEntity(d: BoardEntryDto, @MappingTarget e: BoardEntry, @Context fileRepository: FileRepository)
|
||||||
|
|
||||||
fun map(file: File): String{
|
fun map(file: File): String{
|
||||||
return file.uuid.toString()
|
return file.uuid.toString()
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,28 @@ import at.eisibaer.jbear2.repository.FileRepository
|
||||||
import at.eisibaer.jbear2.repository.UserRepository
|
import at.eisibaer.jbear2.repository.UserRepository
|
||||||
import org.mapstruct.*
|
import org.mapstruct.*
|
||||||
|
|
||||||
@Mapper(uses = [UserMapper::class,QuestionMapper::class,AnswerMapper::class,BoardEntryMapper::class,CategoryMapper::class])
|
@Mapper(uses = [QuestionMapper::class,AnswerMapper::class,BoardEntryMapper::class,CategoryMapper::class])
|
||||||
abstract class BoardMapper {
|
abstract class BoardMapper {
|
||||||
|
|
||||||
abstract fun toDto(e: Board): BoardDto;
|
abstract fun toDto(e: Board): BoardDto
|
||||||
abstract fun toDto(e: List<Board>): List<BoardDto>;
|
abstract fun toDto(e: List<Board>): List<BoardDto>
|
||||||
|
|
||||||
@Mapping(target = "owner", source = "owner")
|
@Mappings(value = [
|
||||||
@Mapping(target = "id", source = "d.id")
|
Mapping(target = "owner", source = "ownerUser"),
|
||||||
abstract fun toEntity(d: BoardDto, owner: User, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): Board;
|
Mapping(target = "id", source = "d.id"),
|
||||||
abstract fun toEntity(d: List<BoardDto>, @Context owner: User, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): List<Board>;
|
])
|
||||||
|
abstract fun toEntity(d: BoardDto, ownerUser: User, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): Board
|
||||||
|
abstract fun toEntity(d: List<BoardDto>, ownerUser: User, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): ArrayList<Board>
|
||||||
|
|
||||||
|
@Mappings(value = [
|
||||||
|
Mapping(target = "owner", source = "ownerUser"),
|
||||||
|
Mapping(target = "id", source = "d.id"),
|
||||||
|
])
|
||||||
|
abstract fun mapToEntity(@MappingTarget board: Board, d: BoardDto, ownerUser: User, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): Board
|
||||||
|
|
||||||
|
fun map(owner: User): String{
|
||||||
|
return owner.username
|
||||||
|
}
|
||||||
|
|
||||||
@AfterMapping
|
@AfterMapping
|
||||||
fun addBoardToCategory(source: BoardDto, @MappingTarget target: Board ): Board{
|
fun addBoardToCategory(source: BoardDto, @MappingTarget target: Board ): Board{
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import java.util.*
|
||||||
@Mapper(uses = [QuestionMapper::class,AnswerMapper::class,BoardEntryMapper::class])
|
@Mapper(uses = [QuestionMapper::class,AnswerMapper::class,BoardEntryMapper::class])
|
||||||
abstract class CategoryMapper {
|
abstract class CategoryMapper {
|
||||||
|
|
||||||
abstract fun toDto(e: Category): CategoryDto;
|
abstract fun toDto(e: Category): CategoryDto
|
||||||
abstract fun toDto(e: List<Category>): List<CategoryDto>;
|
abstract fun toDto(e: List<Category>): List<CategoryDto>
|
||||||
|
|
||||||
abstract fun toEntity(d: CategoryDto, @Context fileRepository: FileRepository): Category;
|
abstract fun toEntity(d: CategoryDto, @Context fileRepository: FileRepository, @Context boardEntryMapper: BoardEntryMapper): Category
|
||||||
abstract fun toEntity(d: List<CategoryDto>, @Context fileRepository: FileRepository): List<Category>;
|
abstract fun toEntity(d: List<CategoryDto>, @Context fileRepository: FileRepository, @Context boardEntryMapper: BoardEntryMapper): List<Category>
|
||||||
|
|
||||||
fun map(file: File): String{
|
fun map(file: File): String{
|
||||||
return file.uuid.toString()
|
return file.uuid.toString()
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,8 @@
|
||||||
package at.eisibaer.jbear2.service.mapper
|
package at.eisibaer.jbear2.service.mapper
|
||||||
|
|
||||||
import at.eisibaer.jbear2.dto.game.GameDto
|
import at.eisibaer.jbear2.dto.game.GameDto
|
||||||
import at.eisibaer.jbear2.model.BoardEntry
|
|
||||||
import at.eisibaer.jbear2.model.Game
|
import at.eisibaer.jbear2.model.Game
|
||||||
import org.mapstruct.Mapper
|
import org.mapstruct.Mapper
|
||||||
|
|
||||||
@Mapper
|
@Mapper(uses = [PlayerMapper::class])
|
||||||
interface GameMapper : EntityMapper<GameDto, Game> {
|
interface GameMapper : EntityMapper<GameDto, Game>
|
||||||
//
|
|
||||||
// fun map(boardEntry: BoardEntry): Long{
|
|
||||||
// return boardEntry.id!!
|
|
||||||
// }
|
|
||||||
// fun map(boardEntries: List<BoardEntry>): List<Long>{
|
|
||||||
// return boardEntries.map { it.id!! }
|
|
||||||
// }
|
|
||||||
// fun map(ids: List<Long>): List<BoardEntry>{
|
|
||||||
// return emptyList()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package at.eisibaer.jbear2.service.mapper
|
|
||||||
|
|
||||||
import at.eisibaer.jbear2.dto.user.UserDto
|
|
||||||
import at.eisibaer.jbear2.model.User
|
|
||||||
import at.eisibaer.jbear2.repository.FileRepository
|
|
||||||
import at.eisibaer.jbear2.repository.UserRepository
|
|
||||||
import org.mapstruct.Context
|
|
||||||
import org.mapstruct.Mapper
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
|
||||||
|
|
||||||
@Mapper(uses = [ImageMapper::class, UserMapper::class])
|
|
||||||
interface UserMapper{
|
|
||||||
|
|
||||||
fun toDto(e: User): UserDto;
|
|
||||||
fun toDto(e: List<User>): List<UserDto>;
|
|
||||||
|
|
||||||
fun toEntity(d: UserDto, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): User;
|
|
||||||
fun toEntity(d: List<UserDto>, @Context userRepository: UserRepository, @Context fileRepository: FileRepository): List<User>;
|
|
||||||
|
|
||||||
fun map(e: User): String{
|
|
||||||
return e.username;
|
|
||||||
}
|
|
||||||
fun map(d: String, @Context userRepository: UserRepository): User{
|
|
||||||
val user = userRepository.findUserByUsername(d)
|
|
||||||
?: throw UsernameNotFoundException("User not found by username \"$d\"")
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -11,14 +11,14 @@ spring:
|
||||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
default-schema: jeobeardy-app
|
default-schema: jeobeardy-app
|
||||||
open-in-view: false
|
open-in-view: false
|
||||||
docker:
|
|
||||||
compose:
|
|
||||||
lifecycle-management: start-only
|
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 5MB
|
max-file-size: 1MB
|
||||||
max-request-size: 50MB
|
max-request-size: 40MB
|
||||||
# session:
|
session:
|
||||||
|
store-type: jdbc
|
||||||
|
jdbc:
|
||||||
|
initialize-schema: always
|
||||||
# redis:
|
# redis:
|
||||||
# flush-mode: on_save
|
# flush-mode: on_save
|
||||||
# namespace: spring:session
|
# namespace: spring:session
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ $dropdown-link-hover-bg: $dark-accented;
|
||||||
$modal-fade-transform: scale(.75);
|
$modal-fade-transform: scale(.75);
|
||||||
$breadcrumb-divider: quote(">");
|
$breadcrumb-divider: quote(">");
|
||||||
|
|
||||||
|
$border-radius: 0;
|
||||||
|
|
||||||
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
||||||
@import "bootstrap/scss/variables";
|
@import "bootstrap/scss/variables";
|
||||||
@import "bootstrap/scss/variables-dark";
|
@import "bootstrap/scss/variables-dark";
|
||||||
|
|
@ -55,9 +57,6 @@ $breadcrumb-divider: quote(">");
|
||||||
$form-file-button-bg: var(--#{$prefix}secondary-bg);
|
$form-file-button-bg: var(--#{$prefix}secondary-bg);
|
||||||
$form-file-button-hover-bg: var(--#{$prefix}tertiary-bg);
|
$form-file-button-hover-bg: var(--#{$prefix}tertiary-bg);
|
||||||
|
|
||||||
// $btn-border-radius: 0;
|
|
||||||
// $card-border-radius: 0;
|
|
||||||
|
|
||||||
/* Bootstrap Color Map adjustments */
|
/* Bootstrap Color Map adjustments */
|
||||||
$custom-colors: (
|
$custom-colors: (
|
||||||
"gray": $gray-500,
|
"gray": $gray-500,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { AnswerType } from '@/models/board/AnswerType';
|
||||||
import type { BoardEntry } from '@/models/board/BoardEntry';
|
import type { BoardEntry } from '@/models/board/BoardEntry';
|
||||||
|
import { QuestionType } from '@/models/board/QuestionType';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
const QUESTION_TYPE_SIMPLE_TEXT_ID = 1;
|
|
||||||
const QUESTION_TYPE_IMAGE_ID = 2;
|
|
||||||
const QUESTION_TYPE_AUDIO_ID = 3;
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
boardEntry: BoardEntry,
|
boardEntry: BoardEntry,
|
||||||
selectedQuestionIndex: number,
|
selectedQuestionIndex: number,
|
||||||
|
|
@ -70,14 +68,24 @@ function stopAudio(){
|
||||||
<div class="col h-100 mx-3 overflow-y-auto">
|
<div class="col h-100 mx-3 overflow-y-auto">
|
||||||
<div class="ratio ratio-16x9">
|
<div class="ratio ratio-16x9">
|
||||||
<div class="w-100 h-100 d-flex justify-content-center align-items-center">
|
<div class="w-100 h-100 d-flex justify-content-center align-items-center">
|
||||||
<template v-if="isQuestionShown">
|
<template v-if="isAnswerShown && boardEntry.answer.answerType === AnswerType.IMAGE">
|
||||||
|
<div class="h-75 w-100 d-flex justify-content-center align-items-center">
|
||||||
|
<img
|
||||||
|
v-if="boardEntry.answer.image"
|
||||||
|
:src="boardEntry.answer.image"
|
||||||
|
alt="User uploaded - No caption available"
|
||||||
|
class="h-100 w-100 object-contain"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="isQuestionShown">
|
||||||
<span v-if="boardEntry.questions.length === 0" class="fs-1">
|
<span v-if="boardEntry.questions.length === 0" class="fs-1">
|
||||||
No Question to show
|
No Question to show
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="question.questionType.id === QUESTION_TYPE_SIMPLE_TEXT_ID" class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
<span v-else-if="question.questionType === QuestionType.TEXT" class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
||||||
{{ question.text }}
|
{{ question.text }}
|
||||||
</span>
|
</span>
|
||||||
<template v-else-if="question.questionType.id === QUESTION_TYPE_IMAGE_ID">
|
<template v-else-if="question.questionType === QuestionType.IMAGE">
|
||||||
<div class="d-flex flex-column justify-content-center align-items-center h-100 w-100">
|
<div class="d-flex flex-column justify-content-center align-items-center h-100 w-100">
|
||||||
<span class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
<span class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
||||||
{{ question.text }}
|
{{ question.text }}
|
||||||
|
|
@ -92,7 +100,7 @@ function stopAudio(){
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="question.questionType.id === QUESTION_TYPE_AUDIO_ID">
|
<template v-else-if="question.questionType === QuestionType.AUDIO">
|
||||||
<div class="d-flex flex-column justify-content-center align-items-center h-100 w-100">
|
<div class="d-flex flex-column justify-content-center align-items-center h-100 w-100">
|
||||||
<span class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
<span class="text-center preserve-breaks" :style="`font-size: ${question.fontScaling}em`">
|
||||||
{{ question.text }}
|
{{ question.text }}
|
||||||
|
|
@ -121,21 +129,27 @@ function stopAudio(){
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Points -->
|
<!-- Points -->
|
||||||
<div class="position-absolute top-0 end-0">
|
<div class="position-absolute bottom-0 end-0">
|
||||||
<span class="fs-2">
|
<span class="fs-2">
|
||||||
{{ boardEntry.points }}
|
{{ boardEntry.points }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- title -->
|
||||||
|
<div class="position-absolute top-0 start-50 translate-middle-x">
|
||||||
|
<span class="fs-2">
|
||||||
|
{{ boardEntry.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<!-- Back to Board -->
|
<!-- Back to Board -->
|
||||||
<div class="position-absolute bottom-0 end-0">
|
<div class="position-absolute top-0 start-0">
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-outline-primary" @click="backToBoard">
|
<button class="btn btn-outline-primary mt-2" @click="backToBoard">
|
||||||
Back to Board
|
Back to Board
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Back to Board -->
|
<!-- Back to Board -->
|
||||||
<div class="position-absolute top-0 start-50 translate-middle-x">
|
<div class="position-absolute top-0 end-0">
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<button v-if="props.isQuestionShown" class="btn btn-outline-primary me-3" @click="hideQuestion">
|
<button v-if="props.isQuestionShown" class="btn btn-outline-primary me-3" @click="hideQuestion">
|
||||||
Hide Question
|
Hide Question
|
||||||
|
|
@ -151,9 +165,9 @@ function stopAudio(){
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Answer -->
|
<!-- Answer text -->
|
||||||
<div v-if="isAnswerShown" class="position-absolute bottom-0 start-50 translate-middle-x mb-2">
|
<div v-if="isAnswerShown" class="position-absolute bottom-0 start-50 translate-middle-x mb-2">
|
||||||
<div class="bg-primary p-2 rounded bg-opacity-50 fs-4 text-center">
|
<div class="bg-primary p-2 rounded fs-4 text-center">
|
||||||
Answer:<br>{{ boardEntry.answer.text }}
|
Answer:<br>{{ boardEntry.answer.text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ const router = useRouter();
|
||||||
|
|
||||||
const boards = ref<Array<Board>>([]);
|
const boards = ref<Array<Board>>([]);
|
||||||
|
|
||||||
function editBoard(board: Board){
|
function editBoard(board: Board) {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'create',
|
name: 'create',
|
||||||
params: {
|
params: {
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewBoard() {
|
function createNewBoard() {
|
||||||
|
|
@ -53,18 +53,28 @@ userService
|
||||||
<template v-for="board in boards" :key="board.id">
|
<template v-for="board in boards" :key="board.id">
|
||||||
<div class="col-4 mb-3">
|
<div class="col-4 mb-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header position-relative">
|
||||||
{{ board.boardName }}
|
{{ board.boardName }}
|
||||||
|
<button
|
||||||
|
class="d-hover position-absolute top-50 translate-middle-y end-0 me-1 btn btn-sm btn-outline-danger"
|
||||||
|
>
|
||||||
|
<!-- TODO -->
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'trash']" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-around align-items-center">
|
<div class="d-flex justify-content-around align-items-center">
|
||||||
<button class="btn btn-sm btn-primary" @click="editBoard(board)">
|
<button
|
||||||
|
class="btn btn-sm btn-primary"
|
||||||
|
@click="editBoard(board)"
|
||||||
|
>
|
||||||
<FontAwesomeIcon :icon="['fas', 'edit']" />
|
<FontAwesomeIcon :icon="['fas', 'edit']" />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-primary">
|
<button class="btn btn-sm btn-primary">
|
||||||
|
<!-- TODO -->
|
||||||
<FontAwesomeIcon :icon="['fas', 'play']" />
|
<FontAwesomeIcon :icon="['fas', 'play']" />
|
||||||
Play
|
Play
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -89,8 +99,17 @@ userService
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.spinny-size{
|
.spinny-size {
|
||||||
max-width: 6.66em;
|
max-width: 6.66em;
|
||||||
max-height: 6.66em;
|
max-height: 6.66em;
|
||||||
}
|
}
|
||||||
</style>
|
.d-hover {
|
||||||
|
display: initial;
|
||||||
|
opacity: 0%;
|
||||||
|
transition: opacity ease 0.33s;
|
||||||
|
}
|
||||||
|
div:hover > div > .d-hover {
|
||||||
|
opacity: 100%;
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,15 @@ function moveQuestionDown( index: number ) {
|
||||||
boardEntry.value.questions[index + 1] = tmp;
|
boardEntry.value.questions[index + 1] = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newImageUploaded( event: Event ) {
|
||||||
|
const element = event.currentTarget as HTMLInputElement;
|
||||||
|
let files = element.files;
|
||||||
|
if( files === null || files.length === 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boardEntry.value.answer.image = URL.createObjectURL(files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
function openBoard() {
|
function openBoard() {
|
||||||
emit("editBoard");
|
emit("editBoard");
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +200,16 @@ function openQuestion(categoryIndex: number, boardEntryIndex: number, questionIn
|
||||||
<label class="mt-2" for="answer-text">{{ t( 'board.answer.text' ) }}</label>
|
<label class="mt-2" for="answer-text">{{ t( 'board.answer.text' ) }}</label>
|
||||||
<textarea id="answer-text" v-model="boardEntry.answer.text" class="form-control mb-2"
|
<textarea id="answer-text" v-model="boardEntry.answer.text" class="form-control mb-2"
|
||||||
:placeholder="t( 'board.answer.text' )"></textarea>
|
:placeholder="t( 'board.answer.text' )"></textarea>
|
||||||
|
<template v-if="boardEntry.answer.answerType === AnswerType.IMAGE">
|
||||||
|
<label for="answer-image-input">{{ t( 'board.question.upload.image' ) }}</label>
|
||||||
|
<input
|
||||||
|
id="answer-image-input"
|
||||||
|
type="file"
|
||||||
|
class="form-control mb-2"
|
||||||
|
@change="newImageUploaded"
|
||||||
|
accept="image/png, image/jpeg"
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import type { Board } from '@/models/board/Board';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { QuestionType } from '@/models/board/QuestionType';
|
import { QuestionType } from '@/models/board/QuestionType';
|
||||||
|
import { useFileStore } from '@/stores/FileStore';
|
||||||
|
import { getFileKeyForQuestion } from '@/services/UtilService';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
|
@ -34,12 +36,14 @@ function openBoardEntry( categoryIndex: number, boardEntryIndex: number ) {
|
||||||
emit( "editBoardEntry", categoryIndex, boardEntryIndex );
|
emit( "editBoardEntry", categoryIndex, boardEntryIndex );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileStore = useFileStore();
|
||||||
function newImageUploaded( event: Event ) {
|
function newImageUploaded( event: Event ) {
|
||||||
const element = event.currentTarget as HTMLInputElement;
|
const element = event.currentTarget as HTMLInputElement;
|
||||||
let files = element.files;
|
let files = element.files;
|
||||||
if( files === null || files.length === 0 ) {
|
if( files === null || files.length === 0 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
fileStore.addFile(files[0], getFileKeyForQuestion(props.categoryIndex, props.boardEntryIndex, props.questionIndex));
|
||||||
question.value.image = URL.createObjectURL(files[0]);
|
question.value.image = URL.createObjectURL(files[0]);
|
||||||
}
|
}
|
||||||
function newAudioUploaded( event: Event ) {
|
function newAudioUploaded( event: Event ) {
|
||||||
|
|
@ -48,6 +52,7 @@ function newAudioUploaded( event: Event ) {
|
||||||
if( files === null || files.length === 0 ) {
|
if( files === null || files.length === 0 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
fileStore.addFile(files[0], getFileKeyForQuestion(props.categoryIndex, props.boardEntryIndex, props.questionIndex));
|
||||||
question.value.audio = URL.createObjectURL(files[0]);
|
question.value.audio = URL.createObjectURL(files[0]);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import CreatePanel from '@/components/blocks/CreatePanel.vue';
|
||||||
import BoardEntryView from '@/components/blocks/BoardEntryView.vue';
|
import BoardEntryView from '@/components/blocks/BoardEntryView.vue';
|
||||||
import { userService } from '@/services/UserService';
|
import { userService } from '@/services/UserService';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useFileStore } from '@/stores/FileStore';
|
||||||
|
|
||||||
const navbar = inject<Ref<InstanceType<typeof NavBar> | null>>(navbarKey);
|
const navbar = inject<Ref<InstanceType<typeof NavBar> | null>>(navbarKey);
|
||||||
const navbarHeight = computed(() => {
|
const navbarHeight = computed(() => {
|
||||||
|
|
@ -65,14 +66,20 @@ function hideAnswer() {
|
||||||
isAnswerShown.value = false;
|
isAnswerShown.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileStore = useFileStore();
|
||||||
const savingBoardInProgress = ref(false);
|
const savingBoardInProgress = ref(false);
|
||||||
function saveBoard() {
|
function saveBoard() {
|
||||||
savingBoardInProgress.value = true;
|
savingBoardInProgress.value = true;
|
||||||
|
const formData: FormData = new FormData();
|
||||||
|
formData.set('board', new Blob([JSON.stringify(board.value)], { 'type': 'application/json' } ));
|
||||||
|
for( const file of fileStore.files ){
|
||||||
|
formData.append('files', file.data, `${file.category}-${file.boardEntry}-${file.answer ? 'answer' : file.question!}`);
|
||||||
|
}
|
||||||
let savePromise;
|
let savePromise;
|
||||||
if( board.value.id ){
|
if( board.value.id ){
|
||||||
savePromise = userService.updateBoard(board.value);
|
savePromise = userService.updateBoard(board.value, formData);
|
||||||
} else {
|
} else {
|
||||||
savePromise = userService.saveNewBoard(board.value);
|
savePromise = userService.saveNewBoard(formData);
|
||||||
}
|
}
|
||||||
savePromise
|
savePromise
|
||||||
.then((savedBoard) => {
|
.then((savedBoard) => {
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@ class UserService {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
saveNewBoard(board: Board): Promise<Board> {
|
saveNewBoard(formData: FormData): Promise<Board> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios
|
axios
|
||||||
.post(`${ENV.API_BASE_URL}/user/boards`, board, {
|
.post(`${ENV.API_BASE_URL}/user/boards`, formData, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
|
@ -62,13 +62,13 @@ class UserService {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
updateBoard(board: Board): Promise<Board> {
|
updateBoard(board: Board, formData: FormData): Promise<Board> {
|
||||||
if( board.id === undefined ){
|
if( board.id === undefined ){
|
||||||
throw new Error("New board cant be updated");
|
throw new Error("New board cant be updated");
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios
|
axios
|
||||||
.put(`${ENV.API_BASE_URL}/user/boards/${board.id}`, board, {
|
.put(`${ENV.API_BASE_URL}/user/boards/${board.id}`, formData, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,30 @@ import type { InjectionKey, Ref } from 'vue';
|
||||||
import type NavBar from '@/components/blocks/NavBar.vue';
|
import type NavBar from '@/components/blocks/NavBar.vue';
|
||||||
|
|
||||||
export const infoModalShowFnKey = Symbol() as InjectionKey<Function>;
|
export const infoModalShowFnKey = Symbol() as InjectionKey<Function>;
|
||||||
export const navbarKey = Symbol() as InjectionKey<Ref<InstanceType<typeof NavBar> | undefined>>;
|
export const navbarKey = Symbol() as InjectionKey<Ref<InstanceType<typeof NavBar> | undefined>>;
|
||||||
|
|
||||||
|
export interface FileAssociation {
|
||||||
|
category: number,
|
||||||
|
boardEntry: number,
|
||||||
|
answer: boolean,
|
||||||
|
question?: number,
|
||||||
|
}
|
||||||
|
export interface FileAssociationWFile extends FileAssociation {
|
||||||
|
data: File
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFileKeyForQuestion(categoryIndex: number, boardEntryIndex: number, questionIndex: number): FileAssociation {
|
||||||
|
return {
|
||||||
|
category: categoryIndex,
|
||||||
|
boardEntry: boardEntryIndex,
|
||||||
|
answer: false,
|
||||||
|
question: questionIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function getFileKeyForAnswer(categoryIndex: number, boardEntryIndex: number): FileAssociation {
|
||||||
|
return {
|
||||||
|
category: categoryIndex,
|
||||||
|
boardEntry: boardEntryIndex,
|
||||||
|
answer: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import type { FileAssociation, FileAssociationWFile } from '@/services/UtilService';
|
||||||
|
|
||||||
|
export const useFileStore = defineStore('file', () => {
|
||||||
|
const files = ref<Array<FileAssociationWFile>>([]);
|
||||||
|
|
||||||
|
function addFile(file: File, fileAssociation: FileAssociation) {
|
||||||
|
const fileWData = {
|
||||||
|
...fileAssociation,
|
||||||
|
data: file,
|
||||||
|
};
|
||||||
|
const idx = files.value.findIndex(x =>
|
||||||
|
x.category === fileAssociation.category &&
|
||||||
|
x.boardEntry === fileAssociation.boardEntry &&
|
||||||
|
x.answer === fileAssociation.answer &&
|
||||||
|
x.question === x.question
|
||||||
|
);
|
||||||
|
if( idx === -1 ){
|
||||||
|
files.value.splice(idx, 1, fileWData);
|
||||||
|
} else {
|
||||||
|
files.value.push(fileWData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function deleteFile(fileAssociation: FileAssociation) {
|
||||||
|
const idxToDel = files.value.findIndex(x =>
|
||||||
|
x.category === fileAssociation.category &&
|
||||||
|
x.boardEntry === fileAssociation.boardEntry &&
|
||||||
|
x.answer === fileAssociation.answer &&
|
||||||
|
x.question === x.question
|
||||||
|
);
|
||||||
|
if(idxToDel !== -1){
|
||||||
|
files.value.splice(idxToDel, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// state
|
||||||
|
files,
|
||||||
|
|
||||||
|
// functions
|
||||||
|
addFile,
|
||||||
|
deleteFile,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -5,7 +5,11 @@ import type { Board } from '@/models/board/Board';
|
||||||
import { userService } from '@/services/UserService';
|
import { userService } from '@/services/UserService';
|
||||||
import { Player } from '@/models/game/Player';
|
import { Player } from '@/models/game/Player';
|
||||||
import { Client as StompClient } from '@stomp/stompjs';
|
import { Client as StompClient } from '@stomp/stompjs';
|
||||||
import { GAME_STATUS_CONST, gameService, SESSION_GAME_KEY } from '@/services/GameService';
|
import {
|
||||||
|
GAME_STATUS_CONST,
|
||||||
|
gameService,
|
||||||
|
SESSION_GAME_KEY,
|
||||||
|
} from '@/services/GameService';
|
||||||
import type { Game } from '@/models/game/Game';
|
import type { Game } from '@/models/game/Game';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import type { GameMessage } from '@/models/dto/messages/GameMessage';
|
import type { GameMessage } from '@/models/dto/messages/GameMessage';
|
||||||
|
|
@ -16,9 +20,9 @@ import type { PlayerChoice } from '@/models/game/PlayerChoice';
|
||||||
export const useGameStore = defineStore('game', () => {
|
export const useGameStore = defineStore('game', () => {
|
||||||
const board = ref<Board | null>(null);
|
const board = ref<Board | null>(null);
|
||||||
const players = ref<Array<Player>>([]);
|
const players = ref<Array<Player>>([]);
|
||||||
const playerCount = computed( () => {
|
const playerCount = computed(() => {
|
||||||
return players.value.length;
|
return players.value.length;
|
||||||
})
|
});
|
||||||
const hostUsername = ref('');
|
const hostUsername = ref('');
|
||||||
const isHost = ref(false);
|
const isHost = ref(false);
|
||||||
const self = ref<Player | null>(null);
|
const self = ref<Player | null>(null);
|
||||||
|
|
@ -54,7 +58,11 @@ export const useGameStore = defineStore('game', () => {
|
||||||
board.value = b;
|
board.value = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGameProperties(game: Game, isHostAttr: boolean, playerSelf: Player | null){
|
function setGameProperties(
|
||||||
|
game: Game,
|
||||||
|
isHostAttr: boolean,
|
||||||
|
playerSelf: Player | null,
|
||||||
|
) {
|
||||||
gameUuid = game.uuid;
|
gameUuid = game.uuid;
|
||||||
gameInviteCode.value = game.inviteCode;
|
gameInviteCode.value = game.inviteCode;
|
||||||
players.value = game.players;
|
players.value = game.players;
|
||||||
|
|
@ -75,7 +83,7 @@ export const useGameStore = defineStore('game', () => {
|
||||||
setGameProperties(game, true, null);
|
setGameProperties(game, true, null);
|
||||||
return connect();
|
return connect();
|
||||||
})
|
})
|
||||||
.then( () => {
|
.then(() => {
|
||||||
resolve(retGame);
|
resolve(retGame);
|
||||||
})
|
})
|
||||||
.catch((error: AxiosError) => {
|
.catch((error: AxiosError) => {
|
||||||
|
|
@ -85,9 +93,9 @@ export const useGameStore = defineStore('game', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForGame(): Game | null{
|
function checkForGame(): Game | null {
|
||||||
const gameJson = sessionStorage.getItem(SESSION_GAME_KEY)
|
const gameJson = sessionStorage.getItem(SESSION_GAME_KEY);
|
||||||
if( gameJson === null ){
|
if (gameJson === null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return JSON.parse(gameJson) as Game;
|
return JSON.parse(gameJson) as Game;
|
||||||
|
|
@ -100,12 +108,12 @@ export const useGameStore = defineStore('game', () => {
|
||||||
gameService
|
gameService
|
||||||
.getGameByInviteCodeOrUuid(inviteCode)
|
.getGameByInviteCodeOrUuid(inviteCode)
|
||||||
.then((game) => {
|
.then((game) => {
|
||||||
retGame = game
|
retGame = game;
|
||||||
sessionStorage.setItem(SESSION_GAME_KEY, JSON.stringify(game))
|
sessionStorage.setItem(SESSION_GAME_KEY, JSON.stringify(game));
|
||||||
setGameProperties(game, false, null);
|
setGameProperties(game, false, null);
|
||||||
return connect();
|
return connect();
|
||||||
})
|
})
|
||||||
.then( () => {
|
.then(() => {
|
||||||
resolve(retGame);
|
resolve(retGame);
|
||||||
})
|
})
|
||||||
.catch((error: AxiosError) => {
|
.catch((error: AxiosError) => {
|
||||||
|
|
@ -115,7 +123,7 @@ export const useGameStore = defineStore('game', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function joinGame(playerName: string): Promise<void> {
|
function joinGame(playerName: string): Promise<void> {
|
||||||
if( gameStatus.value === GAME_STATUS_CONST.nA ){
|
if (gameStatus.value === GAME_STATUS_CONST.nA) {
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
return new Promise<void>((resolve, _reject) => {
|
return new Promise<void>((resolve, _reject) => {
|
||||||
|
|
@ -134,9 +142,9 @@ export const useGameStore = defineStore('game', () => {
|
||||||
const stompClient = new StompClient({
|
const stompClient = new StompClient({
|
||||||
brokerURL: 'ws://localhost:8008/ws',
|
brokerURL: 'ws://localhost:8008/ws',
|
||||||
});
|
});
|
||||||
const isConnected = computed( () => {
|
const isConnected = computed(() => {
|
||||||
return stompClient.connected;
|
return stompClient.connected;
|
||||||
})
|
});
|
||||||
|
|
||||||
stompClient.onStompError = (frame) => {
|
stompClient.onStompError = (frame) => {
|
||||||
console.error('Broker reported error: ' + frame.headers['message']);
|
console.error('Broker reported error: ' + frame.headers['message']);
|
||||||
|
|
@ -144,7 +152,7 @@ export const useGameStore = defineStore('game', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function connect(): Promise<void> {
|
function connect(): Promise<void> {
|
||||||
return new Promise<void>(( resolve, reject ) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
stompClient.onConnect = (_frame) => {
|
stompClient.onConnect = (_frame) => {
|
||||||
connected.value = true;
|
connected.value = true;
|
||||||
subscribeToCommonRoutes();
|
subscribeToCommonRoutes();
|
||||||
|
|
@ -152,10 +160,10 @@ export const useGameStore = defineStore('game', () => {
|
||||||
};
|
};
|
||||||
stompClient.onWebSocketError = (error) => {
|
stompClient.onWebSocketError = (error) => {
|
||||||
console.error('Error with websocket', error);
|
console.error('Error with websocket', error);
|
||||||
reject()
|
reject();
|
||||||
};
|
};
|
||||||
stompClient.activate();
|
stompClient.activate();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function subscribeToCommonRoutes() {
|
function subscribeToCommonRoutes() {
|
||||||
|
|
@ -170,112 +178,149 @@ export const useGameStore = defineStore('game', () => {
|
||||||
players.value.push(JSON.parse(message.body) as Player);
|
players.value.push(JSON.parse(message.body) as Player);
|
||||||
});
|
});
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/started`, (message) => {
|
stompClient.subscribe(`/topic/game/${gameUuid}/started`, (message) => {
|
||||||
const gameMessage = JSON.parse(message.body) as GameMessage
|
const gameMessage = JSON.parse(message.body) as GameMessage;
|
||||||
if( gameMessage.uuid !== gameUuid ){
|
if (gameMessage.uuid !== gameUuid) {
|
||||||
throw new Error("Got event for different Game!");
|
throw new Error('Got event for different Game!');
|
||||||
}
|
}
|
||||||
router.push({name: 'game', params: { uuid: gameUuid }})
|
router.push({ name: 'game', params: { uuid: gameUuid } });
|
||||||
});
|
});
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/boardentry/selected`, (message) => {
|
stompClient.subscribe(
|
||||||
const messageBody = JSON.parse(message.body);
|
`/topic/game/${gameUuid}/boardentry/selected`,
|
||||||
if( !isHost.value ){
|
(message) => {
|
||||||
showingAnswer.value = false;
|
const messageBody = JSON.parse(message.body);
|
||||||
showingQuestion.value = false;
|
if (!isHost.value) {
|
||||||
acceptAnswers.value = true;
|
showingAnswer.value = false;
|
||||||
} else {
|
showingQuestion.value = false;
|
||||||
currentQuestion.value = messageBody.question;
|
acceptAnswers.value = true;
|
||||||
}
|
|
||||||
});
|
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/board/selected`, (message) => {
|
|
||||||
if( !isHost.value ){
|
|
||||||
showingAnswer.value = false;
|
|
||||||
showingQuestion.value = false;
|
|
||||||
acceptAnswers.value = false;
|
|
||||||
}
|
|
||||||
for( const i in players.value ){
|
|
||||||
players.value[i].answerText = '';
|
|
||||||
}
|
|
||||||
categoryIndex.value = null;
|
|
||||||
boardEntryIndex.value = null;
|
|
||||||
questionIndex.value = null;
|
|
||||||
});
|
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/points/adjusted`, (message) => {
|
|
||||||
const messageBody = JSON.parse(message.body);
|
|
||||||
const playerIndex = players.value.findIndex( playerEntry => playerEntry.uuid === messageBody.playerUuid );
|
|
||||||
players.value[playerIndex].points = messageBody.adjustedPoints;
|
|
||||||
});
|
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/player/answer/revealed`, (message) => {
|
|
||||||
currentAnswer.value = JSON.parse(message.body) as Answer;
|
|
||||||
});
|
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/player/answer/update`, (message) => {
|
|
||||||
const messageBody = JSON.parse(message.body);
|
|
||||||
const playerIndex = players.value.findIndex( playerEntry => playerEntry.uuid === messageBody.playerUuid );
|
|
||||||
players.value[playerIndex].answerText = messageBody.answerText;
|
|
||||||
});
|
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/player/buzzered`, (message) => {
|
|
||||||
const messageBody = JSON.parse(message.body);
|
|
||||||
for( const i in players.value ){
|
|
||||||
if( players.value[i].uuid === messageBody.playerUuid ){
|
|
||||||
players.value[i].isAnswering = true;
|
|
||||||
//TODO Play Buzzer sound!
|
|
||||||
} else {
|
} else {
|
||||||
players.value[i].isAnswering = false;
|
currentQuestion.value = messageBody.question;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
acceptAnswers.value = false;
|
);
|
||||||
//TODO if( audioInstance ) If audio was playing - stop!
|
stompClient.subscribe(
|
||||||
});
|
`/topic/game/${gameUuid}/board/selected`,
|
||||||
|
(message) => {
|
||||||
|
if (!isHost.value) {
|
||||||
|
showingAnswer.value = false;
|
||||||
|
showingQuestion.value = false;
|
||||||
|
acceptAnswers.value = false;
|
||||||
|
}
|
||||||
|
for (const i in players.value) {
|
||||||
|
players.value[i].answerText = '';
|
||||||
|
}
|
||||||
|
categoryIndex.value = null;
|
||||||
|
boardEntryIndex.value = null;
|
||||||
|
questionIndex.value = null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/points/adjusted`,
|
||||||
|
(message) => {
|
||||||
|
const messageBody = JSON.parse(message.body);
|
||||||
|
const playerIndex = players.value.findIndex(
|
||||||
|
(playerEntry) => playerEntry.uuid === messageBody.playerUuid,
|
||||||
|
);
|
||||||
|
players.value[playerIndex].points = messageBody.adjustedPoints;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/player/answer/revealed`,
|
||||||
|
(message) => {
|
||||||
|
currentAnswer.value = JSON.parse(message.body) as Answer;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/player/answer/update`,
|
||||||
|
(message) => {
|
||||||
|
const messageBody = JSON.parse(message.body);
|
||||||
|
const playerIndex = players.value.findIndex(
|
||||||
|
(playerEntry) => playerEntry.uuid === messageBody.playerUuid,
|
||||||
|
);
|
||||||
|
players.value[playerIndex].answerText = messageBody.answerText;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/player/buzzered`,
|
||||||
|
(message) => {
|
||||||
|
const messageBody = JSON.parse(message.body);
|
||||||
|
for (const i in players.value) {
|
||||||
|
if (players.value[i].uuid === messageBody.playerUuid) {
|
||||||
|
players.value[i].isAnswering = true;
|
||||||
|
//TODO Play Buzzer sound!
|
||||||
|
} else {
|
||||||
|
players.value[i].isAnswering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acceptAnswers.value = false;
|
||||||
|
//TODO if( audioInstance ) If audio was playing - stop!
|
||||||
|
},
|
||||||
|
);
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/player/chose`, (message) => {
|
stompClient.subscribe(`/topic/game/${gameUuid}/player/chose`, (message) => {
|
||||||
const messageBody = JSON.parse(message.body) as null | PlayerChoice;
|
const messageBody = JSON.parse(message.body) as null | PlayerChoice;
|
||||||
currentPlayerChoice.value = messageBody;
|
currentPlayerChoice.value = messageBody;
|
||||||
});
|
});
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/player/canchoose`, (message) => {
|
stompClient.subscribe(
|
||||||
});
|
`/topic/game/${gameUuid}/player/canchoose`,
|
||||||
|
(message) => {},
|
||||||
|
);
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/player/left`, (message) => {
|
stompClient.subscribe(`/topic/game/${gameUuid}/player/left`, (message) => {
|
||||||
const messageBody = JSON.parse(message.body);
|
const messageBody = JSON.parse(message.body);
|
||||||
const playerIndex = players.value.findIndex( playerEntry => playerEntry.uuid === messageBody.playerUuid );
|
const playerIndex = players.value.findIndex(
|
||||||
|
(playerEntry) => playerEntry.uuid === messageBody.playerUuid,
|
||||||
|
);
|
||||||
players.value.splice(playerIndex, 1);
|
players.value.splice(playerIndex, 1);
|
||||||
});
|
});
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/question/audio/playing`, (message) => {
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/question/audio/playing`,
|
||||||
});
|
(message) => {},
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/question/audio/stopped`, (message) => {
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
});
|
`/topic/game/${gameUuid}/question/audio/stopped`,
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/question/revealed`, (message) => {
|
(message) => {},
|
||||||
currentQuestion.value = JSON.parse(message.body) as Question;
|
);
|
||||||
});
|
stompClient.subscribe(
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/question/locked`, (message) => {
|
`/topic/game/${gameUuid}/question/revealed`,
|
||||||
|
(message) => {
|
||||||
});
|
currentQuestion.value = JSON.parse(message.body) as Question;
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/question/status/update`, (message) => {
|
},
|
||||||
|
);
|
||||||
});
|
stompClient.subscribe(
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/question/hidden`, (message) => {
|
`/topic/game/${gameUuid}/question/locked`,
|
||||||
|
(message) => {},
|
||||||
});
|
);
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/questionlayer/selected`, (message) => {
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/question/status/update`,
|
||||||
});
|
(message) => {},
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/answer/revealed`, (message) => {
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
});
|
`/topic/game/${gameUuid}/question/hidden`,
|
||||||
stompClient.subscribe(`/topic/game/${gameUuid}/answer/hidden`, (message) => {
|
(message) => {},
|
||||||
|
);
|
||||||
});
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/questionlayer/selected`,
|
||||||
|
(message) => {},
|
||||||
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/answer/revealed`,
|
||||||
|
(message) => {},
|
||||||
|
);
|
||||||
|
stompClient.subscribe(
|
||||||
|
`/topic/game/${gameUuid}/answer/hidden`,
|
||||||
|
(message) => {},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disconnect() {
|
function disconnect() {
|
||||||
stompClient.deactivate();
|
stompClient.deactivate();
|
||||||
connected.value = false;
|
connected.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startGame() {
|
function startGame() {
|
||||||
if( !isHost.value ){
|
if (!isHost.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = {
|
const message = {
|
||||||
content: 'start'
|
content: 'start',
|
||||||
};
|
};
|
||||||
stompClient.publish({
|
stompClient.publish({
|
||||||
destination: `/app/host/game/${gameUuid}/start`,
|
destination: `/app/host/game/${gameUuid}/start`,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue