diff --git a/Jenkinsfile b/Jenkinsfile index 55fa603..adf3575 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -44,8 +44,10 @@ pipeline { when { branch 'develop' } - withCredentials(bindings: [usernamePassword(credentialsId: 'dockerhub-credentials', passwordVariable: 'pass', usernameVariable: 'name')]) { - sh 'mvn compile jib:dockerBuild -Djib.to.auth.username=$name -Djib.to.auth.password=$pass' + steps { + withCredentials(bindings: [usernamePassword(credentialsId: 'dockerhub-credentials', passwordVariable: 'pass', usernameVariable: 'name')]) { + sh 'mvn compile jib:dockerBuild -Djib.to.auth.username=$name -Djib.to.auth.password=$pass' + } } } @@ -70,14 +72,8 @@ pipeline { } } - - post { - always { - junit 'build/reports/**/*.xml' - } - } tools { maven 'maven' } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 9152541..a6df27b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ -# PBL5 -PBL5 +

+ GitHub Readme Stats +

Deck Learn

+

A spaced repetition based learning application

+

+ +

+ + Tests Passing + + + + + + Issues + + + GitHub pull requests + +

+ +## Description +Learning is an everyday necessity worldwide, which needs to be done the correct way +to be effective. Information tends to fade in what is known as “the forgetting curve”, a problem +that concerns a wide range of population. However, the fading of the data can be reverted and +stabilised by means of various techniques. One that can be used to achieve a good procure of +knowledge is spaced repetition. This method has been demonstrated to provide a sustainable +capacity of memorization for its users. One of the many ways of using it is by means of the +Leitner system, a flashcard approach that determines when is necessary to regain certain +knowledge. Those flashcards are composed by a question and its answer, storing cards of the +same topic in decks. Spaced repetition does not usually consider the motivation of the user. +However, as it is highly important to maintain the user comfortable, gamification has been +introduced to fulfil that purpose. Due to the importance of providing social interaction, a social +network-like approach has been embraced by the application. Due to the importance of fulfilling +the 2030 agenda of the United Nations, considering the SDGs has also been relevant. This work +explains the development of the product, named DeckLearn, as well as its impact towards the +established objectives and working hypotheses. + +## Used technologies +* [![image](https://img.shields.io/badge/Spring_Boot-F2F4F9?style=for-the-badge&logo=spring-boot)](https://spring.io/projects/spring-boot) +* [Thymeleaf](https://www.thymeleaf.org/) +* [![image](https://img.shields.io/badge/Hibernate-59666C?style=for-the-badge&logo=Hibernate&logoColor=white)](https://hibernate.org/) +* [![image](https://img.shields.io/badge/Jenkins-D24939?style=for-the-badge&logo=Jenkins&logoColor=white)](https://www.jenkins.io/) +* [![image](https://img.shields.io/badge/Nginx-009639?style=for-the-badge&logo=nginx&logoColor=white)](https://www.nginx.com/) +* [Sonarqube](https://www.sonarqube.org/) + + +## CI/CD + +## + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. diff --git a/pom.xml b/pom.xml index a572c0f..8006f50 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ eus.blankcard decklearn - 0.0.1-SNAPSHOT + 1.0 decklearn DeckLearn project source code @@ -61,6 +61,14 @@ test + + junit + junit + 4.4 + test + + + @@ -93,7 +101,26 @@ - + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + prepare-agent + + prepare-agent + + + + report + test + + report + + + + diff --git a/src/main/java/eus/blankcard/decklearn/controller/DeckController.java b/src/main/java/eus/blankcard/decklearn/controller/DeckController.java new file mode 100644 index 0000000..d26c0c8 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/DeckController.java @@ -0,0 +1,223 @@ +package eus.blankcard.decklearn.controller; + +import java.sql.Time; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.deck.DeckRepository; +import eus.blankcard.decklearn.repository.user.UserRepository; +import eus.blankcard.decklearn.util.DeckCreationUtils; +import eus.blankcard.decklearn.util.StatsCalculator; + +@Controller +public class DeckController { + + @Autowired + UserRepository userRepository; + + @Autowired + DeckRepository deckRepository; + + @Autowired + DeckCreationUtils deckCreationUtils; + + @Autowired + StatsCalculator statsCalculator; + + String redirectError = "redirect:/error"; + + @GetMapping("/deck/{deckId}") + public String getMethodName(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUsername = authentication.getName(); + + UserModel loggedUser = userRepository.findByUsername(loggedUsername); + + DeckModel deck = deckRepository.getById(deckId); + UserModel creator = deck.getCreator(); + + req.setAttribute("deck", deck); + req.setAttribute("cardNumber", deck.getCards().size()); + req.setAttribute("types", deck.getTypes()); + req.setAttribute("creator", creator.getUsername()); + req.setAttribute("studies", deck.getTrainings().size()); + req.setAttribute("home", true); + req.setAttribute("saved", loggedUser.getSavedDecks().contains(deck)); + req.setAttribute("isCreator", loggedUser.getDecks().contains(deck)); + + return "/deck/deck_view"; + } + + @GetMapping("/deck/{deckId}/stats") + public String getDeckStats(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse response) { + DeckModel deck = deckRepository.getById(deckId); + + AtomicInteger monthStudies = statsCalculator.getMonthStudies(deck); + int totalStudies = deck.getTrainings().size(); + Time avgTime = statsCalculator.getAvgResponseTime(deck); + String timeFormat = new SimpleDateFormat("mm:ss").format(avgTime); + + int totalSaves = deck.getSavers().size(); + int averagePass = statsCalculator.getAveragePassRatio(deck); + + req.setAttribute("deck", deck); + req.setAttribute("monthStudies", monthStudies); + req.setAttribute("totalStudies", totalStudies); + req.setAttribute("avgTime", timeFormat); + req.setAttribute("totalSaves", totalSaves); + req.setAttribute("averagePass", averagePass); + req.setAttribute("stats", true); + + return "/deck/deck_stats"; + } + + @PostMapping("/deck/{deckId}/save") + public String saveDeck(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse response) { + + Optional optionalDeck = deckRepository.findById(deckId); + + if (optionalDeck.isPresent()) { + DeckModel deckModel = optionalDeck.get(); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUsername = authentication.getName(); + + UserModel userModel = userRepository.findByUsername(loggedUsername); + + if (userModel.getSavedDecks().contains(deckModel)) { + userModel.getSavedDecks().remove(deckModel); + } else { + userModel.getSavedDecks().add(deckModel); + } + + userRepository.save(userModel); + + return "redirect:/deck/" + deckModel.getId(); + } else { + return redirectError; + } + } + + @PostMapping("/deck/{deckId}/remove") + public String deleteDeck(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse response) { + + Optional optionalDeck = deckRepository.findById(deckId); + + if (optionalDeck.isPresent()) { + DeckModel deck = optionalDeck.get(); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUsername = authentication.getName(); + + UserModel loggedUser = userRepository.findByUsername(loggedUsername); + + if (deck.getCreator().getId().equals(loggedUser.getId())) { + deckRepository.delete(deck); + } else { + return redirectError; + } + + return "redirect:/" + loggedUsername; + + } else { + return redirectError; + } + } + + @GetMapping("/create/deck") + public String getCreationForm(HttpServletRequest req, + HttpServletResponse response) { + + DeckModel deck = new DeckModel(); + deck.setCards(new ArrayList<>()); + + req.setAttribute("deck", deck); + req.setAttribute("cardNum", deck.getCards().size()); + req.setAttribute("action", "new"); + req.setAttribute("create", true); + + return "deck/deck_creation"; + } + + @GetMapping("/create/deck/{deckId}") + public String getCreateFormWithDeck(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse res) { + + DeckModel deck = deckRepository.getById(deckId); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUsername = authentication.getName(); + UserModel userModel = userRepository.findByUsername(loggedUsername); + + if (deck.getCreator().getId().equals(userModel.getId())) { + req.setAttribute("deck", deck); + req.setAttribute("cardNum", deck.getCards().size()); + req.setAttribute("action", "edit"); + req.setAttribute("create", true); + return "deck/deck_creation"; + } else { + return redirectError; + } + + } + + @PostMapping("/create/deck") + public String saveDeckForFirstTime(HttpServletRequest req, HttpServletResponse res) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUsername = authentication.getName(); + UserModel loggedUser = userRepository.findByUsername(loggedUsername); + + DeckModel deck = new DeckModel(); + + deck.setCreator(loggedUser); + deck = deckRepository.save(deck); + + deck.setTitle(req.getParameter("title")); + deck.setDescription(req.getParameter("description")); + deck.setImgPath("/images/deck/default.png"); + + String redirectUrl = "redirect:/create/deck/" + deck.getId(); + String action = req.getParameter("action"); + + redirectUrl = deckCreationUtils.checkAction(req, res, deck, action); + + return redirectUrl; + } + + @PostMapping("/create/deck/{deckId}") + public String createDeck(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse res) { + + DeckModel deck = deckRepository.getById(deckId); + deck.setTitle(req.getParameter("title")); + deck.setDescription(req.getParameter("description")); + deck.setImgPath("/images/deck/default.png"); + + String redirectUrl = "redirect:/create/deck/" + deck.getId(); + String action = req.getParameter("action"); + + redirectUrl = deckCreationUtils.checkAction(req, res, deck, action); + + return redirectUrl; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/HomeController.java b/src/main/java/eus/blankcard/decklearn/controller/HomeController.java new file mode 100644 index 0000000..30bfa4b --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/HomeController.java @@ -0,0 +1,65 @@ +package eus.blankcard.decklearn.controller; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.deck.DeckRepository; +import eus.blankcard.decklearn.repository.user.UserRepository; +import eus.blankcard.decklearn.util.UserUtils; + +@Controller +public class HomeController { + + @Autowired + UserRepository userRepository; + + @Autowired + DeckRepository deckRepository; + + @Autowired + UserUtils userUtils; + + @GetMapping("/home") + public String getHome(HttpServletRequest req, HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + UserModel user = userRepository.findByUsername(username); + + List studySessions = new ArrayList<>(); + + user.getTrainings().forEach(t -> studySessions.add(t.getDeck())); + req.setAttribute("study_sessions", studySessions); + + Set followedDecks = userUtils.getFollowingDecks(user); + + if (followedDecks.isEmpty()) { + Pageable limit = PageRequest.of(0, 20); + followedDecks = deckRepository.findAll(limit).toSet(); + req.setAttribute("listName", "Explore Decks"); + } else { + req.setAttribute("listName", "Followed Users Decks"); + } + + req.setAttribute("explore_deck", followedDecks); + + req.setAttribute("home", true); + + return "home"; + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/controller/IndexController.java b/src/main/java/eus/blankcard/decklearn/controller/IndexController.java new file mode 100644 index 0000000..1a5bdd1 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/IndexController.java @@ -0,0 +1,15 @@ +package eus.blankcard.decklearn.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping({ "", "/" }) +public class IndexController { + + @GetMapping({ "", "/" }) + public String getHome() { + return "redirect:/home"; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/LoginController.java b/src/main/java/eus/blankcard/decklearn/controller/LoginController.java new file mode 100644 index 0000000..4c861dc --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/LoginController.java @@ -0,0 +1,16 @@ +package eus.blankcard.decklearn.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class LoginController { + + @GetMapping("/login") + public String login(HttpServletRequest request, HttpServletResponse response) { + return "login"; + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/controller/LogoutController.java b/src/main/java/eus/blankcard/decklearn/controller/LogoutController.java new file mode 100644 index 0000000..a78584b --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/LogoutController.java @@ -0,0 +1,30 @@ +package eus.blankcard.decklearn.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Controller +public class LogoutController { + + @Autowired + UserRepository userRepository; + + @GetMapping(value = "/logout") + public String logoutPage(HttpServletRequest request, HttpServletResponse response) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + new SecurityContextLogoutHandler().logout(request, response, auth); + } + return "redirect:/login?handler=logout"; // You can redirect wherever you want, but generally it's a good practice + // to show login screen again. + } +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/ProfileController.java b/src/main/java/eus/blankcard/decklearn/controller/ProfileController.java new file mode 100644 index 0000000..0dda619 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/ProfileController.java @@ -0,0 +1,182 @@ +package eus.blankcard.decklearn.controller; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; +import eus.blankcard.decklearn.util.UserUtils; + +@Controller +public class ProfileController { + + @Autowired + UserRepository userRepository; + + @Autowired + UserUtils userUtils; + + String userProfile = "user/profile"; + String error = "error"; + + @GetMapping("/{username}") + public String getProfile(@PathVariable("username") String username, HttpServletRequest req, + HttpServletResponse response) { + UserModel user = null; + + user = userRepository.findByUsername(username); + if (user != null) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + UserModel loggedUser = userRepository.findByUsername(currentPrincipalName); + + req.setAttribute("user", user); + req.setAttribute("followers", user.getFollowers().size()); + req.setAttribute("following", user.getFollowed().size()); + req.setAttribute("loggedUserFollowing", userUtils.checkFollowed(loggedUser, user)); + req.setAttribute("profile", true); + + req.setAttribute("userDecks", user.getDecks()); + + if (currentPrincipalName.equals(username)) { + req.setAttribute("decks", true); + return userProfile; + } else { + return "user/profile_visit"; + } + } else { + response.setStatus(404); + return error; + } + } + + @GetMapping("/{username}/followers") + public String getFollowers(@PathVariable("username") String username, HttpServletRequest req, + HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + UserModel loggedUser = userRepository.findByUsername(currentPrincipalName); + UserModel user = userRepository.findByUsername(username); + + Set loggedUserFollowing = userUtils.getFollowingFromList(loggedUser, user.getFollowers()); + Set loggedUserNotFollowing = user.getFollowers(); + loggedUserNotFollowing.removeAll(loggedUserFollowing); + + req.setAttribute("loggedUserNotFollowing", loggedUserNotFollowing); + req.setAttribute("loggedUserFollowing", loggedUserFollowing); + req.setAttribute("type", "Followers"); + req.setAttribute("profile", true); + + return "user/following"; + } + + @GetMapping("/{username}/following") + public String getFollowing(@PathVariable("username") String username, HttpServletRequest req, + HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + UserModel loggedUser = userRepository.findByUsername(currentPrincipalName); + UserModel user = userRepository.findByUsername(username); + + Set loggedUserFollowing = userUtils.getFollowingFromList(loggedUser, user.getFollowed()); + Set loggedUserNotFollowing = user.getFollowed(); + loggedUserNotFollowing.removeAll(loggedUserFollowing); + + req.setAttribute("loggedUserNotFollowing", loggedUserNotFollowing); + req.setAttribute("loggedUserFollowing", loggedUserFollowing); + req.setAttribute("type", "Following"); + req.setAttribute("profile", true); + + return "user/following"; + } + + @GetMapping("/{username}/saved") + public String getSaved(@PathVariable("username") String username, HttpServletRequest req, + HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + if (username.equals(currentPrincipalName)) { + UserModel user = null; + user = userRepository.findByUsername(username); + + req.setAttribute("user", user); + + req.setAttribute("followers", user.getFollowers().size()); + req.setAttribute("following", user.getFollowed().size()); + req.setAttribute("saved", true); + req.setAttribute("userDecks", user.getSavedDecks()); + return userProfile; + } else { + return error; + } + } + + @GetMapping("/{username}/sessions") + public String getSessions(@PathVariable("username") String username, HttpServletRequest req, + HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + if (username.equals(currentPrincipalName)) { + UserModel user = null; + user = userRepository.findByUsername(username); + + List trainingSessions = new ArrayList<>(); + user.getTrainings().forEach(t -> trainingSessions.add(t.getDeck())); + + req.setAttribute("user", user); + + req.setAttribute("followers", user.getFollowers().size()); + req.setAttribute("following", user.getFollowed().size()); + req.setAttribute("sessions", true); + req.setAttribute("userDecks", trainingSessions); + return userProfile; + } else { + return error; + } + } + + @PostMapping("/{username}/follow") + public String follow(@PathVariable("username") String username, HttpServletRequest req, + HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + UserModel loggedUser = userRepository.findByUsername(currentPrincipalName); + UserModel targetUser = userRepository.findByUsername(username); + + if (userUtils.checkFollowed(loggedUser, targetUser)) { + loggedUser.removeFollowed(targetUser); + targetUser.removeFollower(loggedUser); + + userRepository.save(loggedUser); + userRepository.save(targetUser); + } else { + loggedUser.addFollowed(targetUser); + targetUser.addFollower(loggedUser); + + userRepository.save(loggedUser); + userRepository.save(targetUser); + } + + return "redirect:/" + username; + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/controller/RegisterController.java b/src/main/java/eus/blankcard/decklearn/controller/RegisterController.java new file mode 100644 index 0000000..f408861 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/RegisterController.java @@ -0,0 +1,48 @@ +package eus.blankcard.decklearn.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; +import eus.blankcard.decklearn.util.RegisterUtils; + +@Controller +public class RegisterController { + + @Autowired + RegisterUtils registerUtils; + + @Autowired + private UserRepository userRepository; + + @Autowired + private BCryptPasswordEncoder encoder; + + @GetMapping("/register") + public String registerForm() { + return "register"; + } + + @PostMapping("/register") + public String registerSubmit(UserModel user) { + + String redirectUrl = "redirect:/login"; + + UserModel existingUser = userRepository.findByUsername(user.getUsername()); + + if(existingUser == null) { + user.setPassword(encoder.encode(user.getPassword())); + user.setImgPath("/images/user/default.png"); + user = userRepository.save(user); + registerUtils.setDefaultFollower(user); + } else { + redirectUrl = "redirect:/register"; + } + + return redirectUrl; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/SearchController.java b/src/main/java/eus/blankcard/decklearn/controller/SearchController.java new file mode 100644 index 0000000..7fbd1c8 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/SearchController.java @@ -0,0 +1,49 @@ +package eus.blankcard.decklearn.controller; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.repository.deck.DeckRepository; +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Controller +public class SearchController { + + @Autowired + UserRepository userRepository; + + @Autowired + DeckRepository deckRepository; + + @GetMapping(value = "/deck/search") + public String getSearchResults(HttpServletRequest req, HttpServletResponse response) { + String title = req.getParameter("searchName"); + + String strippedTitle = title.trim(); + + if (strippedTitle.equals("")) { + return "redirect:/home"; + } else { + Pageable limit = PageRequest.of(0, 20); + + List searchResult = deckRepository.findByTitleContaining(title, limit); + + req.setAttribute("decks", searchResult); + + req.setAttribute("pageName", "Search results"); + req.setAttribute("home", true); + + return "search_results"; + } + + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/controller/SessionsController.java b/src/main/java/eus/blankcard/decklearn/controller/SessionsController.java new file mode 100644 index 0000000..24c9a20 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/SessionsController.java @@ -0,0 +1,47 @@ +package eus.blankcard.decklearn.controller; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.deck.DeckRepository; +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Controller +public class SessionsController { + + @Autowired + UserRepository userRepository; + + @Autowired + DeckRepository deckRepository; + + @GetMapping("/sessions") + public String getSessions(HttpServletRequest req, HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + UserModel user = userRepository.findByUsername(username); + + List studySessions = new ArrayList<>(); + + user.getTrainings().forEach(t -> studySessions.add(t.getDeck())); + req.setAttribute("decks", studySessions); + + req.setAttribute("pageName", "Study sessions"); + req.setAttribute("sessions", true); + + return "search_results"; + } + +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/SettingsController.java b/src/main/java/eus/blankcard/decklearn/controller/SettingsController.java new file mode 100644 index 0000000..001eb95 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/SettingsController.java @@ -0,0 +1,122 @@ +package eus.blankcard.decklearn.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; + +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Controller +public class SettingsController { + + @Autowired + UserRepository userRepository; + + @Autowired + private BCryptPasswordEncoder encoder; + + String defaultFolder = "settings/"; + String constantError = "error"; + + public String defaultGet(HttpServletRequest req, HttpServletResponse response, String obtaniedPage) { + UserModel user = null; + String username = null; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + username = authentication.getName(); + + user = userRepository.findByUsername(username); + if (user != null) { + req.setAttribute("user", user); + req.setAttribute("settings", true); + return defaultFolder + obtaniedPage; + } else { + response.setStatus(404); + return constantError; + } + } + + @GetMapping({ "/settings/profile", "/settings" }) + public String getProfile(HttpServletRequest req, HttpServletResponse response) { + req.setAttribute("settings", true); + return defaultGet(req, response, "profile_settings"); + } + + @GetMapping("/settings/security") + public String getSecurity(HttpServletRequest req, HttpServletResponse response) { + req.setAttribute("settings", true); + return defaultGet(req, response, "security_settings"); + } + + @GetMapping("/settings/language") + public String getLanguage(HttpServletRequest req, HttpServletResponse response) { + req.setAttribute("settings", true); + return defaultGet(req, response, "language_settings"); + } + + @PostMapping("/settings/profile") + public String profileSubmit(UserModel newUser, HttpServletRequest req, HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + UserModel user = userRepository.findByUsername(username); + + String newName = newUser.getName(); + String newSurname = newUser.getSurname(); + String newUsername = newUser.getUsername(); + String newPostalCode = newUser.getPostalCode(); + String newCountry = newUser.getCountry(); + + user.setName(newName); + user.setSurname(newSurname); + user.setUsername(newUsername); + user.setPostalCode(newPostalCode); + user.setCountry(newCountry); + + Authentication token = new PreAuthenticatedAuthenticationToken(user.getUsername(), user.getPassword(), + authentication.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(token); + + userRepository.save(user); + return "redirect:/home"; + } + + @PostMapping("/settings/security") + public String securitySubmit(UserModel newUser, HttpServletRequest req, HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + UserModel user = userRepository.findByUsername(username); + + String newEmail = newUser.getEmail(); + String newPass = newUser.getPassword(); + + if (newEmail != null) { + user.setEmail(newEmail); + } else { + return "redirect:/error"; + } + + if (newPass != null) { + user.setPassword(encoder.encode(newPass)); + } else { + return "redirect:/error"; + } + + Authentication token = new PreAuthenticatedAuthenticationToken(user.getUsername(), user.getPassword(), + authentication.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(token); + + userRepository.save(user); + return "redirect:/home"; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/StatsController.java b/src/main/java/eus/blankcard/decklearn/controller/StatsController.java new file mode 100644 index 0000000..431f98e --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/StatsController.java @@ -0,0 +1,54 @@ +package eus.blankcard.decklearn.controller; + +import java.sql.Time; +import java.text.SimpleDateFormat; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; +import eus.blankcard.decklearn.util.StatsCalculator; + +@Controller +public class StatsController { + + @Autowired + UserRepository userRepository; + + @Autowired + StatsCalculator statsCalculator; + + @GetMapping("/stats") + public String getStats(HttpServletRequest req, HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String currentPrincipalName = authentication.getName(); + + UserModel user = userRepository.findByUsername(currentPrincipalName); + + AtomicInteger totalStudies = statsCalculator.getTotalStudies(user); + AtomicInteger monthStudies = statsCalculator.getMonthStudies(user); + int savedDecks = user.getSavedDecks().size(); + Time avgTime = statsCalculator.getAvgResponseTime(user); + String timeFormat = new SimpleDateFormat("mm:ss").format(avgTime); + + int avgPass = statsCalculator.getAveragePassRatio(user); + + req.setAttribute("user", user); + req.setAttribute("totalStudies", totalStudies); + req.setAttribute("studiesMonth", monthStudies); + req.setAttribute("saves", savedDecks); + req.setAttribute("avgTime", timeFormat); + req.setAttribute("averagePass", avgPass); + req.setAttribute("stats", true); + + return "user/user_stats"; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/controller/StudyController.java b/src/main/java/eus/blankcard/decklearn/controller/StudyController.java new file mode 100644 index 0000000..f22a5f9 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/controller/StudyController.java @@ -0,0 +1,196 @@ +package eus.blankcard.decklearn.controller; + +import java.sql.Time; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.Optional; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +import eus.blankcard.decklearn.gamification.SessionCardManager; +import eus.blankcard.decklearn.gamification.SessionManager; +import eus.blankcard.decklearn.models.TrainingModel; +import eus.blankcard.decklearn.models.TrainingSessionModel; +import eus.blankcard.decklearn.models.card.CardModel; +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.CardRepository; +import eus.blankcard.decklearn.repository.TrainingRepository; +import eus.blankcard.decklearn.repository.deck.DeckRepository; +import eus.blankcard.decklearn.repository.trainingsession.TrainingSessionRepository; +import eus.blankcard.decklearn.repository.user.UserRepository; +import eus.blankcard.decklearn.util.StatsCalculator; + +@Controller +public class StudyController { + + @Autowired + TrainingRepository trainingRepository; + + @Autowired + TrainingSessionRepository trainingSessionRepository; + + @Autowired + CardRepository cardRepository; + + @Autowired + DeckRepository deckRepository; + + @Autowired + SessionManager sessionManager; + + @Autowired + UserRepository userRepository; + + @Autowired + StatsCalculator statsCalculator; + + @PostMapping("/study/{deckId}") + public String studyDeck(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUser = authentication.getName(); + + UserModel user = userRepository.findByUsername(loggedUser); + DeckModel deck = deckRepository.getById(deckId); + + // Try loading the trainign and if null (not exist) create one. + TrainingModel training = trainingRepository.findByUserIdInAndDeckId(user.getId(), deckId); + if (training == null) { + training = new TrainingModel(); + training.setUser(user); + training.setDeck(deck); + training.setTrainingDate(java.sql.Timestamp.valueOf(LocalDateTime.now())); + training = trainingRepository.save(training); + } + + // Create a new Training Session and save it on the database + TrainingSessionModel trainingSession = new TrainingSessionModel(); + trainingSession.setTraining(training); + trainingSession.setDate(java.sql.Timestamp.valueOf(LocalDateTime.now())); + trainingSession = trainingSessionRepository.save(trainingSession); + + // Add the current session to the sessionManager + sessionManager.addSession(loggedUser, trainingSession); + + return "redirect:/study/" + deckId; + } + + @GetMapping("/study/{deckId}") + public String getStudyView(@PathVariable("deckId") Integer deckId, HttpServletRequest req, + HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUser = authentication.getName(); + DeckModel deck = deckRepository.getById(deckId); + + SessionCardManager sessionCardManager = sessionManager.getSession(loggedUser); + + // Get the card Id and load all the params from DB bc if there is a second time + // the card is empty + CardModel card = sessionCardManager.getNextCard(); + Optional optional = cardRepository.findById(card.getId()); + + TrainingSessionModel currentTraining = sessionCardManager.getCurrentTrainingSession(); + // Add sessionId to response header (for testing) + response.setHeader("sessionId", String.valueOf(currentTraining.getId())); + + if (optional.isPresent()) { + card = optional.get(); + + req.setAttribute("card", card); + req.setAttribute("deck", deck); + req.setAttribute("sessions", true); + + return "study/card_question"; + } else { + return "redirect:/error"; + } + } + + @GetMapping("/study/{deckId}/{cardId}") + public String getResponseView(@PathVariable("deckId") Integer deckId, @PathVariable("cardId") Integer cardId, + HttpServletRequest req, HttpServletResponse response) { + CardModel card = cardRepository.getById(cardId); + DeckModel deck = deckRepository.getById(deckId); + + req.setAttribute("deck", deck); + req.setAttribute("card", card); + req.setAttribute("sessions", true); + + return "/study/card_answer"; + } + + @PostMapping("/study/{deckId}/{cardId}") + public String saveResponse(@PathVariable("deckId") Integer deckId, @PathVariable("cardId") Integer cardId, + HttpServletRequest req, HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String loggedUser = authentication.getName(); + + String cardResult = req.getParameter("response"); + + boolean correct = cardResult.equals("pass"); + CardModel card = cardRepository.getById(cardId); + + SessionCardManager sessionCardManager = sessionManager.getSession(loggedUser); + sessionCardManager.saveCardResponse(card, correct); + + if (sessionCardManager.cardsRemaining()) { + return "redirect:/study/" + deckId; + } else { + sessionCardManager.saveSessionResults(); + TrainingSessionModel currentTraining = sessionCardManager.getCurrentTrainingSession(); + sessionManager.removeSession(loggedUser); + + return "redirect:/study/stats/" + currentTraining.getId(); + } + } + + @GetMapping("/study/stats/{trainingSessionId}") + public String getTrainingSessionStats(@PathVariable("trainingSessionId") Integer trainingSessionId, + HttpServletRequest req, HttpServletResponse response) { + + Optional optionalTrainingSession = trainingSessionRepository.findById(trainingSessionId); + + if (optionalTrainingSession.isPresent()) { + TrainingSessionModel trainingSession = optionalTrainingSession.get(); + DeckModel deck = trainingSession.getTraining().getDeck(); + + Time totalStudyTime = statsCalculator.getTotalStudyTime(trainingSession); + String studyTimeFormat = new SimpleDateFormat("mm:ss").format(totalStudyTime); + + Time avgResponseTime = statsCalculator.getAvgResponseTime(trainingSession); + String avgFormat = new SimpleDateFormat("mm:ss").format(avgResponseTime); + + int totalSessions = trainingSession.getTraining().getTrainingSessions().size(); + int passRatio = statsCalculator.getPassRatio(trainingSession); + float gradeChange = statsCalculator.getGradeChange(trainingSession, passRatio); + + req.setAttribute("deck", deck); + req.setAttribute("totalStudyTime", studyTimeFormat); + req.setAttribute("avgResponseTime", avgFormat); + req.setAttribute("totalSessions", totalSessions); + req.setAttribute("passRatio", passRatio); + req.setAttribute("gradeChange", gradeChange); + req.setAttribute("stats", true); + + req.setAttribute("up", gradeChange >= 0); + req.setAttribute("down", gradeChange < 0); + + return "study/session_review"; + } else { + return "redirect:/error"; + } + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/gamification/SessionCardManager.java b/src/main/java/eus/blankcard/decklearn/gamification/SessionCardManager.java new file mode 100644 index 0000000..a53360e --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/gamification/SessionCardManager.java @@ -0,0 +1,289 @@ +package eus.blankcard.decklearn.gamification; + +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; + +import java.sql.Time; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import eus.blankcard.decklearn.models.ResultsModel; +import eus.blankcard.decklearn.models.TrainingSessionModel; +import eus.blankcard.decklearn.models.card.CardModel; +import eus.blankcard.decklearn.models.card.CardResponseModel; +import eus.blankcard.decklearn.repository.CardRepository; +import eus.blankcard.decklearn.repository.CardResponseRepository; +import eus.blankcard.decklearn.repository.ResultsRepository; +import eus.blankcard.decklearn.repository.trainingsession.TrainingSessionRepository; + +@Component +@Scope("prototype") +public class SessionCardManager { + private TrainingSessionModel trainingSession; + private List cards; + private Map> resultResponseMap; + private LocalDateTime cardSendTime; + + @Autowired + CardResponseRepository cardResponseRepository; + + @Autowired + TrainingSessionRepository trainingSessionRepository; + + @Autowired + CardRepository cardRepository; + + @Autowired + ResultsRepository resultsRepository; + + public SessionCardManager() { + resultResponseMap = new HashMap<>(); + cards = new ArrayList<>(); + } + + public void initSessionCardManager(TrainingSessionModel trainingSession) { + this.trainingSession = trainingSession; + + loadCards(); + initMap(); + } + + private void initMap() { + cards.forEach(c -> resultResponseMap.put(c.getId(), new ArrayList<>())); + } + + private void loadCards() { + List traininSessions = trainingSession.getTraining().getTrainingSessions(); + + // If it's the first traininSession of the training load all the cards + + if (traininSessions.size() <= 1) { + this.cards = trainingSession.getTraining().getDeck().getCards(); + } else { + // Get the previous to the current one + TrainingSessionModel prevTraining = traininSessions.get(traininSessions.size() - 2); + + LocalDateTime prevDate = prevTraining.getDate().toLocalDateTime(); + Long daysBetween = Duration.between(prevDate, LocalDateTime.now()).toDays(); + + // If it's the second study today, load all the cards and ignore the previous + if (daysBetween < 1) { + this.cards = trainingSession.getTraining().getDeck().getCards(); + + } else { + + loadRequiredCards(); + } + } + + } + + private void loadRequiredCards() { + // Load the previous + List traininSessions = trainingSession.getTraining().getTrainingSessions(); + TrainingSessionModel prevTraining = traininSessions.get(traininSessions.size() - 2); + + if (prevTraining != null) { + + prevTraining.getResults().forEach(result -> { + int boxNum = result.getBoxNumber(); + LocalDate resDate = result.getCardResponses().get(0).getResponseDate().toLocalDate(); + + if (boxNum != 0) { + Long daysBetween = Duration.between(resDate.atStartOfDay(), LocalDate.now().atStartOfDay()).toDays(); + CardModel card = result.getCardResponses().get(0).getCard(); + + if (Math.pow(2, boxNum - (double) 1) <= daysBetween) { + cards.add(card); + } else { + // If the card is not required I pass the previous cardResponses + resultResponseMap.put(card.getId(), result.getCardResponses()); + } + } + }); + } else { + this.cards = trainingSession.getTraining().getDeck().getCards(); + } + } + + public void saveSessionResults() { + List traininSessions = trainingSession.getTraining().getTrainingSessions(); + + resultResponseMap.forEach((k, v) -> { + + ResultsModel result = new ResultsModel(); + CardModel card = cardRepository.getById(k); + + result.setCard(card); + result.setTrainingSession(trainingSession); + + // Mirar el anterior que caja es. Si no existe anterior a la caja 1, si existe y + // es a la primera bien + // Anterior caja + 1 y si está mal directamente a la 1. + if (v.size() > 1) { + result.setBoxNumber(1); + + } else { + // If it's the first trainingSession and the card is correct put on box 1 + if (traininSessions.size() <= 1) { + + result.setBoxNumber(1); + } else { + TrainingSessionModel prevTraining = traininSessions.get(traininSessions.size() - 2); + + // I load the whole prevTraining to be able to acces it's child tables. Bc if i + // dont load it, i get a LazyInitializationException + prevTraining = trainingSessionRepository.getById(prevTraining.getId()); + + List previousResults = prevTraining.getResults(); + + for (ResultsModel prevResult : previousResults) { + if (prevResult.getCard().getId().equals(k)) { + result.setBoxNumber(prevResult.getBoxNumber() + 1); + break; + } + } + } + } + + result.setErrorCount(v.size()); + result.setAvgTime(calculateAvgTime(v)); + + result = resultsRepository.save(result); + + // Ponerle el padre a cada CardResponse y guardarla + for (CardResponseModel cardResponse : v) { + cardResponse.setResult(result); + cardResponseRepository.save(cardResponse); + } + }); + } + + /** + * Calculates the average time of the given List of CardResponseModel. + * + * @param cardResponses List of CardResponseModel. + * @return Time with the average time of the given List of CardResponseModel. + */ + private Time calculateAvgTime(List cardResponses) { + long totalMilis = 0; + + for (CardResponseModel cardResponse : cardResponses) { + LocalTime resTim = cardResponse.getResponseTime().toLocalTime(); + LocalDateTime responseTime = LocalDateTime.of(LocalDate.now(), resTim); + + long sec = responseTime.getSecond(); + long min = responseTime.getMinute(); + + totalMilis += sec * 1000; + totalMilis += (min * 60) * 1000; + } + + long milisMean = totalMilis / cardResponses.size(); + + return new Time(milisMean); + } + + public TrainingSessionModel getTrainingSessionId() { + return trainingSession; + } + + public void setTrainingSessionId(TrainingSessionModel trainingSession) { + this.trainingSession = trainingSession; + } + + /** + * Saves a card response into database. + * The card must have been loaded from the database + * This method will add the card to the current session buffer if it's not + * correct. + * + * @param card The card that has been answered by the user + * @param correct The boolean that indicates if the card was answered correctly + * @return void + */ + public void saveCardResponse(CardModel card, boolean correct) { + LocalDateTime cardReciveTime = LocalDateTime.now(); + + Time responseTime = calculateTimeDifference(cardReciveTime, cardSendTime); + + // Si el cardResponse es correcto se guarda y se borra la carta de la lista de + // las posibilidades + CardResponseModel cardResponse = new CardResponseModel(); + + cardResponse.setCard(card); + cardResponse.setCorrect(correct); + cardResponse.setResponseTime(responseTime); + cardResponse.setResponseDate(java.sql.Date.valueOf(LocalDate.now())); + + // Si está mal la vuelves a meter para que la pregunte otra vez + if (!correct) { + cards.add(card); + } + resultResponseMap.get(card.getId()).add(cardResponse); + } + + /** + * Returns the next card of the study session and deletes it. + * When this method is called, the current time is stored as the time when the + * card was sent. + * This method will return null if there are no more cards to study. + * + * @param card The card that has been answered by the user + * @param correct The boolean that indicates if the card was answered correctly + * @return CardModel if the card was found + * of null if there are no more cards to study. + */ + public CardModel getNextCard() { + CardModel card = null; + + if (!cards.isEmpty()) { + card = cards.get(0); + cards.remove(card); + } + + cardSendTime = LocalDateTime.now(); + + return card; + } + + public boolean cardsRemaining() { + return !cards.isEmpty(); + } + + /** + * Returns the difference between two LocalDateTimes. + * + * @param card The card that has been answered by the user + * @param correct The boolean that indicates if the card was answered correctly + * @return CardModel if the card was found + * of null if there are no more cards to study. + */ + private Time calculateTimeDifference(LocalDateTime closestTime, LocalDateTime otherTime) { + long secDiff = SECONDS.between(otherTime, closestTime); + long minDiff = MINUTES.between(otherTime, closestTime); + + long milis = 0; + milis += secDiff * 1000; + milis += (minDiff * 60) * 1000; + + // - 1h bc Time adds an hour depending on your GTM + milis -= 3600000; + + return new Time(milis); + } + + public TrainingSessionModel getCurrentTrainingSession() { + return this.trainingSession; + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/gamification/SessionManager.java b/src/main/java/eus/blankcard/decklearn/gamification/SessionManager.java new file mode 100644 index 0000000..cfd36e7 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/gamification/SessionManager.java @@ -0,0 +1,53 @@ +package eus.blankcard.decklearn.gamification; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import eus.blankcard.decklearn.models.TrainingSessionModel; + +@Component +public class SessionManager { + + private Map sessionRelation; + + // This is used to inject prototypeScope components into singleton components + // https://www.baeldung.com/spring-inject-prototype-bean-into-singleton#javax_api + @Autowired + private ObjectFactory sessionCardManagerFactory; + + public SessionManager() { + sessionRelation = new HashMap<>(); + } + + public SessionCardManager addSession(String username, TrainingSessionModel trainingSession) { + + SessionCardManager sessionCardManager = sessionCardManagerFactory.getObject(); + sessionCardManager.initSessionCardManager(trainingSession); + + sessionRelation.put(username, sessionCardManager); + + return sessionCardManager; + } + + public void removeSession(String username) { + try { + sessionRelation.remove(username); + } catch (Exception e) { + } + } + + public SessionCardManager getSession(String username) { + SessionCardManager sessionCardManager = null; + + try { + sessionCardManager = sessionRelation.get(username); + } catch (Exception e) { + } + + return sessionCardManager; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/ResultsModel.java b/src/main/java/eus/blankcard/decklearn/models/ResultsModel.java new file mode 100644 index 0000000..55cf915 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/ResultsModel.java @@ -0,0 +1,102 @@ +package eus.blankcard.decklearn.models; + +import java.sql.Time; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import eus.blankcard.decklearn.models.card.CardModel; +import eus.blankcard.decklearn.models.card.CardResponseModel; + +@Entity +@Table( name = "results" ) +public class ResultsModel { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column( name = "results_id") + private Integer id; + + @ManyToOne + @JoinColumn(name = "training_session_id") + private TrainingSessionModel trainingSession; + + + @ManyToOne + @JoinColumn(name = "card_id") + private CardModel card; + + @OneToMany(mappedBy = "result", cascade = CascadeType.ALL) + List cardResponses; @Column( name = "box_number") + private int boxNumber; + + @Column( name = "error_count") + private int errorCount; + + @Column( name = "avg_response_time") + private Time avgTime; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public TrainingSessionModel getTrainingSession() { + return trainingSession; + } + + public void setTrainingSession(TrainingSessionModel trainingSession) { + this.trainingSession = trainingSession; + } + + public List getCardResponses() { + return cardResponses; + } + + public void setCardResponses(List cardResponses) { + this.cardResponses = cardResponses; + } + + + public int getBoxNumber() { + return boxNumber; + } public void setBoxNumber(int boxNumber) { + this.boxNumber = boxNumber; + } + + public int getErrorCount() { + return errorCount; + } + + public void setErrorCount(int errorCount) { + this.errorCount = errorCount; + } + + public Time getAvgTime() { + return avgTime; + } + + public void setAvgTime(Time avgTime) { + this.avgTime = avgTime; + } + + public CardModel getCard() { + return card; + } + + public void setCard(CardModel card) { + this.card = card; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/TrainingModel.java b/src/main/java/eus/blankcard/decklearn/models/TrainingModel.java new file mode 100644 index 0000000..192793d --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/TrainingModel.java @@ -0,0 +1,87 @@ +package eus.blankcard.decklearn.models; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; + +@Entity +@Table( name = "training") +public class TrainingModel { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column( name = "training_id" ) + private Integer id; + + @ManyToOne + @JoinColumn(name="user_id") + private UserModel user; + + @ManyToOne + @JoinColumn(name="deck_id") + private DeckModel deck; + + @OneToMany(mappedBy = "training", cascade = CascadeType.ALL) + List trainingSessions = new ArrayList<>(); + + @Column( name = "training_date") + private Timestamp trainingDate; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public UserModel getUser() { + return user; + } + + public void setUser(UserModel user) { + this.user = user; + } + + public DeckModel getDeck() { + return deck; + } + + public void setDeck(DeckModel deck) { + this.deck = deck; + } + + public List getTrainingSessions() { + return trainingSessions; + } + + public void setTrainingSessions(List trainingSessions) { + this.trainingSessions = trainingSessions; + } + + public void addTrainingSession(TrainingSessionModel trainingSession) { + trainingSessions.add(trainingSession); + } + + public Timestamp getTrainingDate() { + return trainingDate; + } + + public void setTrainingDate(Timestamp trainingDate) { + this.trainingDate = trainingDate; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/TrainingSessionModel.java b/src/main/java/eus/blankcard/decklearn/models/TrainingSessionModel.java new file mode 100644 index 0000000..65b01aa --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/TrainingSessionModel.java @@ -0,0 +1,65 @@ +package eus.blankcard.decklearn.models; +import java.sql.Timestamp; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table( name = "training_session") +public class TrainingSessionModel { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column( name = "training_session_id") + private Integer id; + + @ManyToOne + @JoinColumn(name="training_id") + private TrainingModel training; + + @OneToMany(mappedBy = "trainingSession", cascade = CascadeType.ALL) + private List results; + + @Column(name = "training_session_date") + private Timestamp date; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public TrainingModel getTraining() { + return training; + } + + public void setTraining(TrainingModel training) { + this.training = training; + } + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } + public Timestamp getDate() { + return date; + } + + public void setDate(Timestamp date) { + this.date = date; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/card/CardModel.java b/src/main/java/eus/blankcard/decklearn/models/card/CardModel.java new file mode 100644 index 0000000..1458fc4 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/card/CardModel.java @@ -0,0 +1,101 @@ +package eus.blankcard.decklearn.models.card; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import eus.blankcard.decklearn.models.ResultsModel; +import eus.blankcard.decklearn.models.deck.DeckModel; + +@Entity +@Table(name = "card") +public class CardModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "card_id") + private Integer id; + + @ManyToOne + @JoinColumn(name = "deck_id") + private DeckModel deck; + + @OneToMany(mappedBy = "card", cascade = CascadeType.ALL) + List results = new ArrayList<>(); + + @OneToMany(mappedBy = "card", cascade = CascadeType.ALL) + List cardResponses; + + private String question; + + private String answer; + + @Column(name = "img_path") + private String imgPath; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public DeckModel getDeck() { + return deck; + } + + public void setDeck(DeckModel deck) { + this.deck = deck; + } + + public String getQuestion() { + return question; + } + + public void setQuestion(String question) { + this.question = question; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + + public String getImgPath() { + return imgPath; + } + + public void setImgPath(String imgPath) { + this.imgPath = imgPath; + } + + public List getCardResponses() { + return cardResponses; + } + + public void setCardResponses(List cardResponses) { + this.cardResponses = cardResponses; + } + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/card/CardResponseModel.java b/src/main/java/eus/blankcard/decklearn/models/card/CardResponseModel.java new file mode 100644 index 0000000..abb8ed3 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/card/CardResponseModel.java @@ -0,0 +1,89 @@ +package eus.blankcard.decklearn.models.card; + +import java.sql.Date; +import java.sql.Time; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import eus.blankcard.decklearn.models.ResultsModel; + +@Entity +@Table( name = "card_responses") +public class CardResponseModel { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column( name = "card_responses_id ") + private Integer id; + + @ManyToOne + @JoinColumn(name="results_id") + private ResultsModel result; + + @ManyToOne + @JoinColumn(name="card_id") + private CardModel card; + + boolean correct; + + @Column( name = "response_date") + Date responseDate; + + @Column( name = "response_time") + Time responseTime; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public ResultsModel getResult() { + return result; + } + + public void setResult(ResultsModel result) { + this.result = result; + } + + public CardModel getCard() { + return card; + } + + public void setCard(CardModel card) { + this.card = card; + } + + public boolean isCorrect() { + return correct; + } + + public void setCorrect(boolean correct) { + this.correct = correct; + } + + public Date getResponseDate() { + return responseDate; + } + + public void setResponseDate(Date responseDate) { + this.responseDate = responseDate; + } + + public Time getResponseTime() { + return responseTime; + } + + public void setResponseTime(Time responseTime) { + this.responseTime = responseTime; + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/models/deck/DeckModel.java b/src/main/java/eus/blankcard/decklearn/models/deck/DeckModel.java new file mode 100644 index 0000000..bc0233b --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/deck/DeckModel.java @@ -0,0 +1,141 @@ +package eus.blankcard.decklearn.models.deck; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import eus.blankcard.decklearn.models.TrainingModel; +import eus.blankcard.decklearn.models.card.CardModel; +import eus.blankcard.decklearn.models.user.UserModel; + +@Entity +@Table(name = "deck") +public class DeckModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "deck_id") + private Integer id; + + @ManyToOne + @JoinColumn(name = "creator_id") + private UserModel creator; + + @OneToMany(mappedBy = "deck", cascade = CascadeType.ALL) + List cards = new ArrayList<>(); + + @ManyToMany + @JoinTable(name = "type_relation", + joinColumns = { @JoinColumn(name = "deck_id")}, + inverseJoinColumns = { @JoinColumn(name = "deck_type_id")}) + private List types = new ArrayList<>(); + + @OneToMany(mappedBy = "deck", cascade = CascadeType.ALL) + List trainings = new ArrayList<>(); + + @ManyToMany(mappedBy = "savedDecks") + private List savers = new ArrayList<>(); + + private String title; + + private String description; + + @Column(name = "img_path") + private String imgPath; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public UserModel getCreator() { + return creator; + } + + public void setCreator(UserModel creator) { + this.creator = creator; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getImgPath() { + return imgPath; + } + + public void setImgPath(String imgPath) { + this.imgPath = imgPath; + } + + public List getTypes() { + return types; + } + + public void setTypes(List types) { + this.types = types; + } + + public void addType(DeckTypeModel type) { + this.types.add(type); + } + + public List getSavers() { + return savers; + } + + public void setSavers(List savers) { + this.savers = savers; + } + + public List getCards() { + return cards; + } + + public void setCards(List cards) { + this.cards = cards; + } + + public void addCard(CardModel card) { + this.cards.add(card); + } + + public List getTrainings() { + return trainings; + } + + public void setTrainings(List trainings) { + this.trainings = trainings; + } + + public void removeDeckType(DeckTypeModel type) { + this.types.remove(type); + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/deck/DeckTypeModel.java b/src/main/java/eus/blankcard/decklearn/models/deck/DeckTypeModel.java new file mode 100644 index 0000000..bbb5047 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/deck/DeckTypeModel.java @@ -0,0 +1,55 @@ +package eus.blankcard.decklearn.models.deck; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "deck_type") +public class DeckTypeModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "deck_type_id") + private Integer id; + + private String description; + + @ManyToMany(mappedBy = "types") + private List decks = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getDecks() { + return decks; + } + + public void setDecks(List decks) { + this.decks = decks; + } + + public void addDeck(DeckModel deck) { + this.decks.add(deck); + } +} diff --git a/src/main/java/eus/blankcard/decklearn/models/user/UserModel.java b/src/main/java/eus/blankcard/decklearn/models/user/UserModel.java new file mode 100644 index 0000000..8d25160 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/models/user/UserModel.java @@ -0,0 +1,240 @@ +package eus.blankcard.decklearn.models.user; + +import java.sql.Date; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import eus.blankcard.decklearn.models.TrainingModel; +import eus.blankcard.decklearn.models.deck.DeckModel; + +@Entity +@Table(name = "user") +public class UserModel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Integer id; + + private String username; + + @Column(name = "first_name") + private String name; + + @Column(name = "second_name") + private String surname; + + private String password; + + private String email; + + @Column(name = "postal_code") + private String postalCode; + + private String country; + + @Column(name = "birth_date") + private Date birthDate; + + @Column(name = "img_path") + private String imgPath; + + @OneToMany(mappedBy = "creator", cascade = CascadeType.ALL) + Set decks = new HashSet<>(); + + @ManyToMany + @JoinTable(name = "saved_deck", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { + @JoinColumn(name = "deck_id") }) + private Set savedDecks = new HashSet<>(); + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + Set trainings = new HashSet<>(); + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "followed", joinColumns = { @JoinColumn(name = "followed_id") }, inverseJoinColumns = { + @JoinColumn(name = "follower_id") }) + private Set followers = new HashSet<>(); + + @ManyToMany(mappedBy = "followers", fetch = FetchType.LAZY) + private Set followed = new HashSet<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getImgPath() { + return imgPath; + } + + public void setImgPath(String imgPath) { + this.imgPath = imgPath; + } + + public Set getFollowers() { + return followers; + } + + public Date getBirthDate() { + return birthDate; + } + + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + + public Set getDecks() { + return decks; + } + + public void setDecks(Set decks) { + this.decks = decks; + } + + public Set getTrainings() { + return trainings; + } + + public void setTrainings(Set trainings) { + this.trainings = trainings; + } + + public void setFollowers(Set followers) { + this.followers = followers; + } + + public Set getFollowed() { + return followed; + } + + public void setFollowed(Set followed) { + this.followed = followed; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UserModel other = (UserModel) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + + public Set getSavedDecks() { + return savedDecks; + } + + public void setSavedDecks(Set savedDecks) { + this.savedDecks = savedDecks; + } + + public void addFollower(UserModel user) { + followers.add(user); + } + + public void addFollowed(UserModel user) { + followed.add(user); + } + + public void removeFollowed(UserModel user) { + followed.remove(user); + } + + public void removeFollower(UserModel user) { + followers.remove(user); + } +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/CardRepository.java b/src/main/java/eus/blankcard/decklearn/repository/CardRepository.java new file mode 100644 index 0000000..5ddaf6d --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/CardRepository.java @@ -0,0 +1,9 @@ +package eus.blankcard.decklearn.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.card.CardModel; + +public interface CardRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/repository/CardResponseRepository.java b/src/main/java/eus/blankcard/decklearn/repository/CardResponseRepository.java new file mode 100644 index 0000000..903d842 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/CardResponseRepository.java @@ -0,0 +1,9 @@ +package eus.blankcard.decklearn.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.card.CardResponseModel; + +public interface CardResponseRepository extends JpaRepository { + +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/DeckTypeRepository.java b/src/main/java/eus/blankcard/decklearn/repository/DeckTypeRepository.java new file mode 100644 index 0000000..448f559 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/DeckTypeRepository.java @@ -0,0 +1,10 @@ +package eus.blankcard.decklearn.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.deck.DeckTypeModel; + +public interface DeckTypeRepository extends JpaRepository { + DeckTypeModel findByDescription(String description); + +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/ResultsRepository.java b/src/main/java/eus/blankcard/decklearn/repository/ResultsRepository.java new file mode 100644 index 0000000..81179fd --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/ResultsRepository.java @@ -0,0 +1,9 @@ +package eus.blankcard.decklearn.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.ResultsModel; + +public interface ResultsRepository extends JpaRepository { + +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/TrainingRepository.java b/src/main/java/eus/blankcard/decklearn/repository/TrainingRepository.java new file mode 100644 index 0000000..6470d53 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/TrainingRepository.java @@ -0,0 +1,12 @@ +package eus.blankcard.decklearn.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import eus.blankcard.decklearn.models.TrainingModel; + +public interface TrainingRepository extends JpaRepository { + + @Query(value="SELECT * FROM training t WHERE t.user_id = ?1 AND t.deck_id = ?2", nativeQuery = true) + TrainingModel findByUserIdInAndDeckId(Integer userId, Integer deckId); +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/deck/DeckRepository.java b/src/main/java/eus/blankcard/decklearn/repository/deck/DeckRepository.java new file mode 100644 index 0000000..7a92464 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/deck/DeckRepository.java @@ -0,0 +1,13 @@ +package eus.blankcard.decklearn.repository.deck; + +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.deck.DeckModel; + +public interface DeckRepository extends JpaRepository { + + List findByTitleContaining(String title, Pageable limit); +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/trainingSession/TrainingSessionRepository.java b/src/main/java/eus/blankcard/decklearn/repository/trainingSession/TrainingSessionRepository.java new file mode 100644 index 0000000..c2ade01 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/trainingSession/TrainingSessionRepository.java @@ -0,0 +1,8 @@ +package eus.blankcard.decklearn.repository.trainingsession; + +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.TrainingSessionModel; + +public interface TrainingSessionRepository extends JpaRepository { +} diff --git a/src/main/java/eus/blankcard/decklearn/repository/user/UserRepository.java b/src/main/java/eus/blankcard/decklearn/repository/user/UserRepository.java new file mode 100644 index 0000000..525a40d --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/repository/user/UserRepository.java @@ -0,0 +1,10 @@ +package eus.blankcard.decklearn.repository.user; + +import org.springframework.data.jpa.repository.JpaRepository; + +import eus.blankcard.decklearn.models.user.UserModel; + +public interface UserRepository extends JpaRepository { + + UserModel findByUsername(String username); +} diff --git a/src/main/java/eus/blankcard/decklearn/security/FailureHandler.java b/src/main/java/eus/blankcard/decklearn/security/FailureHandler.java new file mode 100644 index 0000000..59f6c2e --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/security/FailureHandler.java @@ -0,0 +1,34 @@ +package eus.blankcard.decklearn.security; + +import java.io.IOException; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +@Component +public class FailureHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + Map data = new HashMap<>(); + data.put( + "timestamp", + Calendar.getInstance().getTime()); + data.put( + "error", + exception.getMessage()); + + response.sendRedirect("/login?handler=fail"); + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/security/SecurityConfig.java b/src/main/java/eus/blankcard/decklearn/security/SecurityConfig.java new file mode 100644 index 0000000..1895248 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/security/SecurityConfig.java @@ -0,0 +1,72 @@ +package eus.blankcard.decklearn.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import eus.blankcard.decklearn.service.UserService; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + SuccessHandler successHandler; + + @Autowired + FailureHandler failureHandler; + + @Bean + public UserDetailsService userDetailsService() { + return new UserService(); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + + authProvider.setUserDetailsService(userDetailsService()); + authProvider.setPasswordEncoder(passwordEncoder()); + + return authProvider; + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authenticationProvider()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + String[] staticResources = { + "/styles/**", + "/images/**", + "/font/**", + "/js/**", + "/register", + "/login**" + }; + + http.authorizeRequests() + .antMatchers(staticResources).permitAll() + .anyRequest().authenticated() + .and() + .formLogin().loginPage("/login").successForwardUrl("/home") + .successHandler(successHandler).permitAll() + .failureHandler(failureHandler).permitAll() + .and() + .logout().permitAll(); + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/security/SuccessHandler.java b/src/main/java/eus/blankcard/decklearn/security/SuccessHandler.java new file mode 100644 index 0000000..0ad665a --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/security/SuccessHandler.java @@ -0,0 +1,38 @@ +package eus.blankcard.decklearn.security; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Component +public class SuccessHandler implements AuthenticationSuccessHandler { + + @Autowired + UserRepository userRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + String username = authentication.getName(); + + UserModel user = userRepository.findByUsername(username); + + HttpSession session = request.getSession(); + session.setAttribute("userImg", user.getImgPath()); + + response.sendRedirect("/home"); + } + +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/service/UserService.java b/src/main/java/eus/blankcard/decklearn/service/UserService.java new file mode 100644 index 0000000..234ca1b --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/service/UserService.java @@ -0,0 +1,37 @@ +package eus.blankcard.decklearn.service; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Service +public class UserService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + UserModel user = userRepository.findByUsername(username); + + if (user == null) { + throw new UsernameNotFoundException("User " + username + " not found."); + } + + List roles = new ArrayList<>(); + roles.add(new SimpleGrantedAuthority("USER")); + + return new User(user.getUsername(), user.getPassword(), roles); + } +} \ No newline at end of file diff --git a/src/main/java/eus/blankcard/decklearn/util/DeckCreationUtils.java b/src/main/java/eus/blankcard/decklearn/util/DeckCreationUtils.java new file mode 100644 index 0000000..a16773e --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/util/DeckCreationUtils.java @@ -0,0 +1,86 @@ +package eus.blankcard.decklearn.util; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import eus.blankcard.decklearn.models.card.CardModel; +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.deck.DeckTypeModel; +import eus.blankcard.decklearn.repository.CardRepository; +import eus.blankcard.decklearn.repository.DeckTypeRepository; +import eus.blankcard.decklearn.repository.deck.DeckRepository; + +@Component +public class DeckCreationUtils { + + @Autowired + DeckRepository deckRepository; + + @Autowired + CardRepository cardRepository; + + @Autowired + DeckTypeRepository deckTypeRepository; + + public void saveCard(String question, String answer, DeckModel deck) { + CardModel card = new CardModel(); + card.setQuestion(question); + card.setAnswer(answer); + card.setDeck(deck); + card = cardRepository.save(card); + + deck.addCard(card); + deckRepository.save(deck); + } + + public void saveDeckType(String description, DeckModel deck) { + DeckTypeModel type; + + type = deckTypeRepository.findByDescription(description); + + if (type == null) { + type = new DeckTypeModel(); + type.setDescription(description); + type.addDeck(deck); + } else { + type.addDeck(deck); + } + + deck.addType(type); + deckTypeRepository.save(type); + deckRepository.save(deck); + } + + public String checkAction(HttpServletRequest req, HttpServletResponse res, DeckModel deck, String action) { + String redirectUrl = "redirect:/create/deck/" + deck.getId(); + + switch (action) { + case "Save Card": + String question = req.getParameter("question"); + String answer = req.getParameter("answer"); + + saveCard(question, answer, deck); + break; + case "Add Type": + String description = req.getParameter("type"); + + saveDeckType(description, deck); + break; + case "Save Deck": + deck = deckRepository.save(deck); + + redirectUrl = "redirect:/deck/" + deck.getId(); + break; + + default: + redirectUrl = "redirect:/error"; + break; + } + + return redirectUrl; + } + +} diff --git a/src/main/java/eus/blankcard/decklearn/util/RegisterUtils.java b/src/main/java/eus/blankcard/decklearn/util/RegisterUtils.java new file mode 100644 index 0000000..87d4603 --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/util/RegisterUtils.java @@ -0,0 +1,27 @@ +package eus.blankcard.decklearn.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import eus.blankcard.decklearn.models.user.UserModel; +import eus.blankcard.decklearn.repository.user.UserRepository; + +@Component +public class RegisterUtils { + + @Autowired + private UserRepository userRepository; + + public void setDefaultFollower(UserModel user) { + + UserModel officialAccount = userRepository.findByUsername("DeckLearnOfficial"); + + if(officialAccount != null) { + user.addFollowed(officialAccount); + officialAccount.addFollower(user); + + userRepository.save(user); + userRepository.save(officialAccount); + } + } +} diff --git a/src/main/java/eus/blankcard/decklearn/util/StatsCalculator.java b/src/main/java/eus/blankcard/decklearn/util/StatsCalculator.java new file mode 100644 index 0000000..eb74e1e --- /dev/null +++ b/src/main/java/eus/blankcard/decklearn/util/StatsCalculator.java @@ -0,0 +1,238 @@ +package eus.blankcard.decklearn.util; + +import java.sql.Time; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Component; + +import eus.blankcard.decklearn.models.ResultsModel; +import eus.blankcard.decklearn.models.TrainingModel; +import eus.blankcard.decklearn.models.TrainingSessionModel; +import eus.blankcard.decklearn.models.card.CardResponseModel; +import eus.blankcard.decklearn.models.deck.DeckModel; +import eus.blankcard.decklearn.models.user.UserModel; + +@Component +public class StatsCalculator { + + public Time getTotalStudyTime(TrainingSessionModel trainingSession) { + List results = trainingSession.getResults(); + + long totalMilis = 0; + + for (ResultsModel result : results) { + for (CardResponseModel cardResponse : result.getCardResponses()) { + LocalTime resTim = cardResponse.getResponseTime().toLocalTime(); + LocalDateTime responseTime = LocalDateTime.of(LocalDate.now(), resTim); + + long sec = responseTime.getSecond(); + long min = responseTime.getMinute(); + + totalMilis += sec * 1000; + totalMilis += (min * 60) * 1000; + } + } + + return new Time(totalMilis); + } + + public Time getAvgResponseTime(TrainingSessionModel trainingSession) { + List cardResponses = new ArrayList<>(); + trainingSession.getResults().forEach(r -> r.getCardResponses().forEach(cr -> cardResponses.add(cr))); + + long totalMilis = 0; + long milisMean = 0; + + for (CardResponseModel cardResponse : cardResponses) { + LocalTime resTim = cardResponse.getResponseTime().toLocalTime(); + LocalDateTime responseTime = LocalDateTime.of(LocalDate.now(), resTim); + + long sec = responseTime.getSecond(); + long min = responseTime.getMinute(); + + totalMilis += sec * 1000; + totalMilis += (min * 60) * 1000; + } + + if (!cardResponses.isEmpty()) { + milisMean = totalMilis / cardResponses.size(); + } + + return new Time(milisMean); + } + + public int getPassRatio(TrainingSessionModel trainingSession) { + List results = trainingSession.getResults(); + + int passRatio = 0; + int cardNum = results.size(); + int errorCount = 0; + + for (ResultsModel result : results) { + if (result.getErrorCount() > 1) { + errorCount++; + } + } + + if (errorCount >= cardNum) { + passRatio = 0; + } else { + int correctCards = cardNum - errorCount; + + passRatio = (correctCards * 100) / cardNum; + } + + return passRatio; + } + + public int getAveragePassRatio(UserModel user) { + int passRatio = 0; + int sessionNumber = 0; + + for (TrainingModel training : user.getTrainings()) { + for (TrainingSessionModel trainingsession : training.getTrainingSessions()) { + passRatio += getPassRatio(trainingsession); + sessionNumber++; + } + } + + if (sessionNumber != 0) { + passRatio = passRatio / sessionNumber; + } + + return passRatio; + } + + public float getGradeChange(TrainingSessionModel trainingSession, int currentPassRatio) { + List traininSessions = trainingSession.getTraining().getTrainingSessions(); + float gradeChange = 0; + + if (traininSessions.size() <= 1) { + gradeChange = currentPassRatio; + } else { + // Get the previous to the current one + TrainingSessionModel prevTraining = traininSessions.get(traininSessions.size() - 2); + + float prevPassRatio = getPassRatio(prevTraining); + + if (prevPassRatio == 0) { + gradeChange = currentPassRatio; + } else { + gradeChange = ((currentPassRatio / prevPassRatio) - 1) * 100; + } + } + + return gradeChange; + } + + public Time getAvgResponseTime(UserModel user) { + List