diff --git a/src/main/java/com/uniovi/components/generators/QuestionGenerator.java b/src/main/java/com/uniovi/components/generators/QuestionGenerator.java index f1c01ffd..b91061a4 100644 --- a/src/main/java/com/uniovi/components/generators/QuestionGenerator.java +++ b/src/main/java/com/uniovi/components/generators/QuestionGenerator.java @@ -10,7 +10,7 @@ @Component public interface QuestionGenerator { - List getQuestions(String language) throws IOException; + List getQuestions(String language) throws IOException, InterruptedException; - List getQuestions(String language, JsonNode question, Category cat) throws IOException; + List getQuestions(String language, JsonNode question, Category cat) throws IOException, InterruptedException; } diff --git a/src/main/java/com/uniovi/components/generators/QuestionGeneratorV2.java b/src/main/java/com/uniovi/components/generators/QuestionGeneratorV2.java index efb1d8f6..7decf49b 100644 --- a/src/main/java/com/uniovi/components/generators/QuestionGeneratorV2.java +++ b/src/main/java/com/uniovi/components/generators/QuestionGeneratorV2.java @@ -16,8 +16,10 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import java.util.Random; public class QuestionGeneratorV2 implements QuestionGenerator{ @@ -27,6 +29,8 @@ public class QuestionGeneratorV2 implements QuestionGenerator{ private String answer_placeholder; private String language; + private Random random = new SecureRandom(); + public QuestionGeneratorV2(JsonNode jsonNode) { this.jsonNode = jsonNode; this.language_placeholder = jsonNode.get("language_placeholder").textValue(); @@ -35,7 +39,7 @@ public QuestionGeneratorV2(JsonNode jsonNode) { } @Override - public List getQuestions(String language) throws IOException { + public List getQuestions(String language) throws IOException, InterruptedException { this.language = language; List questions = new ArrayList<>(); JsonNode categories = jsonNode.findValue("categories"); @@ -51,12 +55,12 @@ public List getQuestions(String language) throws IOException { } @Override - public List getQuestions(String language, JsonNode question, Category cat) throws IOException { + public List getQuestions(String language, JsonNode question, Category cat) throws IOException, InterruptedException { this.language = language; return this.generateQuestion(question, cat); } - private List generateQuestion(JsonNode question, Category cat) throws IOException { + private List generateQuestion(JsonNode question, Category cat) throws IOException, InterruptedException { // Get the SPARQL query from the JSON String query = question.get("sparqlQuery").textValue(); @@ -85,14 +89,16 @@ private List generateQuestion(JsonNode question, Category cat) throws List options = this.generateOptions(results, correctAnswer, answerLabel); options.add(correct); - // Generate the question statement - String questionStatement = statement.replace(question_placeholder, result.path(questionLabel).path("value").asText()); + if (statement != null) { + // Generate the question statement + String questionStatement = statement.replace(question_placeholder, result.path(questionLabel).path("value").asText()); - // Generate the question - Question q = new Question(questionStatement, options, correct, cat, language); + // Generate the question + Question q = new Question(questionStatement, options, correct, cat, language); - // Add the question to the list - questions.add(q); + // Add the question to the list + questions.add(q); + } } return questions; } @@ -103,8 +109,8 @@ private List generateOptions(JsonNode results, String correctAnswer, Str int size = results.size(); int tries = 0; - while (options.size() < 3 && tries < 10){ - int random = (int) (Math.random() * size); + while (options.size() < 3 && tries < 10) { + int random = (int) (this.random.nextFloat() * size); String option = results.get(random).path(answerLabel).path("value").asText(); if (!option.equals(correctAnswer) && !usedOptions.contains(option) ) { usedOptions.add(option); @@ -130,41 +136,28 @@ private String prepareStatement(JsonNode question) { return null; } - private JsonNode getQueryResult(String query) throws IOException { + private JsonNode getQueryResult(String query) throws IOException, InterruptedException { System.out.println("Query: " + query); HttpClient client = HttpClient.newHttpClient(); JsonNode resultsNode; - try { - - String endpointUrl = "https://query.wikidata.org/sparql?query=" + - URLEncoder.encode(query, StandardCharsets.UTF_8) + - "&format=json"; + String endpointUrl = "https://query.wikidata.org/sparql?query=" + + URLEncoder.encode(query, StandardCharsets.UTF_8) + + "&format=json"; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(endpointUrl)) - .header("Accept", "application/json") - .build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(endpointUrl)) + .header("Accept", "application/json") + .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - // Process the JSON response using Jackson ObjectMapper - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode jsonResponse = objectMapper.readTree(response.body()); + // Process the JSON response using Jackson ObjectMapper + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonResponse = objectMapper.readTree(response.body()); - // Access the data from the JSON response - resultsNode = jsonResponse.path("results").path("bindings"); - - } catch (InterruptedException e) { - throw new QuestionGeneratorException("Generation of questions was interrupted"); - } + // Access the data from the JSON response + resultsNode = jsonResponse.path("results").path("bindings"); return resultsNode; - - } - - private static class QuestionGeneratorException extends RuntimeException { - public QuestionGeneratorException(String message) { - super(message); - } } } diff --git a/src/main/java/com/uniovi/configuration/SecurityConfig.java b/src/main/java/com/uniovi/configuration/SecurityConfig.java index c0af5d20..eee1d277 100644 --- a/src/main/java/com/uniovi/configuration/SecurityConfig.java +++ b/src/main/java/com/uniovi/configuration/SecurityConfig.java @@ -47,7 +47,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/signup/**").permitAll() .requestMatchers("/api/**").permitAll() .requestMatchers("/game/**").authenticated() + .requestMatchers("/multiplayerGame/**").authenticated() + .requestMatchers("/lobby/**").authenticated() .requestMatchers("/ranking/playerRanking").authenticated() + .requestMatchers("/player/admin/**").hasAuthority("ROLE_ADMIN") .requestMatchers("/**").permitAll() ).formLogin( form -> form diff --git a/src/main/java/com/uniovi/controllers/GameController.java b/src/main/java/com/uniovi/controllers/GameController.java index ad8a7506..02c0cc85 100644 --- a/src/main/java/com/uniovi/controllers/GameController.java +++ b/src/main/java/com/uniovi/controllers/GameController.java @@ -1,12 +1,16 @@ package com.uniovi.controllers; import com.uniovi.entities.GameSession; +import com.uniovi.entities.MultiplayerSession; import com.uniovi.entities.Player; import com.uniovi.entities.Question; import com.uniovi.services.GameSessionService; +import com.uniovi.services.MultiplayerSessionService; import com.uniovi.services.PlayerService; import com.uniovi.services.QuestionService; import jakarta.servlet.http.HttpSession; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -16,7 +20,7 @@ import java.security.Principal; import java.time.Duration; import java.time.LocalDateTime; -import java.util.Optional; +import java.util.*; @Controller public class GameController { @@ -24,11 +28,16 @@ public class GameController { private final GameSessionService gameSessionService; private final PlayerService playerService; + private final MultiplayerSessionService multiplayerSessionService; + + private boolean isMultiPlayer; + public GameController(QuestionService questionService, GameSessionService gameSessionService, - PlayerService playerService) { + PlayerService playerService, MultiplayerSessionService multiplayerSessionService) { this.questionService = questionService; this.gameSessionService = gameSessionService; this.playerService = playerService; + this.multiplayerSessionService = multiplayerSessionService; } @@ -46,6 +55,60 @@ public String getGame(HttpSession session, Model model, Principal principal) { } } else { gameSession = gameSessionService.startNewGame(getLoggedInPlayer(principal)); + playerService.deleteMultiplayerCode(gameSession.getPlayer().getId()); + session.setAttribute("gameSession", gameSession); + + } + + model.addAttribute("question", gameSession.getCurrentQuestion()); + model.addAttribute("questionDuration", getRemainingTime(gameSession)); + return "game/basicGame"; + } + + @GetMapping("/multiplayerGame") + public String getMultiplayerGame() { + return "game/multiplayerGame"; + } + + @GetMapping("/multiplayerGame/{code}") + public String joinMultiplayerGame(@PathVariable String code, HttpSession session, Principal principal, Model model) { + Optional player = playerService.getUserByUsername(principal.getName()); + Player p = player.orElse(null); + isMultiPlayer=true; + if(playerService.changeMultiplayerCode(p.getId(),code)){ + multiplayerSessionService.addToLobby(code,p.getId()); + model.addAttribute("multiplayerGameCode",code); + session.setAttribute("multiplayerCode",code); + return "redirect:/game/lobby"; + } else { + return "redirect:/multiplayerGame"; + } + } + + @GetMapping("/multiplayerGame/createGame") + public String createMultiplayerGame(HttpSession session, Principal principal, Model model) { + Optional player = playerService.getUserByUsername(principal.getName()); + Player p = player.orElse(null); + String code=""+playerService.createMultiplayerGame(p.getId()); + multiplayerSessionService.multiCreate(code,p.getId()); + session.setAttribute("multiplayerCode",code); + isMultiPlayer=true; + return "redirect:/game/lobby"; + } + + @GetMapping("/startMultiplayerGame") + public String startMultiplayerGame(HttpSession session, Model model, Principal principal) { + GameSession gameSession = (GameSession) session.getAttribute("gameSession"); + if(! isMultiPlayer){ + return "index"; + } + if (gameSession != null) { + if (checkUpdateGameSession(gameSession, session)) { + return "game/fragments/gameFinished"; + } + } else { + gameSession = gameSessionService.startNewMultiplayerGame(getLoggedInPlayer(principal), + playerService.getUserByUsername(principal.getName()).get().getMultiplayerCode()); session.setAttribute("gameSession", gameSession); } @@ -54,6 +117,53 @@ public String getGame(HttpSession session, Model model, Principal principal) { return "game/basicGame"; } + @GetMapping("/multiplayerGame/endGame/{code}") + public String endMultiplayerGame(Model model,@PathVariable String code) { + model.addAttribute("code",code); + return "ranking/multiplayerRanking"; + } + @GetMapping("/endGameList/{code}") + @ResponseBody + public Map endMultiplayerGameTable(@PathVariable String code) { + Map playerScores = multiplayerSessionService.getPlayersWithScores(Integer.parseInt(code)); + Map playersNameWithScore=new HashMap<>(); + for (Map.Entry player : playerScores.entrySet()) { + String playerName = player.getKey().getUsername(); + String playerScoreValue; + if(player.getValue()==-1){ + playerScoreValue="N/A"; + }else{ + playerScoreValue=""+player.getValue(); + } + playersNameWithScore.put(playerName, playerScoreValue); + } + return playersNameWithScore; + } + + @GetMapping("/game/lobby/{code}") + @ResponseBody + public List updatePlayerList(@PathVariable String code) { + Map players= multiplayerSessionService.getPlayersWithScores(Integer.parseInt(code)); + List playerNames = new ArrayList<>(); + for (Map.Entry player : players.entrySet()) { + playerNames.add(player.getKey().getUsername()); + } + Collections.sort(playerNames); + return playerNames; + } + @GetMapping("/game/lobby") + public String createLobby( HttpSession session, Model model) { + int code = Integer.parseInt((String)session.getAttribute("multiplayerCode")); + List players=playerService.getUsersByMultiplayerCode(code); + model.addAttribute("players",players); + model.addAttribute("code",session.getAttribute("multiplayerCode")); + return "/game/lobby"; + } + + @GetMapping("/game/startMultiplayerGame") + public String startMultiplayerGame( HttpSession session, Model model) { + return "/game/lobby"; + } /** * This method is used to check the answer for a specific question @@ -65,7 +175,7 @@ public String getGame(HttpSession session, Model model, Principal principal) { * shown or the timeOutFailure view is shown. */ @GetMapping("/game/{idQuestion}/{idAnswer}") - public String getCheckResult(@PathVariable Long idQuestion, @PathVariable Long idAnswer, Model model, HttpSession session) { + public String getCheckResult(@PathVariable Long idQuestion, @PathVariable Long idAnswer, Model model, HttpSession session,Principal principal) { GameSession gameSession = (GameSession) session.getAttribute("gameSession"); if (gameSession == null) { return "redirect:/game"; @@ -79,39 +189,45 @@ public String getCheckResult(@PathVariable Long idQuestion, @PathVariable Long i if(idAnswer == -1 || getRemainingTime(gameSession) <= 0) { - //model.addAttribute("correctAnswer", gameSession.getCurrentQuestion().getCorrectAnswer()); - //model.addAttribute("messageKey", "timeRunOut.result"); - //model.addAttribute("logoImage", "/images/logo_incorrect.svg"); gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); gameSession.addQuestion(false, 0); } else if(questionService.checkAnswer(idQuestion, idAnswer)) { - //model.addAttribute("messageKey", "correctAnswer.result"); - //model.addAttribute("logoImage", "/images/logo_correct.svg"); - if (!gameSession.isAnswered(gameSession.getCurrentQuestion())) { gameSession.addQuestion(true, getRemainingTime(gameSession)); gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); } } else { - //model.addAttribute("correctAnswer", gameSession.getCurrentQuestion().getCorrectAnswer()); - //model.addAttribute("messageKey", "failedAnswer.result"); - //model.addAttribute("logoImage", "/images/logo_incorrect.svg"); gameSession.addAnsweredQuestion(gameSession.getCurrentQuestion()); gameSession.addQuestion(false, 0); } session.setAttribute("hasJustAnswered", true); gameSession.getNextQuestion(); - //return "game/fragments/questionResult"; - return updateGame(model, session); + return updateGame(model, session, principal); } @GetMapping("/game/update") - public String updateGame(Model model, HttpSession session) { + public String updateGame(Model model, HttpSession session, Principal principal) { GameSession gameSession = (GameSession) session.getAttribute("gameSession"); Question nextQuestion = gameSession.getCurrentQuestion(); + if(nextQuestion == null && isMultiPlayer/*gameSession.getPlayer().getMultiplayerCode()!=null session.getAttribute("multiplayerCode") !=null*/){ + gameSessionService.endGame(gameSession); + + int code = Integer.parseInt((String)session.getAttribute("multiplayerCode")); + List players=playerService.getUsersByMultiplayerCode(code); + model.addAttribute("players",players); + model.addAttribute("code",session.getAttribute("multiplayerCode")); + session.removeAttribute("gameSession"); + + Optional player = playerService.getUserByUsername(principal.getName()); + Player p = player.orElse(null); + playerService.setScoreMultiplayerCode(p.getId(),""+gameSession.getScore()); + multiplayerSessionService.changeScore(p.getMultiplayerCode()+"",p.getId(),gameSession.getScore()); + isMultiPlayer=false; + return "game/multiFinished"; + } if (nextQuestion == null) { gameSessionService.endGame(gameSession); session.removeAttribute("gameSession"); diff --git a/src/main/java/com/uniovi/controllers/PlayersController.java b/src/main/java/com/uniovi/controllers/PlayersController.java index d291b6c6..0df52d19 100644 --- a/src/main/java/com/uniovi/controllers/PlayersController.java +++ b/src/main/java/com/uniovi/controllers/PlayersController.java @@ -1,42 +1,56 @@ package com.uniovi.controllers; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.uniovi.configuration.SecurityConfig; +import com.uniovi.dto.RoleDto; +import com.uniovi.entities.Associations; import com.uniovi.entities.GameSession; import com.uniovi.entities.Player; -import com.uniovi.services.GameSessionService; -import com.uniovi.services.PlayerService; +import com.uniovi.entities.Role; +import com.uniovi.services.*; import com.uniovi.validators.SignUpValidator; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.*; import com.uniovi.dto.PlayerDto; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.security.Principal; import java.util.Optional; +import java.util.List; @Controller public class PlayersController { private final PlayerService playerService; + private final RoleService roleService; + private QuestionService questionService; private final SignUpValidator signUpValidator; private final GameSessionService gameSessionService; @Autowired - public PlayersController(PlayerService playerService, SignUpValidator signUpValidator, GameSessionService gameSessionService) { + public PlayersController(PlayerService playerService, SignUpValidator signUpValidator, GameSessionService gameSessionService, + RoleService roleService, QuestionService questionService) { this.playerService = playerService; this.signUpValidator = signUpValidator; this.gameSessionService = gameSessionService; + this.roleService = roleService; } @GetMapping("/signup") @@ -122,4 +136,154 @@ public String showPlayerRanking(Pageable pageable, Model model, Principal princi return "ranking/playerRanking"; } + + // ----- Admin endpoints ----- + + @GetMapping("/player/admin") + public String showAdminPanel(Model model) { + return "player/admin/admin"; + } + + @GetMapping("/player/admin/userManagement") + public String showUserManagementFragment(Model model, Pageable pageable) { + model.addAttribute("endpoint", "/player/admin/userManagement"); + Page users = playerService.getPlayersPage(pageable); + model.addAttribute("page", users); + model.addAttribute("users", users.getContent()); + + return "player/admin/userManagement"; + } + + @GetMapping("/player/admin/deleteUser") + @ResponseBody + public String deleteUser(HttpServletResponse response, @RequestParam String username, Principal principal) { + Player player = playerService.getUserByUsername(username).orElse(null); + if (player == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return "User not found"; + } + + if (principal.getName().equals(player.getUsername())) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + return "You can't delete yourself"; + } + + playerService.deletePlayer(player.getId()); + return "User deleted"; + } + + @GetMapping("/player/admin/changePassword") + @ResponseBody + public String changePassword(HttpServletResponse response, @RequestParam String username, @RequestParam String password) { + Player player = playerService.getUserByUsername(username).orElse(null); + if (player == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return "User not found"; + } + + playerService.updatePassword(player, password); + return "User password changed"; + } + + @GetMapping("/player/admin/getRoles") + @ResponseBody + public String getRoles(@RequestParam String username) { + List roles = roleService.getAllRoles(); + Player player = playerService.getUserByUsername(username).orElse(null); + + roles.remove(roleService.getRole("ROLE_USER")); + + if (player == null) { + return "{}"; + } + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode rolesJson = mapper.createObjectNode(); + for (Role role : roles) { + boolean hasRole = player.getRoles().contains(role); + rolesJson.put(role.getName(), hasRole); + } + + return rolesJson.toString(); + } + + @GetMapping("/player/admin/changeRoles") + @ResponseBody + public String changeRoles(HttpServletResponse response, @RequestParam String username, @RequestParam String roles) { + Player player = playerService.getUserByUsername(username).orElse(null); + if (player == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return "User not found"; + } + + JsonNode rolesJson; + try { + rolesJson = new ObjectMapper().readTree(roles); + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return "Invalid roles"; + } + + rolesJson.fieldNames().forEachRemaining(roleName -> { + boolean hasRole = rolesJson.get(roleName).asBoolean(); + + Role role = roleService.getRole(roleName); + if (role == null && !hasRole) { + return; + } else if (role == null) { + role = roleService.addRole(new RoleDto(roleName)); + } + + if (hasRole) { + Associations.PlayerRole.addRole(player, role); + } else { + Associations.PlayerRole.removeRole(player, role); + } + }); + + playerService.savePlayer(player); + return "User roles changed"; + } + + @GetMapping("/player/admin/questionManagement") + public String showQuestionManagementFragment(Model model) throws IOException { + File jsonFile = new File(QuestionGeneratorService.jsonFilePath); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode json = objectMapper.readTree(jsonFile); + model.addAttribute("jsonContent", json.toString()); + + return "player/admin/questionManagement"; + } + + @GetMapping("/player/admin/deleteAllQuestions") + @ResponseBody + public String deleteAllQuestions() { + questionService.deleteAllQuestions(); + return "Questions deleted"; + } + + @GetMapping("/player/admin/saveQuestions") + @ResponseBody + public String saveQuestions(HttpServletResponse response, @RequestParam String json) throws IOException { + try { + JsonNode node = new ObjectMapper().readTree(json); + DefaultPrettyPrinter printer = new DefaultPrettyPrinter(); + DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(); + printer.indentObjectsWith(indenter); // Indent JSON objects + printer.indentArraysWith(indenter); // Indent JSON arrays + + ObjectMapper mapper = new ObjectMapper(); + mapper.writer(printer).writeValue(new FileOutputStream(QuestionGeneratorService.jsonFilePath), node); + return "Questions saved"; + } + catch (Exception e) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return "Invalid JSON"; + } + } + + @GetMapping("/player/admin/monitoring") + public String showMonitoring(Model model) { + return "player/admin/monitoring"; + } } diff --git a/src/main/java/com/uniovi/dto/PlayerDto.java b/src/main/java/com/uniovi/dto/PlayerDto.java index 027b26fa..2e3d6571 100644 --- a/src/main/java/com/uniovi/dto/PlayerDto.java +++ b/src/main/java/com/uniovi/dto/PlayerDto.java @@ -22,6 +22,9 @@ public class PlayerDto { @Schema(hidden = true) private String passwordConfirm; + //@Schema(description = "code of group of the player", example = "5565") + //private Integer multiplayerCode; + @Schema(description = "Roles of the player", example = "[\"ROLE_USER\"]") private String[] roles; } diff --git a/src/main/java/com/uniovi/entities/MultiplayerSession.java b/src/main/java/com/uniovi/entities/MultiplayerSession.java new file mode 100644 index 00000000..e9b77a5c --- /dev/null +++ b/src/main/java/com/uniovi/entities/MultiplayerSession.java @@ -0,0 +1,38 @@ +package com.uniovi.entities; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Getter // getters para todas las propiedades +@Setter // setters para todas las propiedades +@Entity +public class MultiplayerSession { + @Id + @GeneratedValue + private Long id; + @Column + private String multiplayerCode; + + @ElementCollection + @Column + private Map playerScores = new HashMap<>(); + + public MultiplayerSession() {} + + public MultiplayerSession(String code, Player p) { + this.multiplayerCode=code; + playerScores.put(p,-1); + + } + + public void addPlayer(Player p){ + playerScores.put(p,-1); + } +} diff --git a/src/main/java/com/uniovi/entities/Player.java b/src/main/java/com/uniovi/entities/Player.java index 491a8bd8..97b93fd2 100644 --- a/src/main/java/com/uniovi/entities/Player.java +++ b/src/main/java/com/uniovi/entities/Player.java @@ -33,6 +33,13 @@ public class Player implements JsonEntity { @NotEmpty private String password; + @Column + private Integer multiplayerCode; + + @Column + private String scoreMultiplayerCode; + + @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER) private Set roles = new HashSet<>(); @@ -42,6 +49,8 @@ public class Player implements JsonEntity { @OneToOne(cascade = CascadeType.ALL, mappedBy = "player") private ApiKey apiKey; + + // Transient: no se almacena en la base de datos @Transient private String passwordConfirm; diff --git a/src/main/java/com/uniovi/entities/Role.java b/src/main/java/com/uniovi/entities/Role.java index c767c425..0d4d45c0 100644 --- a/src/main/java/com/uniovi/entities/Role.java +++ b/src/main/java/com/uniovi/entities/Role.java @@ -22,4 +22,9 @@ public class Role { public Role(String name) { this.name = name; } + + @Override + public String toString() { + return name; + } } diff --git a/src/main/java/com/uniovi/repositories/MultiplayerSessionRepository.java b/src/main/java/com/uniovi/repositories/MultiplayerSessionRepository.java new file mode 100644 index 00000000..fd9e926e --- /dev/null +++ b/src/main/java/com/uniovi/repositories/MultiplayerSessionRepository.java @@ -0,0 +1,17 @@ +package com.uniovi.repositories; + +import com.uniovi.entities.GameSession; +import com.uniovi.entities.MultiplayerSession; +import com.uniovi.entities.Player; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface MultiplayerSessionRepository extends CrudRepository { + MultiplayerSession findByMultiplayerCode(String code); +} + diff --git a/src/main/java/com/uniovi/repositories/PlayerRepository.java b/src/main/java/com/uniovi/repositories/PlayerRepository.java index 77c2d9f8..254adb52 100644 --- a/src/main/java/com/uniovi/repositories/PlayerRepository.java +++ b/src/main/java/com/uniovi/repositories/PlayerRepository.java @@ -1,9 +1,16 @@ package com.uniovi.repositories; import com.uniovi.entities.Player; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.CrudRepository; public interface PlayerRepository extends CrudRepository { Player findByEmail(String email); Player findByUsername(String nickname); + @Query("SELECT player FROM Player player WHERE player.multiplayerCode=:multiplayerCode") + Iterable findAllByMultiplayerCode(int multiplayerCode); + + Page findAll(Pageable pageable); } diff --git a/src/main/java/com/uniovi/services/GameSessionService.java b/src/main/java/com/uniovi/services/GameSessionService.java index e3eb4c55..a70c194c 100644 --- a/src/main/java/com/uniovi/services/GameSessionService.java +++ b/src/main/java/com/uniovi/services/GameSessionService.java @@ -43,5 +43,8 @@ public interface GameSessionService { Page getPlayerRanking(Pageable pageable, Player player); GameSession startNewGame(Player player); + + GameSession startNewMultiplayerGame(Player player, int code); + void endGame(GameSession gameSession); } diff --git a/src/main/java/com/uniovi/services/InsertSampleDataService.java b/src/main/java/com/uniovi/services/InsertSampleDataService.java index f22b1aa8..0b2b1b83 100644 --- a/src/main/java/com/uniovi/services/InsertSampleDataService.java +++ b/src/main/java/com/uniovi/services/InsertSampleDataService.java @@ -41,12 +41,15 @@ public InsertSampleDataService(PlayerService playerService, QuestionService ques @Transactional @EventListener(ApplicationReadyEvent.class) // Uncomment this line to insert sample data on startup public void insertSampleQuestions() throws InterruptedException, IOException { - if (!playerService.getUserByEmail("test@test.com").isPresent()) { + if (playerService.getUserByEmail("test@test.com").isEmpty()) { PlayerDto player = new PlayerDto(); player.setEmail("test@test.com"); player.setUsername("test"); player.setPassword("test"); - player.setRoles(new String[]{"ROLE_USER"}); + if (Arrays.asList(environment.getActiveProfiles()).contains("test")) + player.setRoles(new String[]{"ROLE_USER", "ROLE_ADMIN"}); + else + player.setRoles(new String[]{"ROLE_USER"}); playerService.generateApiKey(playerService.addNewPlayer(player)); } } diff --git a/src/main/java/com/uniovi/services/MultiplayerSessionService.java b/src/main/java/com/uniovi/services/MultiplayerSessionService.java new file mode 100644 index 00000000..c0932339 --- /dev/null +++ b/src/main/java/com/uniovi/services/MultiplayerSessionService.java @@ -0,0 +1,22 @@ +package com.uniovi.services; + +import com.uniovi.entities.GameSession; +import com.uniovi.entities.MultiplayerSession; +import com.uniovi.entities.Player; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Service +public interface MultiplayerSessionService { + + Map getPlayersWithScores(int multiplayerCode); + void multiCreate(String code, Long id); + + void addToLobby(String code, Long id); + + void changeScore(String code,Long id,int score); +} diff --git a/src/main/java/com/uniovi/services/PlayerService.java b/src/main/java/com/uniovi/services/PlayerService.java index f59669e3..0a2db698 100644 --- a/src/main/java/com/uniovi/services/PlayerService.java +++ b/src/main/java/com/uniovi/services/PlayerService.java @@ -4,6 +4,8 @@ import com.uniovi.entities.Player; import com.uniovi.repositories.PlayerRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; @@ -26,6 +28,13 @@ public interface PlayerService { */ List getUsers(); + + /** + * Get all the players in the database with same multiplayerCode + * @return A list with the players + */ + List getUsersByMultiplayerCode(int multiplayerCode); + /** * Get a player by its id * @param id The id of the player @@ -67,9 +76,44 @@ public interface PlayerService { */ void updatePlayer(Long id, PlayerDto playerDto); + /** + * Update the multiplayerCode of a player + * @param id The id of the player to update + * @param code The new multiplayerCode of the player + */ + boolean changeMultiplayerCode(Long id, String code); + + String getScoreMultiplayerCode(Long id); + + void setScoreMultiplayerCode(Long id, String score); + + int createMultiplayerGame(Long id); + + void deleteMultiplayerCode(Long id); + /** * Delete a player from the database * @param id The id of the player to delete */ void deletePlayer(Long id); + + /** + * Get a page with all the players in the database + * @param pageable The page information + * @return A page with all the players + */ + Page getPlayersPage(Pageable pageable); + + /** + * Update the password of a player + * @param player The player to update the password + * @param password The new password + */ + void updatePassword(Player player, String password); + + /** + * Save a player in the database + * @param player The player to save + */ + void savePlayer(Player player); } diff --git a/src/main/java/com/uniovi/services/QuestionGeneratorService.java b/src/main/java/com/uniovi/services/QuestionGeneratorService.java index ec2992b2..e92a4ce0 100644 --- a/src/main/java/com/uniovi/services/QuestionGeneratorService.java +++ b/src/main/java/com/uniovi/services/QuestionGeneratorService.java @@ -8,7 +8,10 @@ import com.uniovi.entities.Answer; import com.uniovi.entities.Category; import com.uniovi.entities.Question; +import com.uniovi.services.impl.QuestionServiceImpl; import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +46,7 @@ public class QuestionGeneratorService { public QuestionGeneratorService(QuestionService questionService) { this.questionService = questionService; + ((QuestionServiceImpl)questionService).setQuestionGeneratorService(this); parseQuestionTypes(); this.started = true; } @@ -72,7 +76,7 @@ public void generateAllQuestions(){ @Scheduled(fixedRate = 150000) @Transactional - public void generateQuestions() throws IOException { + public void generateQuestions() throws IOException, InterruptedException { if (types.isEmpty()) { return; } @@ -105,7 +109,7 @@ public void generateQuestions() throws IOException { } @Transactional - public void generateTestQuestions() throws IOException { + public void generateTestQuestions() throws IOException, InterruptedException { QuestionGenerator qgen = new QuestionGeneratorV2(json); QuestionType type = types.pop(); List questions; @@ -123,24 +127,15 @@ public void generateTestQuestions(String cat) throws IOException { questionService.addNewQuestion(new QuestionDto(q)); } - private class QuestionType { - - private JsonNode question; - private Category category; - - public QuestionType(JsonNode question, Category category) { - this.question = question; - this.category = category; - } - - public JsonNode getQuestion() { - return question; - } - - public Category getCategory() { - return category; - } + public void resetGeneration() { + types.clear(); + parseQuestionTypes(); } - + @Getter + @AllArgsConstructor + private static class QuestionType { + private final JsonNode question; + private final Category category; + } } diff --git a/src/main/java/com/uniovi/services/QuestionService.java b/src/main/java/com/uniovi/services/QuestionService.java index 34801782..4b7b9695 100644 --- a/src/main/java/com/uniovi/services/QuestionService.java +++ b/src/main/java/com/uniovi/services/QuestionService.java @@ -58,6 +58,8 @@ public interface QuestionService { */ List getRandomQuestions(int num); + List getRandomMultiplayerQuestions(int num, int code); + /** * Check if the answer is correct * @param idquestion The id of the question diff --git a/src/main/java/com/uniovi/services/RoleService.java b/src/main/java/com/uniovi/services/RoleService.java index e620105f..fd196940 100644 --- a/src/main/java/com/uniovi/services/RoleService.java +++ b/src/main/java/com/uniovi/services/RoleService.java @@ -20,4 +20,10 @@ public interface RoleService { * @return The role with the given name */ Role getRole(String name); + + /** + * Get all the roles in the database + * @return A list with all the roles + */ + List getAllRoles(); } diff --git a/src/main/java/com/uniovi/services/impl/GameSessionImpl.java b/src/main/java/com/uniovi/services/impl/GameSessionImpl.java index d7e2ad3a..aca3509e 100644 --- a/src/main/java/com/uniovi/services/impl/GameSessionImpl.java +++ b/src/main/java/com/uniovi/services/impl/GameSessionImpl.java @@ -7,7 +7,6 @@ import com.uniovi.services.GameSessionService; import com.uniovi.services.QuestionService; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -51,6 +50,11 @@ public GameSession startNewGame(Player player) { return new GameSession(player, questionService.getRandomQuestions(NORMAL_GAME_QUESTION_NUM)); } + @Override + public GameSession startNewMultiplayerGame(Player player, int code) { + return new GameSession(player, questionService.getRandomMultiplayerQuestions(NORMAL_GAME_QUESTION_NUM,code)); + } + @Override public void endGame(GameSession gameSession) { Associations.PlayerGameSession.addGameSession(gameSession.getPlayer(), gameSession); diff --git a/src/main/java/com/uniovi/services/impl/MultiplayerSessionImpl.java b/src/main/java/com/uniovi/services/impl/MultiplayerSessionImpl.java new file mode 100644 index 00000000..0c10044e --- /dev/null +++ b/src/main/java/com/uniovi/services/impl/MultiplayerSessionImpl.java @@ -0,0 +1,69 @@ +package com.uniovi.services.impl; + +import com.uniovi.entities.MultiplayerSession; +import com.uniovi.entities.Player; +import com.uniovi.repositories.MultiplayerSessionRepository; +import com.uniovi.repositories.PlayerRepository; +import com.uniovi.services.MultiplayerSessionService; +import jakarta.transaction.Transactional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class MultiplayerSessionImpl implements MultiplayerSessionService { + private final PlayerRepository playerRepository; + private final MultiplayerSessionRepository multiplayerSessionRepository; + + + public MultiplayerSessionImpl(PlayerRepository playerRepository, MultiplayerSessionRepository multiplayerSessionRepository) { + this.playerRepository = playerRepository; + this.multiplayerSessionRepository = multiplayerSessionRepository; + } + + @Override + @Transactional + public Map getPlayersWithScores(int multiplayerCode) { + MultiplayerSession session = multiplayerSessionRepository.findByMultiplayerCode(String.valueOf(multiplayerCode)); + Map playerScores = session.getPlayerScores(); + + // Ordenar los jugadores por puntuación de mayor a menor + List sortedPlayers = playerScores.entrySet().stream() + .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + Map playersSorted = new HashMap<>(); + for (Player player : sortedPlayers) { + playersSorted.put(player,playerScores.get(player)); + } + return playersSorted; + } + + @Override + public void multiCreate(String code, Long id) { + Player p = playerRepository.findById(id).get(); + multiplayerSessionRepository.save(new MultiplayerSession(code,p)); + } + + @Override + @Transactional + public void addToLobby(String code, Long id) { + Player p = playerRepository.findById(id).get(); + MultiplayerSession ms=multiplayerSessionRepository.findByMultiplayerCode(code); + ms.addPlayer(p); + multiplayerSessionRepository.save(ms); + } + + @Override + @Transactional + public void changeScore(String code, Long id, int score) { + Player p = playerRepository.findById(id).get(); + MultiplayerSession ms=multiplayerSessionRepository.findByMultiplayerCode(code); + ms.getPlayerScores().put(p,score); + multiplayerSessionRepository.save(ms); + } +} diff --git a/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java b/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java index f4fa462d..549e40b6 100644 --- a/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java +++ b/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java @@ -6,16 +6,17 @@ import com.uniovi.entities.Associations; import com.uniovi.entities.Player; import com.uniovi.repositories.PlayerRepository; -import com.uniovi.repositories.RoleRepository; +import com.uniovi.services.MultiplayerSessionService; import com.uniovi.services.PlayerService; import com.uniovi.services.RoleService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import com.uniovi.entities.Role; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -25,10 +26,13 @@ public class PlayerServiceImpl implements PlayerService { private RoleService roleService; private PasswordEncoder passwordEncoder; - public PlayerServiceImpl(PlayerRepository playerRepository, RoleService roleService, PasswordEncoder passwordEncoder) { + private MultiplayerSessionService multiplayerSessionService; + + public PlayerServiceImpl(PlayerRepository playerRepository, RoleService roleService, MultiplayerSessionService multiplayerSessionService,PasswordEncoder passwordEncoder) { this.playerRepository = playerRepository; this.roleService = roleService; this.passwordEncoder = passwordEncoder; + this.multiplayerSessionService = multiplayerSessionService; } @Override @@ -71,6 +75,13 @@ public List getUsers() { return l; } + @Override + public List getUsersByMultiplayerCode(int multiplayerCode) { + List l = new ArrayList<>(); + playerRepository.findAllByMultiplayerCode(multiplayerCode).forEach(l::add); + return l; + } + @Override public Optional getUser(Long id) { return playerRepository.findById(id); @@ -132,8 +143,90 @@ public void updatePlayer(Long id, PlayerDto playerDto) { playerRepository.save(p); } + @Override + public boolean changeMultiplayerCode(Long id, String code) { + Optional player = playerRepository.findById(id); + if (player.isEmpty()) + return false; + + Player p = player.get(); + if(existsMultiplayerCode(code)){ + p.setMultiplayerCode(Integer.parseInt(code)); + playerRepository.save(p); + return true; + } + return false; + } + @Override + public String getScoreMultiplayerCode(Long id) { + Optional player = playerRepository.findById(id); + if (player.isEmpty()) + return ""; + + return player.get().getScoreMultiplayerCode(); + } + + @Override + public void setScoreMultiplayerCode(Long id, String score) { + Optional player = playerRepository.findById(id); + if (player.isEmpty()) + return; + + Player p =player.get(); + p.setScoreMultiplayerCode(score); + playerRepository.save(p); + } + /** + * A multiplayerCodeExists if there are any player + * with same multiplayerCode at the moment of the join + * */ + private boolean existsMultiplayerCode(String code){ + //return ! getUsersByMultiplayerCode(Integer.parseInt(code)).isEmpty(); + return ! multiplayerSessionService.getPlayersWithScores(Integer.parseInt(code)).isEmpty(); + } + + @Override + public int createMultiplayerGame(Long id){ + Optional player = playerRepository.findById(id); + if (player.isEmpty()) + return -1; + + Player p = player.get(); + int code = (int)Math.floor(Math.random()*10000); + p.setMultiplayerCode(code); + playerRepository.save(p); + return code; + } + + @Override + public void deleteMultiplayerCode(Long id){ + Optional player = playerRepository.findById(id); + if (player.isEmpty()) + return; + + Player p = player.get(); + p.setMultiplayerCode(null); + playerRepository.save(p); + } + @Override public void deletePlayer(Long id) { playerRepository.deleteById(id); } + + @Override + public Page getPlayersPage(Pageable pageable) { + return playerRepository.findAll(pageable); + } + + @Override + public void updatePassword(Player player, String password) { + player.setPassword(passwordEncoder.encode(password)); + playerRepository.save(player); + } + + @Override + public void savePlayer(Player player) { + playerRepository.save(player); + } } diff --git a/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java b/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java index 5a80136b..a86b66c6 100644 --- a/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java +++ b/src/main/java/com/uniovi/services/impl/QuestionServiceImpl.java @@ -9,15 +9,20 @@ import com.uniovi.repositories.QuestionRepository; import com.uniovi.services.AnswerService; import com.uniovi.services.CategoryService; +import com.uniovi.services.QuestionGeneratorService; import com.uniovi.services.QuestionService; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.security.SecureRandom; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -31,6 +36,9 @@ public class QuestionServiceImpl implements QuestionService { private final AnswerRepository answerRepository; private final EntityManager entityManager; + @Setter + private QuestionGeneratorService questionGeneratorService; + private final Random random = new SecureRandom(); public QuestionServiceImpl(QuestionRepository questionRepository, CategoryService categoryService, @@ -106,6 +114,39 @@ public List getRandomQuestions(int num) { return res; } + + @Override + public List getRandomMultiplayerQuestions(int num, int code) { + List allQuestions = questionRepository.findAll().stream() + .filter(question -> question.getLanguage().equals(LocaleContextHolder.getLocale().getLanguage())).toList(); + List res = new ArrayList<>(); + int size= allQuestions.size(); + + int currentIndex = generateIndex(code, size) -4; + + for (int i = 0; i < num; i++) { + + Question question = allQuestions.get(currentIndex); + + while (question.hasEmptyOptions() || res.contains(question)) { + question = allQuestions.get(currentIndex); + } + + res.add(question); + currentIndex++; + } + return res; + } + private int generateIndex(int code, int size) { + int hashCode = combineHash(code, LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + return Math.abs(hashCode) % size; + } + + private int combineHash(int code, String date) { + String combinedString = code + date; + return combinedString.hashCode(); + } + @Override public boolean checkAnswer(Long idquestion, Long idanswer) { Optional q = questionRepository.findById(idquestion); @@ -168,6 +209,8 @@ public void deleteQuestion(Long id) { @Override public void deleteAllQuestions() { + questionGeneratorService.resetGeneration(); questionRepository.deleteAll(); } + } diff --git a/src/main/java/com/uniovi/services/impl/RoleServiceImpl.java b/src/main/java/com/uniovi/services/impl/RoleServiceImpl.java index b10bbcc1..bec8464c 100644 --- a/src/main/java/com/uniovi/services/impl/RoleServiceImpl.java +++ b/src/main/java/com/uniovi/services/impl/RoleServiceImpl.java @@ -35,4 +35,11 @@ public Role addRole(RoleDto role) { public Role getRole(String name) { return roleRepository.findById(name).orElse(null); } + + @Override + public List getAllRoles() { + List roles = new ArrayList<>(); + roleRepository.findAll().forEach(roles::add); + return roles; + } } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 2b91abb1..ea14131d 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -13,6 +13,7 @@ navbar.toEnglish=Inglés navbar.toSpanish=Español navbar.toFrench=Francés navbar.currentLanguage=Español +navbar.section.admin=Panel de administración # Buttons for non-authenticated users navbar.signup=Regístrate @@ -22,6 +23,7 @@ navbar.login=Inicia sesión navbar.profile=Perfil navbar.logout=Cerrar sesión navbar.profile.apikey=Clave de la API +navbar.admin.zone=Zona de administración # -------------------Statements for the footer.html file--------------------- footer.copyright=© ASW - Grupo 04 B @@ -37,6 +39,7 @@ error.error=Error: index.heading=WIQ index.subtitle=Responde las preguntas correctamente y GANA!!! index.button=JUGAR +index.multiplayer.button=JUGAR CON AMIGOS # -------------------Statements for the home.html file--------------------- home.heading=Bienvenido @@ -82,6 +85,29 @@ ranking.question.right=Respuestas correctas ranking.question.wrong=Respuestas incorrectas ranking.time=Tiempo +# -------------------Statements for the multiplayerGame.html file--------------------- +multi.text = ¿Aún no tienes un código? Crea uno y compártelo con tus amigos +multi.create = Crear +multi.placeholder= Introduce el código correcto +multi.label = Únete a una partida +multi.join = Unirse +multi.onlyNumber = Solo se permiten números +multi.copyCode= Copiar código +multi.info=Resultados para la partida: + +# -------------------Statements for the lobby.html file--------------------- +lobby.info =Jugadores unidos a la partida: +lobby.friends =Comparte el código de tu partida con tus amigos +lobby.start = Empezar + +# -------------------Statements for the lobby.html file--------------------- +multi.code= Código de la partida +multi.results =Ver resultados + +# -------------------Statements for the multiFinished.html file--------------------- +multi.finished= Partida finalizada +multi.points = Puntuaciones +multi.menu = Ir a la página de inicio # -------------------Statements for the apiHome.html file--------------------- api.doc.title=Documentación de la API api.doc.description=Esta es la documentación de la API de WIQ. Aquí puedes encontrar información sobre los recursos disponibles, los parámetros que aceptan y los ejemplos de uso. @@ -114,4 +140,29 @@ game.continue=Siguiente pregunta answer.correct=La respuesta correcta era: game.points=Puntos: game.currentQuestion=Pregunta: -game.finish=El juego ha terminado. Tu puntuación ha sido: \ No newline at end of file +game.finish=El juego ha terminado. Tu puntuación ha sido: + + +# -------------------Statements for the admin section--------------------- +admin.section.user.management=Administración de usuarios +admin.section.question.management=Administración de preguntas +role.label=Roles +user.details=Acciones +admin.user.delete=Eliminar usuario +admin.user.delete.title=Confirmar borrado de usuario +admin.user.delete.message=¿Está seguro de que desea eliminar este usuario?\nTodos los datos asociados con esta cuenta se eliminarán.\nLa acción es irreversible. +admin.changepassword=Cambiar contraseña +admin.changeroles=Modificar roles +modal.password.title=Confirmar cambio de contraseña para +admin.password.change.input=Nueva contraseña +admin.roles.change=Confirmar cambio de roles para +modal.new.role=Nuevo rol +modal.close=Cerrar +modal.confirm=Confirmar +admin.questions.delete.title=Borrar todas las preguntas +admin.questions.delete=¿Está seguro de que desea eliminar todas las preguntas?\nEsta acción es irreversible.\nSe generaran de nuevo según pase el tiempo. +admin.monitoring=Monitorización de la aplicación + +# -------------------Statements for the page management--------------------- +page.first=Primera +page.last=Última \ No newline at end of file diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index 97f0f133..479c891f 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -13,6 +13,7 @@ navbar.toEnglish=English navbar.toSpanish=Spanish navbar.toFrench=French navbar.currentLanguage=English +navbar.section.admin=Administration Section # Buttons for non-authenticated users navbar.signup=Sign Up @@ -22,6 +23,7 @@ navbar.login=Log In navbar.profile=Profile navbar.logout=Log Out navbar.profile.apikey=API Key +navbar.admin.zone=Admin Zone # -------------------Statements for the footer.html file--------------------- footer.copyright=© ASW - Group 04 B @@ -37,6 +39,8 @@ error.error=Error: index.heading=WIQ index.subtitle=Answer the questions correctly and WIN!!! index.button=PLAY +index.multiplayer.button=PLAY WITH FRIENDS + # -------------------Statements for the home.html file--------------------- home.heading=Welcome @@ -83,6 +87,31 @@ ranking.question.right=Right answers ranking.question.wrong=Wrong answers ranking.time=Time +# -------------------Statements for the multiplayerGame.html file--------------------- +multi.text =Don't have a code yet? Create one and share it with your friends +multi.create = Create +multi.placeholder=Enter the correct code +multi.label=Join a game +multi.join = Join +multi.onlyNumber=Only numbers allowed +multi.copyCode= Copy code +multi.info=reults for the game: + +# -------------------Statements for the lobby.html file--------------------- +lobby.info =Players joining the game: +lobby.friends =Share your game code with your friends +lobby.start =Start + +# -------------------Statements for the lobby.html file--------------------- +multi.code= Game code +multi.results = See results + +# -------------------Statements for the multiFinished.html file--------------------- +multi.finished= Finished game +multi.points = Points +multi.menu =Go to home page + + # -------------------Statements for the apiHome.html file--------------------- api.doc.title=API Documentation api.doc.description=This document describes the REST API endpoints. @@ -117,4 +146,28 @@ game.points=Points: game.currentQuestion=Question: game.finish=The game has finished. Your score is: +# -------------------Statements for the admin section--------------------- +admin.section.user.management=Users management +admin.section.question.management=Questions management +role.label=Roles +user.details=Details +admin.user.delete=Delete user +admin.user.delete.title=Confirm deleting user +admin.user.delete.message=Are you sure you want to delete this user?\nAll data associated with this account will be erased\nThis action cannot be undone +admin.changepassword=Change password +admin.changeroles=Modify roles +modal.password.title=Confirm password change for +admin.password.change.input=New password +admin.roles.change=Confirm role change for +modal.new.role=New role +modal.close=Close +modal.confirm=Save changes +admin.questions.delete.title=Delete all questions +admin.questions.delete=Are you sure you want to delete all questions?\nThis action cannot be undone.\nQuestions will generate again as time passes. +admin.monitoring=Monitoring + +# -------------------Statements for the page management--------------------- +page.first=First +page.last=Last + diff --git a/src/main/resources/messages_es.properties b/src/main/resources/messages_es.properties index 174dad48..3a860636 100644 --- a/src/main/resources/messages_es.properties +++ b/src/main/resources/messages_es.properties @@ -13,6 +13,7 @@ navbar.toEnglish=Inglés navbar.toSpanish=Español navbar.toFrench=Francés navbar.currentLanguage=Español +navbar.section.admin=Panel de administración # Buttons for non-authenticated users navbar.signup=Regístrate @@ -22,7 +23,7 @@ navbar.profile.apikey=Clave de la API # Buttons for authenticated users navbar.profile=Perfil navbar.logout=Cerrar sesión - +navbar.admin.zone=Zona de administración # -------------------Statements for the footer.html file--------------------- footer.copyright=© ASW - Grupo 04 B @@ -38,6 +39,8 @@ error.error=Error: index.heading=WIQ index.subtitle=Responde las preguntas correctamente y GANA!!! index.button=JUGAR +index.multiplayer.button=JUGAR CON AMIGOS + # -------------------Statements for the home.html file--------------------- home.heading=Bienvenido @@ -84,6 +87,31 @@ ranking.question.right=Respuestas correctas ranking.question.wrong=Respuestas incorrectas ranking.time=Tiempo +# -------------------Statements for the multiplayerGame.html file--------------------- +multi.text = ¿Aún no tienes un código? Crea uno y compártelo con tus amigos +multi.create = Crear +multi.placeholder= Introduce el código correcto +multi.label = Únete a una partida +multi.join = Unirse +multi.onlyNumber = Solo se permiten números +multi.copyCode= Copiar código +multi.info=Resultados para la partida: + +# -------------------Statements for the lobby.html file--------------------- +lobby.info =Jugadores unidos a la partida: +lobby.friends =Comparte el código de tu partida con tus amigos +lobby.start =Empezar + +# -------------------Statements for the lobby.html file--------------------- +multi.code= Código de la partida +multi.results =Ver resultados + +# -------------------Statements for the multiFinished.html file--------------------- +multi.finished= Partida finalizada +multi.points = Puntuaciones +multi.menu = Ir a la página de inicio + + # -------------------Statements for the apiHome.html file--------------------- api.doc.title=Documentación de la API api.doc.description=Esta es la documentación de la API de WIQ. Aquí puedes encontrar información sobre los recursos disponibles, los parámetros que aceptan y los ejemplos de uso. @@ -116,4 +144,25 @@ game.continue=Siguiente pregunta answer.correct=La respuesta correcta era: game.points=Puntos: game.currentQuestion=Pregunta: -game.finish=El juego ha terminado. Tu puntuación ha sido: \ No newline at end of file +game.finish=El juego ha terminado. Tu puntuación ha sido: + +# -------------------Statements for the admin section--------------------- +admin.section.user.management=Administración de usuarios +admin.section.question.management=Administración de preguntas +role.label=Roles +user.details=Acciones +admin.user.delete=Eliminar usuario +admin.user.delete.title=Confirmar borrado de usuario +admin.user.delete.message=¿Está seguro de que desea eliminar este usuario?\nTodos los datos asociados con esta cuenta se eliminarán.\nLa acción es irreversible. +admin.changepassword=Cambiar contraseña +admin.changeroles=Modificar roles +modal.password.title=Confirmar cambio de contraseña para +admin.password.change.input=Nueva contraseña +admin.roles.change=Confirmar cambio de roles para +modal.new.role=Nuevo rol +modal.close=Cerrar +modal.confirm=Confirmar + +# -------------------Statements for the page management--------------------- +page.first=Primera +page.last=Última \ No newline at end of file diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index 1f5d2b22..96a377f1 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -12,6 +12,7 @@ navbar.toEnglish=Anglais navbar.toSpanish=Espagnol navbar.toFrench=Français navbar.currentLanguage=Français +navbar.section.admin=Espace administrateur navbar.signup=S'inscrire navbar.login=Se connecter @@ -20,7 +21,7 @@ navbar.profile.apikey=Clé d'API # Buttons for authenticated users navbar.profile=Profil navbar.logout=Se déconnecter - +navbar.admin.zone=Espace administrateur # -------------------Statements for the footer.html file--------------------- footer.copyright=© ASW - Groupe 04 B @@ -36,6 +37,7 @@ error.error=Erreur : index.heading=WIQ index.subtitle=Répondez aux questions correctement et GAGNEZ !!! index.button=JOUER +index.multiplayer.button=JOUEZ AVEC DES AMIS # -------------------Statements for the home.html file--------------------- home.heading=Bienvenue @@ -78,6 +80,31 @@ ranking.question.right=Réponses correctes ranking.question.wrong=Réponses incorrectes ranking.time=Temps +# -------------------Statements for the multiplayerGame.html file--------------------- +multi.text = Vous n'avez pas encore de code ? Créez-en un et partagez-le avec vos amis. +multi.create = Créer +multi.placeholder=Entrez le bon code +multi.label=Rejoignez une partie +multi.join =Rejoindre +multi.onlyNumber=Seuls les chiffres sont autorisés +multi.copyCode= Copier le code +multi.info=résultats du jeu: + +# -------------------Statements for the lobby.html file--------------------- +lobby.info =Joueurs rejoignant le jeu : +lobby.friends =Partagez votre code de jeu avec vos amis +lobby.start = Commencer + +# -------------------Statements for the lobby.html file--------------------- +multi.code=Code du jeu +multi.results=Voir les résultats + +# -------------------Statements for the multiFinished.html file--------------------- +multi.finished= Jeu terminé +multi.points = Points +multi.menu =aller à la page d'accueil + + # -------------------Statements for the apiHome.html file--------------------- api.doc.title=Documentation de l'API api.doc.description=Ceci est la documentation de l'API de WIQ. Vous pouvez trouver ici des informations sur les ressources disponibles, les paramètres qu'elles acceptent et des exemples d'utilisation. @@ -112,3 +139,27 @@ game.points=Points: game.currentQuestion=Question: game.finish=Le jeu est terminé. Votre score est : +# -------------------Déclarations pour la section administrateur--------------------- +admin.section.user.management=Gestion des utilisateurs +admin.section.question.management=Gestion des questions +role.label=Rôles +user.details=Actions +admin.user.delete=Supprimer l'utilisateur +admin.user.delete.title=Confirmer la suppression de l'utilisateur +admin.user.delete.message=Êtes-vous sûr de vouloir supprimer cet utilisateur ?\nToutes les données associées à ce compte seront supprimées.\nL'action est irréversible. +admin.changepassword=Changer le mot de passe +admin.changeroles=Modifier les rôles +modal.password.title=Confirmer le changement de mot de passe pour +admin.password.change.input=Nouveau mot de passe +admin.roles.change=Confirmer le changement de rôles pour +modal.new.role=Nouveau rôle +modal.close=Fermer +modal.confirm=Confirmer +admin.questions.delete.title=Confirmer la suppression de toutes les questions +admin.questions.delete=Vous êtes sur le point de supprimer toutes les questions. Êtes-vous sûr de vouloir continuer ? +admin.monitoring=Surveillance + +# -------------------Déclarations pour la gestion de la page--------------------- +page.first=Première +page.last=Dernière + diff --git a/src/main/resources/static/JSON/QuestionTemplates.json b/src/main/resources/static/JSON/QuestionTemplates.json index 825176a9..a910f19b 100644 --- a/src/main/resources/static/JSON/QuestionTemplates.json +++ b/src/main/resources/static/JSON/QuestionTemplates.json @@ -1,97 +1,97 @@ { - "language_placeholder": "[LANGUAGE]", - "question_placeholder": "[QUESTION]", - "answer_placeholder": "[ANSWER]", - "categories": [ + "language_placeholder" : "[LANGUAGE]", + "question_placeholder" : "[QUESTION]", + "answer_placeholder" : "[ANSWER]", + "categories" : [ { - "name": "Geography", - "questions": [ + "name" : "Geography", + "questions" : [ { - "type": "capital", - "statements": [ + "type" : "capital", + "statements" : [ { - "language": "es", - "statement": "¿Cuál es la capital de [QUESTION]?" + "language" : "es", + "statement" : "¿Cuál es la capital de [QUESTION]?" }, { - "language": "en", - "statement": "What is the capital of [QUESTION]?" + "language" : "en", + "statement" : "What is the capital of [QUESTION]?" }, { - "language": "fr", - "statement": "Quelle est la capitale de [QUESTION]?" + "language" : "fr", + "statement" : "Quelle est la capitale de [QUESTION]?" } ], - "question": "countryLabel", - "answer": "capitalLabel", - "sparqlQuery": "select distinct ?country ?[QUESTION] ?capital ?[ANSWER] where {\n ?country wdt:P31 wd:Q6256 .\n ?capital wdt:P31 wd:Q5119 .\n ?country wdt:P36 ?capital .\n ?country rdfs:label ?[QUESTION] .\n ?capital rdfs:label ?[ANSWER] .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\" && LANG(?[ANSWER])=\"[LANGUAGE]\")\n }" + "question" : "countryLabel", + "answer" : "capitalLabel", + "sparqlQuery" : "select distinct ?country ?[QUESTION] ?capital ?[ANSWER] where {\n ?country wdt:P31 wd:Q6256 .\n ?capital wdt:P31 wd:Q5119 .\n ?country wdt:P36 ?capital .\n ?country rdfs:label ?[QUESTION] .\n ?capital rdfs:label ?[ANSWER] .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\" && LANG(?[ANSWER])=\"[LANGUAGE]\")\n }" }, { - "type": "currency", - "statements": [ - { - "language": "es", - "statement": "¿Cuál es la moneda de [QUESTION]?" - }, - { - "language": "en", - "statement": "What is the currency of [QUESTION]?" - }, - { - "language": "fr", - "statement": "Quelle est la monnaie de [QUESTION]?" - } - ], - "question": "countryLabel", - "answer": "currencyLabel", - "sparqlQuery": "select distinct ?country ?[QUESTION] ?currency ?[ANSWER] where {\n ?country wdt:P31 wd:Q6256 .\n ?currency wdt:P31 wd:Q8142 .\n ?country wdt:P38 ?currency .\n ?country rdfs:label ?[QUESTION] .\n ?currency rdfs:label ?[ANSWER] .\n FILTER NOT EXISTS {?country wdt:P31 wd:Q3024240} .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\" && LANG(?[ANSWER])=\"[LANGUAGE]\")\n }" + "type" : "currency", + "statements" : [ + { + "language" : "es", + "statement" : "¿Cuál es la moneda de [QUESTION]?" + }, + { + "language" : "en", + "statement" : "What is the currency of [QUESTION]?" + }, + { + "language" : "fr", + "statement" : "Quelle est la monnaie de [QUESTION]?" + } + ], + "question" : "countryLabel", + "answer" : "currencyLabel", + "sparqlQuery" : "select distinct ?country ?[QUESTION] ?currency ?[ANSWER] where {\n ?country wdt:P31 wd:Q6256 .\n ?currency wdt:P31 wd:Q8142 .\n ?country wdt:P38 ?currency .\n ?country rdfs:label ?[QUESTION] .\n ?currency rdfs:label ?[ANSWER] .\n FILTER NOT EXISTS {?country wdt:P31 wd:Q3024240} .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\" && LANG(?[ANSWER])=\"[LANGUAGE]\")\n }" } ] }, { - "name": "Science", - "questions": [ + "name" : "Science", + "questions" : [ { - "type": "element", - "statements": [ + "type" : "element", + "statements" : [ { - "language": "es", - "statement": "¿Cuál es el símbolo químico del [QUESTION]?" + "language" : "es", + "statement" : "¿Cuál es el símbolo químico del [QUESTION]?" }, { - "language": "en", - "statement": "What is the chemical symbol of [QUESTION]?" + "language" : "en", + "statement" : "What is the chemical symbol of [QUESTION]?" }, { - "language": "fr", - "statement": "Quel est le symbole chimique du [QUESTION]?" + "language" : "fr", + "statement" : "Quel est le symbole chimique du [QUESTION]?" } ], - "question": "elementLabel", - "answer": "symbol", - "sparqlQuery": "select distinct ?element ?[QUESTION] ?[ANSWER] where {\n ?element wdt:P31 wd:Q11344 .\n ?element wdt:P246 ?[ANSWER] .\n ?element rdfs:label ?[QUESTION] .\n FILTER NOT EXISTS {?element wdt:P31 wd:Q1299291} .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\")\n }" + "question" : "elementLabel", + "answer" : "symbol", + "sparqlQuery" : "select distinct ?element ?[QUESTION] ?[ANSWER] where {\n ?element wdt:P31 wd:Q11344 .\n ?element wdt:P246 ?[ANSWER] .\n ?element rdfs:label ?[QUESTION] .\n FILTER NOT EXISTS {?element wdt:P31 wd:Q1299291} .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\")\n }" }, { - "type": "atomic_number", - "statements": [ + "type" : "atomic_number", + "statements" : [ { - "language": "es", - "statement": "¿Cuál es el número atómico del [QUESTION]?" + "language" : "es", + "statement" : "¿Cuál es el número atómico del [QUESTION]?" }, { - "language": "en", - "statement": "What is the atomic number of [QUESTION]?" + "language" : "en", + "statement" : "What is the atomic number of [QUESTION]?" }, { - "language": "fr", - "statement": "Quel est le numéro atomique du [QUESTION]?" + "language" : "fr", + "statement" : "Quel est le numéro atomique du [QUESTION]?" } ], - "question": "elementLabel", - "answer": "atomicNumber", - "sparqlQuery": "select distinct ?element ?[QUESTION] ?[ANSWER] where {\n ?element wdt:P31 wd:Q11344 .\n ?element wdt:P1086 ?[ANSWER] .\n ?element rdfs:label ?[QUESTION] .\n FILTER NOT EXISTS {?element wdt:P31 wd:Q1299291} .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\")\n }" + "question" : "elementLabel", + "answer" : "atomicNumber", + "sparqlQuery" : "select distinct ?element ?[QUESTION] ?[ANSWER] where {\n ?element wdt:P31 wd:Q11344 .\n ?element wdt:P1086 ?[ANSWER] .\n ?element rdfs:label ?[QUESTION] .\n FILTER NOT EXISTS {?element wdt:P31 wd:Q1299291} .\n FILTER(LANG(?[QUESTION])=\"[LANGUAGE]\")\n }" } ] } ] -} +} \ No newline at end of file diff --git a/src/main/resources/static/css/admin.css b/src/main/resources/static/css/admin.css new file mode 100644 index 00000000..db24eb8e --- /dev/null +++ b/src/main/resources/static/css/admin.css @@ -0,0 +1,26 @@ +.nav .nav-link { + color: white; + border-color: white; +} + +.nav .nav-item { + margin: 0 5px; + flex: 1; +} + +.nav .nav-link.active { + color: black !important; +} + +.nav-tabs { + border-bottom: 0px; +} + +.separator { + border-bottom: 1px solid white; + margin: 10px 0; +} + +.text-danger-light { + color: #ff5e5e; +} \ No newline at end of file diff --git a/src/main/resources/static/css/custom.css b/src/main/resources/static/css/custom.css index 1499a864..18ebb97e 100644 --- a/src/main/resources/static/css/custom.css +++ b/src/main/resources/static/css/custom.css @@ -8,14 +8,6 @@ body { margin-bottom: 60px; color: #fff; } -footer { - position: absolute; - bottom: 0; - width: 100%; - height: 60px; - text-align:center; - line-height:60px -} .bg-primary { background-color: rgb(1, 85, 20) !important; @@ -66,4 +58,64 @@ footer { .prueba { font-weight: bold; +} + +.button-container { + display: flex; + justify-content: space-between; +} + +.button-container a { + flex: 1; + margin: 0 5px; +} + +.modal { + color: black; +} + +.modal .btn.btn-primary { + background-color: #007bff; + border-color: #007bff; +} + +.btn-close { + box-sizing: content-box; + width: 1em; + height: 1em; + padding: .25em .25em; + color: #000; + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; + border: 0; + border-radius: .25rem; + opacity: .5 +} + +.btn-close:hover { + color: #000; + text-decoration: none; + opacity: .75 +} + +.btn-close:focus { + outline: 0; + box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25); + opacity: 1 +} + +.btn-close.disabled, +.btn-close:disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + opacity: .25 +} + +.modal-body { + white-space: pre; +} + +.container, .container-fluid { + flex: 1 1 auto !important; } \ No newline at end of file diff --git a/src/main/resources/static/css/footer.css b/src/main/resources/static/css/footer.css index 77a89c6c..e9e67a72 100644 --- a/src/main/resources/static/css/footer.css +++ b/src/main/resources/static/css/footer.css @@ -1,14 +1,9 @@ /* Estilo del footer */ .footer { - position: absolute; bottom: 0; - min-height: 10%; width: 100%; - padding: 0; /* Eliminar relleno */ - text-align: center; /* Alineación del texto */ - display: flex; /* Usar flexbox para centrar verticalmente */ - /*align-items: center; /* Centrar verticalmente */ - margin-top: 5%; + height: 60px; /* Set the fixed height of the footer here */ + line-height: 60px; /* Vertically center the text there */ background-color: transparent !important; /* Hace que el footer sea transparente */ } diff --git a/src/main/resources/static/css/multiplayer.css b/src/main/resources/static/css/multiplayer.css new file mode 100644 index 00000000..6715e189 --- /dev/null +++ b/src/main/resources/static/css/multiplayer.css @@ -0,0 +1,26 @@ +.display-5 { + margin-top: 3em; +} + +#lobbyCode, #label-code { + font-size: 3em; +} + +#lobbyInfo { + margin-top: 3em; +} + +#playerList { + list-style-type: none; + text-align: center; + padding: 0; +} + +#finishedGame { + font-size: 5em; + margin-bottom: 0.5em; +} + +#multiPoints { + margin-top: 3em; +} diff --git a/src/main/resources/static/css/nav.css b/src/main/resources/static/css/nav.css index 79c8ca3c..102f4e28 100644 --- a/src/main/resources/static/css/nav.css +++ b/src/main/resources/static/css/nav.css @@ -18,7 +18,7 @@ /* Estilo para los desplegables */ .dropdown-menu { color: #fff; - background-color: black; + background-color: rgb(19, 19, 19); border: 2px solid #fff; } diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index 84d2796c..a28db0d5 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -6,4 +6,12 @@ #apiKeyDiv form { margin: 5% 20% 0 20%; +} + +html { + position: relative; + min-height: 100%; +} +body { + margin-bottom: 60px; /* Margin bottom by footer height */ } \ No newline at end of file diff --git a/src/main/resources/static/script/adminModals.js b/src/main/resources/static/script/adminModals.js new file mode 100644 index 00000000..aa468c2e --- /dev/null +++ b/src/main/resources/static/script/adminModals.js @@ -0,0 +1,112 @@ +function setupUserEvents() { + $("#deleteUserAdminModal").on('show.bs.modal', function (event) { + let button = $(event.relatedTarget); + let username = button.attr('data-bs-username'); + $(".modal-title b").text('"' + username + '"'); + $("#deleteModalConfirm").attr('data-bs-username', username); + }); + + $("#deleteModalConfirm").click(function () { + let username = $(this).attr('data-bs-username'); + $.ajax({ + url: "/player/admin/deleteUser", + type: "GET", + data: { + username: username + }, + success: function (data) { + $('#tab-content').load('/player/admin/userManagement'); + $("#deleteUserAdminModal").modal('hide'); + } + }); + }); + + $("#changePasswordAdminModal").on('show.bs.modal', function (event) { + let button = $(event.relatedTarget); + let username = button.attr('data-bs-username'); + $(".modal-title b").text('"' + username + '"'); + $("#changePasswordConfirm").attr('data-bs-username', username); + }); + + $("#changePasswordConfirm").click(function () { + let username = $(this).attr('data-bs-username'); + let newPass = $("#changePasswordInput").val(); + $.ajax({ + url: "/player/admin/changePassword", + type: "GET", + data: { + username: username, + password: newPass + }, + success: function (data) { + $('#tab-content').load('/player/admin/userManagement'); + $("#changePasswordAdminModal").modal('hide'); + } + }); + }); + + $("#changeRolesAdminModal").on('show.bs.modal', function (event) { + let button = $(event.relatedTarget); + let username = button.attr('data-bs-username'); + $(".modal-title b").text('"' + username + '"'); + $("#changeRolesConfirm").attr('data-bs-username', username); + $.ajax({ + url: "/player/admin/getRoles", + type: "GET", + data: { + username: username + }, + success: function (data) { + let roles = JSON.parse(data); + let rolesContainer = $("#rolesContainer"); + rolesContainer.empty(); + let i = 0; + for (const role in roles) { + let hasRole = roles[role]; + let div = $('
'); + let input = $(''); + let label = $(''); + div.append(input); + div.append(label); + rolesContainer.append(div); + i = i + 1; + } + }, + error: function (data) { + alert("Error: " + data); + } + }); + }); + + $("#changeRolesConfirm").click(function () { + let username = $(this).attr('data-bs-username'); + + let allRoles = $("#rolesContainer input"); + let roles = {}; + allRoles.each(function() { + roles[$(this).val()] = $(this).is(':checked'); + }); + let newRoleInput = $("#newRole").val(); + if (newRoleInput !== "") { + roles[newRoleInput] = true; + } + + let rolesString = JSON.stringify(roles); + + $.ajax({ + url: "/player/admin/changeRoles", + type: "GET", + data: { + username: username, + roles: rolesString + }, + success: function (data) { + $('#tab-content').load('/player/admin/userManagement'); + $("#changeRolesAdminModal").modal('hide'); + }, + error: function (data) { + alert("Error: " + data); + } + }); + }); +} \ No newline at end of file diff --git a/src/main/resources/static/script/questionManagement.js b/src/main/resources/static/script/questionManagement.js new file mode 100644 index 00000000..1d3280dd --- /dev/null +++ b/src/main/resources/static/script/questionManagement.js @@ -0,0 +1,38 @@ +function setupQuestionManagement() { + var editor; + $("#deleteQuestionsConfirm").on("click", function () { + $.ajax({ + url: "/player/admin/deleteAllQuestions", + type: "GET", + success: function () { + $('#tab-content').load('/player/admin/questionManagement'); + } + }); + }); + + $("#saveButton").on("click", function () { + $.ajax({ + url: "/player/admin/saveQuestions", + type: "GET", + data: { + json: JSON.stringify(editor.get()) + }, + contentType: "application/json" + }); + }); + + $.ajax({ + url: '/JSON/QuestionTemplates.json', + type: 'GET', + success: function (data) { + let json = data; + const element = document.getElementById('jsonEditorElement'); + const options = {} + editor = new JSONEditor(element, options) + editor.set(json) + }, + error: function (error) { + console.log(error); + } + }); +} \ No newline at end of file diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 66d23f12..e4ac7290 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -1,7 +1,7 @@ - + diff --git a/src/main/resources/templates/fragments/adminModals.html b/src/main/resources/templates/fragments/adminModals.html new file mode 100644 index 00000000..d5c19e74 --- /dev/null +++ b/src/main/resources/templates/fragments/adminModals.html @@ -0,0 +1,76 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/footer.html b/src/main/resources/templates/fragments/footer.html index 0579216d..bebd50d9 100644 --- a/src/main/resources/templates/fragments/footer.html +++ b/src/main/resources/templates/fragments/footer.html @@ -1,5 +1,5 @@ -