From e603718cfe3ab0c91703e7d7757ac248754a828f Mon Sep 17 00:00:00 2001 From: Pelayori <31128562+Pelayori@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:30:17 +0200 Subject: [PATCH 01/12] Remove old commented code --- .../java/com/uniovi/controllers/GameController.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/com/uniovi/controllers/GameController.java b/src/main/java/com/uniovi/controllers/GameController.java index ad8a7506..f8c0ffa3 100644 --- a/src/main/java/com/uniovi/controllers/GameController.java +++ b/src/main/java/com/uniovi/controllers/GameController.java @@ -79,32 +79,23 @@ 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); } From 0c7930b671d9f84b8ae52b77ee05d847faaa68a7 Mon Sep 17 00:00:00 2001 From: Pelayori <31128562+Pelayori@users.noreply.github.com> Date: Thu, 25 Apr 2024 01:00:53 +0200 Subject: [PATCH 02/12] Add table and dropdown to user management --- .../uniovi/configuration/SecurityConfig.java | 1 + .../uniovi/controllers/PlayersController.java | 22 ++++++++ src/main/java/com/uniovi/entities/Role.java | 5 ++ .../uniovi/repositories/PlayerRepository.java | 4 ++ .../services/InsertSampleDataService.java | 41 +++++++------- .../com/uniovi/services/PlayerService.java | 9 ++++ .../services/impl/PlayerServiceImpl.java | 7 +++ src/main/resources/messages.properties | 14 ++++- src/main/resources/messages_en.properties | 1 + src/main/resources/messages_es.properties | 1 + src/main/resources/messages_fr.properties | 1 + src/main/resources/static/css/admin.css | 22 ++++++++ src/main/resources/static/css/custom.css | 10 ++++ .../resources/templates/fragments/head.html | 2 +- .../templates/fragments/pagination.html | 4 +- .../fragments/paginationDynamic.html | 36 +++++++++++++ .../templates/player/admin/admin.html | 54 +++++++++++++++++++ .../player/admin/questionManagement.html | 1 + .../player/admin/userManagement.html | 30 +++++++++++ src/main/resources/templates/player/home.html | 13 ++--- 20 files changed, 248 insertions(+), 30 deletions(-) create mode 100644 src/main/resources/static/css/admin.css create mode 100644 src/main/resources/templates/fragments/paginationDynamic.html create mode 100644 src/main/resources/templates/player/admin/admin.html create mode 100644 src/main/resources/templates/player/admin/questionManagement.html create mode 100644 src/main/resources/templates/player/admin/userManagement.html diff --git a/src/main/java/com/uniovi/configuration/SecurityConfig.java b/src/main/java/com/uniovi/configuration/SecurityConfig.java index c0af5d20..674cd83f 100644 --- a/src/main/java/com/uniovi/configuration/SecurityConfig.java +++ b/src/main/java/com/uniovi/configuration/SecurityConfig.java @@ -48,6 +48,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/api/**").permitAll() .requestMatchers("/game/**").authenticated() .requestMatchers("/ranking/playerRanking").authenticated() + .requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN") .requestMatchers("/**").permitAll() ).formLogin( form -> form diff --git a/src/main/java/com/uniovi/controllers/PlayersController.java b/src/main/java/com/uniovi/controllers/PlayersController.java index d291b6c6..afbc33c1 100644 --- a/src/main/java/com/uniovi/controllers/PlayersController.java +++ b/src/main/java/com/uniovi/controllers/PlayersController.java @@ -122,4 +122,26 @@ 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/questionManagement") + public String showQuestionManagementFragment() { + return "player/admin/questionManagement"; + } } 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/PlayerRepository.java b/src/main/java/com/uniovi/repositories/PlayerRepository.java index 77c2d9f8..cf4f0a98 100644 --- a/src/main/java/com/uniovi/repositories/PlayerRepository.java +++ b/src/main/java/com/uniovi/repositories/PlayerRepository.java @@ -1,9 +1,13 @@ package com.uniovi.repositories; import com.uniovi.entities.Player; +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); + + Page findAll(Pageable pageable); } diff --git a/src/main/java/com/uniovi/services/InsertSampleDataService.java b/src/main/java/com/uniovi/services/InsertSampleDataService.java index f51a0ed8..d5415fc7 100644 --- a/src/main/java/com/uniovi/services/InsertSampleDataService.java +++ b/src/main/java/com/uniovi/services/InsertSampleDataService.java @@ -32,35 +32,36 @@ public class InsertSampleDataService { private final QuestionService questionService; private final CategoryService categoryService; private final QuestionRepository questionRepository; - private final GameSessionRepository gameSessionRepository; - private Environment environment; + private final Environment environment; - private Logger log = LoggerFactory.getLogger(InsertSampleDataService.class); + private final Logger log = LoggerFactory.getLogger(InsertSampleDataService.class); public InsertSampleDataService(PlayerService playerService, QuestionService questionService, CategoryService categoryService, QuestionRepository questionRepository, - GameSessionRepository gameSessionRepository, Environment environment) { + Environment environment) { this.playerService = playerService; this.questionService = questionService; this.categoryService = categoryService; this.questionRepository = questionRepository; - this.gameSessionRepository = gameSessionRepository; this.environment = environment; } @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 (isProfileActive("prod")) + player.setRoles(new String[]{"ROLE_USER"}); + else + player.setRoles(new String[]{"ROLE_USER", "ROLE_ADMIN"}); playerService.generateApiKey(playerService.addNewPlayer(player)); } - if (Arrays.stream(environment.getActiveProfiles()).anyMatch(env -> (env.equalsIgnoreCase("test")))) { + if (isProfileActive("test")) { log.info("Test profile active, skipping sample data insertion"); return; } @@ -80,29 +81,33 @@ public void generateSampleData() throws InterruptedException, IOException { questionRepository.deleteAll(); MultipleQuestionGenerator allQuestionGenerator = new MultipleQuestionGenerator( - new ContinentQuestionGeneration(categoryService, Question.ENGLISH), - new CapitalQuestionGenerator(categoryService, Question.ENGLISH), - new BorderQuestionGenerator(categoryService, Question.ENGLISH) + //new ContinentQuestionGeneration(categoryService, Question.ENGLISH), + //new CapitalQuestionGenerator(categoryService, Question.ENGLISH), + //new BorderQuestionGenerator(categoryService, Question.ENGLISH) ); List questionsEn = allQuestionGenerator.getQuestions(); questionsEn.forEach(questionService::addNewQuestion); allQuestionGenerator = new MultipleQuestionGenerator( - new ContinentQuestionGeneration(categoryService, Question.SPANISH), - new CapitalQuestionGenerator(categoryService, Question.SPANISH), + //new ContinentQuestionGeneration(categoryService, Question.SPANISH), + //new CapitalQuestionGenerator(categoryService, Question.SPANISH), new BorderQuestionGenerator(categoryService, Question.SPANISH) ); List questionsEs = allQuestionGenerator.getQuestions(); questionsEs.forEach(questionService::addNewQuestion); - allQuestionGenerator = new MultipleQuestionGenerator( - new ContinentQuestionGeneration(categoryService, Question.FRENCH), - new CapitalQuestionGenerator(categoryService, Question.FRENCH), - new BorderQuestionGenerator(categoryService, Question.FRENCH) - ); + //allQuestionGenerator = new MultipleQuestionGenerator( + // new ContinentQuestionGeneration(categoryService, Question.FRENCH), + // new CapitalQuestionGenerator(categoryService, Question.FRENCH), + // new BorderQuestionGenerator(categoryService, Question.FRENCH) + //); List questionsFr = allQuestionGenerator.getQuestions(); questionsFr.forEach(questionService::addNewQuestion); log.info("Sample questions inserted"); } + + public boolean isProfileActive(String profile) { + return Arrays.stream(environment.getActiveProfiles()).anyMatch(env -> (env.equalsIgnoreCase(profile))); + } } diff --git a/src/main/java/com/uniovi/services/PlayerService.java b/src/main/java/com/uniovi/services/PlayerService.java index f59669e3..0cf320a2 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; @@ -72,4 +74,11 @@ public interface PlayerService { * @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); } diff --git a/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java b/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java index f4fa462d..d3b868a4 100644 --- a/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java +++ b/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java @@ -10,6 +10,8 @@ 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; @@ -136,4 +138,9 @@ public void updatePlayer(Long id, PlayerDto playerDto) { public void deletePlayer(Long id) { playerRepository.deleteById(id); } + + @Override + public Page getPlayersPage(Pageable pageable) { + return playerRepository.findAll(pageable); + } } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 2b91abb1..7130729c 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 @@ -114,4 +115,15 @@ 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 + +# -------------------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..901ef79b 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 diff --git a/src/main/resources/messages_es.properties b/src/main/resources/messages_es.properties index 174dad48..61bf6b83 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 diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index 1f5d2b22..616c23b8 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 diff --git a/src/main/resources/static/css/admin.css b/src/main/resources/static/css/admin.css new file mode 100644 index 00000000..a797f39b --- /dev/null +++ b/src/main/resources/static/css/admin.css @@ -0,0 +1,22 @@ +.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; +} \ 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..9d7cb489 100644 --- a/src/main/resources/static/css/custom.css +++ b/src/main/resources/static/css/custom.css @@ -66,4 +66,14 @@ footer { .prueba { font-weight: bold; +} + +.button-container { + display: flex; + justify-content: space-between; +} + +.button-container a { + flex: 1; + margin: 0 5px; } \ No newline at end of file diff --git a/src/main/resources/templates/fragments/head.html b/src/main/resources/templates/fragments/head.html index b75eb3c5..77172126 100644 --- a/src/main/resources/templates/fragments/head.html +++ b/src/main/resources/templates/fragments/head.html @@ -14,7 +14,7 @@ crossorigin="anonymous"> - + diff --git a/src/main/resources/templates/fragments/pagination.html b/src/main/resources/templates/fragments/pagination.html index ff816374..3a7fd539 100644 --- a/src/main/resources/templates/fragments/pagination.html +++ b/src/main/resources/templates/fragments/pagination.html @@ -2,7 +2,7 @@
  • - Primera + Primera
  • @@ -22,7 +22,7 @@
  • Última + th:href="@{'?page='+${page.getTotalPages()-1}}" th:text="#{page.last}"> Última
\ No newline at end of file diff --git a/src/main/resources/templates/fragments/paginationDynamic.html b/src/main/resources/templates/fragments/paginationDynamic.html new file mode 100644 index 00000000..7fb943c9 --- /dev/null +++ b/src/main/resources/templates/fragments/paginationDynamic.html @@ -0,0 +1,36 @@ +
+ + +
\ No newline at end of file diff --git a/src/main/resources/templates/player/admin/admin.html b/src/main/resources/templates/player/admin/admin.html new file mode 100644 index 00000000..27b2b915 --- /dev/null +++ b/src/main/resources/templates/player/admin/admin.html @@ -0,0 +1,54 @@ + + + + + + + + + + +
+

+ +
+
+
+
+
+ + + + diff --git a/src/main/resources/templates/player/admin/questionManagement.html b/src/main/resources/templates/player/admin/questionManagement.html new file mode 100644 index 00000000..1c9b64fb --- /dev/null +++ b/src/main/resources/templates/player/admin/questionManagement.html @@ -0,0 +1 @@ +

Preguntas

\ No newline at end of file diff --git a/src/main/resources/templates/player/admin/userManagement.html b/src/main/resources/templates/player/admin/userManagement.html new file mode 100644 index 00000000..0f723162 --- /dev/null +++ b/src/main/resources/templates/player/admin/userManagement.html @@ -0,0 +1,30 @@ +
+

+
+ + + + + + + + + + + + + + + + + +
UsernameEmailRoleActions
UsernameEmailRole
+
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/player/home.html b/src/main/resources/templates/player/home.html index abf528c5..9de4e5cb 100644 --- a/src/main/resources/templates/player/home.html +++ b/src/main/resources/templates/player/home.html @@ -8,18 +8,15 @@

-

-
-
- -
-
- -
+ +
+ + +
From ee0ae97247e4678cfa53d06aca24efa7ee10a313 Mon Sep 17 00:00:00 2001 From: Pelayori <31128562+Pelayori@users.noreply.github.com> Date: Fri, 26 Apr 2024 02:25:54 +0200 Subject: [PATCH 03/12] Finish user management --- .../uniovi/configuration/SecurityConfig.java | 2 +- .../uniovi/controllers/PlayersController.java | 111 ++++++++++++++++- .../services/InsertSampleDataService.java | 20 ++-- .../com/uniovi/services/PlayerService.java | 13 ++ .../java/com/uniovi/services/RoleService.java | 6 + .../services/impl/PlayerServiceImpl.java | 11 ++ .../uniovi/services/impl/RoleServiceImpl.java | 7 ++ src/main/resources/messages.properties | 11 ++ src/main/resources/messages_en.properties | 21 ++++ src/main/resources/messages_es.properties | 23 +++- src/main/resources/messages_fr.properties | 21 ++++ src/main/resources/static/css/admin.css | 4 + src/main/resources/static/css/custom.css | 46 +++++++ src/main/resources/static/css/nav.css | 2 +- .../resources/static/script/adminModals.js | 112 ++++++++++++++++++ .../templates/fragments/adminModals.html | 76 ++++++++++++ .../templates/player/admin/admin.html | 3 +- .../player/admin/userManagement.html | 38 ++++-- 18 files changed, 501 insertions(+), 26 deletions(-) create mode 100644 src/main/resources/static/script/adminModals.js create mode 100644 src/main/resources/templates/fragments/adminModals.html diff --git a/src/main/java/com/uniovi/configuration/SecurityConfig.java b/src/main/java/com/uniovi/configuration/SecurityConfig.java index 674cd83f..068364b0 100644 --- a/src/main/java/com/uniovi/configuration/SecurityConfig.java +++ b/src/main/java/com/uniovi/configuration/SecurityConfig.java @@ -48,7 +48,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/api/**").permitAll() .requestMatchers("/game/**").authenticated() .requestMatchers("/ranking/playerRanking").authenticated() - .requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN") + .requestMatchers("/player/admin/**").hasAuthority("ROLE_ADMIN") .requestMatchers("/**").permitAll() ).formLogin( form -> form diff --git a/src/main/java/com/uniovi/controllers/PlayersController.java b/src/main/java/com/uniovi/controllers/PlayersController.java index afbc33c1..c9fb5ff9 100644 --- a/src/main/java/com/uniovi/controllers/PlayersController.java +++ b/src/main/java/com/uniovi/controllers/PlayersController.java @@ -1,42 +1,52 @@ package com.uniovi.controllers; +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.entities.Role; import com.uniovi.services.GameSessionService; import com.uniovi.services.PlayerService; +import com.uniovi.services.RoleService; 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.security.Principal; import java.util.Optional; +import java.util.List; @Controller public class PlayersController { private final PlayerService playerService; + private final RoleService roleService; 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) { this.playerService = playerService; this.signUpValidator = signUpValidator; this.gameSessionService = gameSessionService; + this.roleService = roleService; } @GetMapping("/signup") @@ -140,6 +150,97 @@ public String showUserManagementFragment(Model model, Pageable pageable) { 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() { return "player/admin/questionManagement"; diff --git a/src/main/java/com/uniovi/services/InsertSampleDataService.java b/src/main/java/com/uniovi/services/InsertSampleDataService.java index d5415fc7..456aab2d 100644 --- a/src/main/java/com/uniovi/services/InsertSampleDataService.java +++ b/src/main/java/com/uniovi/services/InsertSampleDataService.java @@ -80,15 +80,15 @@ public void generateSampleData() throws InterruptedException, IOException { questionRepository.deleteAll(); - MultipleQuestionGenerator allQuestionGenerator = new MultipleQuestionGenerator( - //new ContinentQuestionGeneration(categoryService, Question.ENGLISH), - //new CapitalQuestionGenerator(categoryService, Question.ENGLISH), - //new BorderQuestionGenerator(categoryService, Question.ENGLISH) - ); - List questionsEn = allQuestionGenerator.getQuestions(); - questionsEn.forEach(questionService::addNewQuestion); + //MultipleQuestionGenerator allQuestionGenerator = new MultipleQuestionGenerator( + // //new ContinentQuestionGeneration(categoryService, Question.ENGLISH), + // //new CapitalQuestionGenerator(categoryService, Question.ENGLISH), + // //new BorderQuestionGenerator(categoryService, Question.ENGLISH) + //); + //List questionsEn = allQuestionGenerator.getQuestions(); + //questionsEn.forEach(questionService::addNewQuestion); - allQuestionGenerator = new MultipleQuestionGenerator( + MultipleQuestionGenerator allQuestionGenerator = new MultipleQuestionGenerator( //new ContinentQuestionGeneration(categoryService, Question.SPANISH), //new CapitalQuestionGenerator(categoryService, Question.SPANISH), new BorderQuestionGenerator(categoryService, Question.SPANISH) @@ -101,8 +101,8 @@ public void generateSampleData() throws InterruptedException, IOException { // new CapitalQuestionGenerator(categoryService, Question.FRENCH), // new BorderQuestionGenerator(categoryService, Question.FRENCH) //); - List questionsFr = allQuestionGenerator.getQuestions(); - questionsFr.forEach(questionService::addNewQuestion); + //List questionsFr = allQuestionGenerator.getQuestions(); + //questionsFr.forEach(questionService::addNewQuestion); log.info("Sample questions inserted"); } diff --git a/src/main/java/com/uniovi/services/PlayerService.java b/src/main/java/com/uniovi/services/PlayerService.java index 0cf320a2..fc9da433 100644 --- a/src/main/java/com/uniovi/services/PlayerService.java +++ b/src/main/java/com/uniovi/services/PlayerService.java @@ -81,4 +81,17 @@ public interface PlayerService { * @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/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/PlayerServiceImpl.java b/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java index d3b868a4..9d0fb497 100644 --- a/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java +++ b/src/main/java/com/uniovi/services/impl/PlayerServiceImpl.java @@ -143,4 +143,15 @@ public void deletePlayer(Long id) { 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/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 7130729c..dde5f6c0 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -123,6 +123,17 @@ 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 diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index 901ef79b..538ec9ee 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -118,4 +118,25 @@ 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 + +# -------------------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 61bf6b83..8445a750 100644 --- a/src/main/resources/messages_es.properties +++ b/src/main/resources/messages_es.properties @@ -117,4 +117,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 616c23b8..1722bb01 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -113,3 +113,24 @@ 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 + +# -------------------Déclarations pour la gestion de la page--------------------- +page.first=Première +page.last=Dernière + diff --git a/src/main/resources/static/css/admin.css b/src/main/resources/static/css/admin.css index a797f39b..db24eb8e 100644 --- a/src/main/resources/static/css/admin.css +++ b/src/main/resources/static/css/admin.css @@ -19,4 +19,8 @@ .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 9d7cb489..679d7a7e 100644 --- a/src/main/resources/static/css/custom.css +++ b/src/main/resources/static/css/custom.css @@ -76,4 +76,50 @@ footer { .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; } \ No newline at end of file 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/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/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/player/admin/admin.html b/src/main/resources/templates/player/admin/admin.html index 27b2b915..bf185e25 100644 --- a/src/main/resources/templates/player/admin/admin.html +++ b/src/main/resources/templates/player/admin/admin.html @@ -8,7 +8,7 @@ -
+