diff --git a/README.md b/README.md index fd53a788..5b026ea0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Wikidata Infinite Quest 🌐 [wikiquest.sytes.net](http://wikiquest.sytes.net/) +⚠️ If you entered the site time ago it requires log out and log in one time to remove errors. ## 👨‍💻 Contributors: | Contributor | Contact | diff --git a/docs/images/05_level1.png b/docs/images/05_level1.png index fd9b6c69..9ade36da 100644 Binary files a/docs/images/05_level1.png and b/docs/images/05_level1.png differ diff --git a/docs/images/05_level2_gatewayService.png b/docs/images/05_level2_gatewayService.png index e865e18e..e29cdc20 100644 Binary files a/docs/images/05_level2_gatewayService.png and b/docs/images/05_level2_gatewayService.png differ diff --git a/docs/images/05_level2_multiplayerService.png b/docs/images/05_level2_multiplayerService.png index bbd88589..343a521a 100644 Binary files a/docs/images/05_level2_multiplayerService.png and b/docs/images/05_level2_multiplayerService.png differ diff --git a/docs/images/05_level2_questionGenerationService.png b/docs/images/05_level2_questionGenerationService.png index bce9c874..9feba249 100644 Binary files a/docs/images/05_level2_questionGenerationService.png and b/docs/images/05_level2_questionGenerationService.png differ diff --git a/docs/images/05_level2_userService.png b/docs/images/05_level2_userService.png index b29b1c20..b404dd0b 100644 Binary files a/docs/images/05_level2_userService.png and b/docs/images/05_level2_userService.png differ diff --git a/docs/images/05_level2_webApp.png b/docs/images/05_level2_webApp.png index 85f00bb4..26fff282 100644 Binary files a/docs/images/05_level2_webApp.png and b/docs/images/05_level2_webApp.png differ diff --git a/docs/images/05_level3_routesQuestionGenerationService.png b/docs/images/05_level3_routesQuestionGenerationService.png index 87775bfc..12505e1c 100644 Binary files a/docs/images/05_level3_routesQuestionGenerationService.png and b/docs/images/05_level3_routesQuestionGenerationService.png differ diff --git a/docs/images/05_level3_routesUserService.png b/docs/images/05_level3_routesUserService.png index 57b9827e..255a518b 100644 Binary files a/docs/images/05_level3_routesUserService.png and b/docs/images/05_level3_routesUserService.png differ diff --git a/docs/images/05_level3_servicesUserService.png b/docs/images/05_level3_servicesUserService.png index 725e83c5..1e4329de 100644 Binary files a/docs/images/05_level3_servicesUserService.png and b/docs/images/05_level3_servicesUserService.png differ diff --git a/docs/images/05_level3_srcWebApp.png b/docs/images/05_level3_srcWebApp.png index 673378b1..f340e3c8 100644 Binary files a/docs/images/05_level3_srcWebApp.png and b/docs/images/05_level3_srcWebApp.png differ diff --git a/docs/images/06_create_a_group_seq.svg b/docs/images/06_create_a_group_seq.svg index 2036f57a..f1f4fb9e 100644 --- a/docs/images/06_create_a_group_seq.svg +++ b/docs/images/06_create_a_group_seq.svg @@ -1 +1 @@ -MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged inloop[Create a group]click GROUPS navbar buttonaxios.get('/user/group/list')axios.get('/user/group/list')router.get('/user/group/list')JSON responseshow groups list and group creation forminsert a group name and click CREATE buttonaxios.post('/group/add')axios.post('/group/add')router.post('/user/group/add')JSON responseupdate the groups view \ No newline at end of file +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged inloop[Create a group]click GROUPS navbar buttonaxios.get('/user/group')axios.get('/user/group')router.get('/user/group')JSON responseshow groups list and group creation forminsert a group name and click CREATE buttonaxios.post('/group')axios.post('/user/group')router.post('/user/group')JSON responseupdate the groups list view \ No newline at end of file diff --git a/docs/images/06_exit_a_group_seq.svg b/docs/images/06_exit_a_group_seq.svg new file mode 100644 index 00000000..6210fb14 --- /dev/null +++ b/docs/images/06_exit_a_group_seq.svg @@ -0,0 +1 @@ +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged in and in the group list viewUser must belong to a groupalt[the user created the group][the user just belongs to the group]search group in list and click DELETE buttonsearch group in list and click EXIT IT! buttonaxios.put(`/group/:name/exit`, { username })axios.post(`/group/:name/exit`, { username })router.post('/group/:name/exit')JSON responseupdate the groups list view \ No newline at end of file diff --git a/docs/images/06_group_details_seq.svg b/docs/images/06_group_details_seq.svg index 631ac1f2..81020981 100644 --- a/docs/images/06_group_details_seq.svg +++ b/docs/images/06_group_details_seq.svg @@ -1 +1 @@ -MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged in and in the group list viewShould create a groupalt[There is a group in the list][No group exists]search group in list and click SEE MEMBERS buttonaxios.get('/group/${groupName}')axios.get('/group/${groupName}')router.get('/user/group/:name/')JSON responseshow specific group details view \ No newline at end of file +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged in and in the group list viewShould create a groupalt[There is a group in the list][No group exists]search group in list and click SEE MEMBERS buttonaxios.get('/group/:groupName', {params:{username:username}})axios.get('/group/:groupName' {params:{username:username}})router.get('/user/group/:name')JSON responseshow specific group details view \ No newline at end of file diff --git a/docs/images/06_join_a_group_seq.svg b/docs/images/06_join_a_group_seq.svg index c0d74994..cbb6b6ec 100644 --- a/docs/images/06_join_a_group_seq.svg +++ b/docs/images/06_join_a_group_seq.svg @@ -1 +1 @@ -MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged in and in the group list viewShould create a group or wait till one has spacealt[A group is available to join][No group is available to join]search group in list and click JOIN IT! buttonaxios.post('/group/${name}/join')axios.post('/user/group/${name}/join')router.post('/user/group/:name/join')JSON responseupdating groups view changing button text to JOINED \ No newline at end of file +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged in and in the group list viewShould create a group or wait till one has spacealt[A group is available to join][No group is available to join]search group in list and click JOIN IT! buttonaxios.put('/group/:name')axios.post('/user/group/:name')router.post('/user/group/:name')JSON responseupdating groups view changing button text to JOINED \ No newline at end of file diff --git a/docs/images/06_login_seq.svg b/docs/images/06_login_seq.svg index 8b6752a1..2f0a9796 100644 --- a/docs/images/06_login_seq.svg +++ b/docs/images/06_login_seq.svg @@ -1 +1 @@ -MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserclick login link or play buttonredirect to login viewcomplete form and clickaxios.post('/login')axios.post('/login')router.post('/login')JSON responseshow homepage \ No newline at end of file +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserclick login link or play buttonredirect to login viewcomplete form and clickaxios.post('/login',{username,password})axios.post('/login',{username,password})router.post('/login')JSON responseshow homepage \ No newline at end of file diff --git a/docs/images/06_play_seq.svg b/docs/images/06_play_seq.svg index df25e1f0..70c511ad 100644 --- a/docs/images/06_play_seq.svg +++ b/docs/images/06_play_seq.svg @@ -1 +1 @@ -MariaDBUsersWikidataMongoDBQuestionsGateway ServiceWebappUserMariaDBUsersWikidataMongoDBQuestionsGateway ServiceWebappUserUser must be logged inalt[questions>10]alt[last question]loop[search questions]click home PLAY button or navbar PLAY linkshow homepage viewclick a mode game and PLAY buttonsaxios.get('/questions')axios.get('/questions')router.get('/questions')generateQuestions()getRandomEntity(), getProperties(), convertUrlsToLabels()getQuestion()JSON responseJSON responsedeleteQuestionById()show question dataclick an answershow homepageaxios.post('/statistics/edit')axios.post('/user/statistics/edit')router.post('/user/statistics/edit')JSON responseaxios.post('/user/questionsRecord')axios.post('/user/questionsRecord')router.post('/user/questionsRecord')JSON response \ No newline at end of file +MariaDBUsersWikidataMongoDBQuestionsGateway ServiceWebappUserMariaDBUsersWikidataMongoDBQuestionsGateway ServiceWebappUserUser must be logged inalt[game mode]alt[last question]loop[search questions]click home PLAY button or navbar PLAY linkshow homepage viewclick a mode game and PLAY buttonsaxios.get('/questions/:lang')axios.get('/questions/:lang')router.get('/questions/:lang')axios.get('/questions/:lang/:category')router.get('/questions/:lang/:category')generateQuestions()getRandomEntity(), getProperties(), convertUrlsToLabels()getQuestion()JSON responseJSON responsedeleteQuestionById()show question dataclick an answershow homepageaxios.put('/statistics')axios.post('/user/statistics')router.post('/user/statistics')JSON responseaxios.post('/questionsRecord')axios.post('user/questionsRecord')router.post('/user/questionsRecord')JSON response \ No newline at end of file diff --git a/docs/images/06_profile_seq.svg b/docs/images/06_profile_seq.svg new file mode 100644 index 00000000..9f515995 --- /dev/null +++ b/docs/images/06_profile_seq.svg @@ -0,0 +1 @@ +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged inopt[Update your user avatar]click his name/photo in the navbaraxios.get('/profile', {params:{username:username}})axios.get('/user/profile', {params:{username:username}})router.get('/user/profile')JSON responseshow the users rankingclick any avatar of your choiceclick Confirm change buttonaxios.put('/profile/:username', {imageUrl:currentSelectedAvatar})axios.get('/user/profile/:username', {imageUrl:currentSelectedAvatar})router.get('/user/profile/:username')JSON responseupdate the navbar avatar and profile view \ No newline at end of file diff --git a/docs/images/06_ranking_seq.svg b/docs/images/06_ranking_seq.svg new file mode 100644 index 00000000..01b0b638 --- /dev/null +++ b/docs/images/06_ranking_seq.svg @@ -0,0 +1 @@ +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged inopt[See groups ranking]click RANKING navbar buttonaxios.get('/ranking')axios.get('/user/ranking')router.get('/user/ranking')JSON responseshow the users rankingclick ranking view GROUPS buttonaxios.get('/group/ranking')axios.get('/user/group/ranking')router.get('/user/group/ranking')JSON responseshow the group ranking \ No newline at end of file diff --git a/docs/images/06_register_seq.svg b/docs/images/06_register_seq.svg index 1092bed8..93d7eaea 100644 --- a/docs/images/06_register_seq.svg +++ b/docs/images/06_register_seq.svg @@ -1 +1 @@ -MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserclick register link in login viewredirect to register viewcomplete form and clickaxios.post('/user/add')axios.post('/user/add')router.post('/user/add')JSON responseshow homepage \ No newline at end of file +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserclick register link in login viewredirect to register viewcomplete form and clickaxios.post('/user', {username,password})axios.post('/user', {username,password})router.post('/user')JSON response with user infoaxios.post('/login',{username,password})axios.post('/login',{username,password})router.post('/login')JSON responseshow homepage \ No newline at end of file diff --git a/docs/images/06_statistics_seq.svg b/docs/images/06_statistics_seq.svg index 615f7e55..feec0b18 100644 --- a/docs/images/06_statistics_seq.svg +++ b/docs/images/06_statistics_seq.svg @@ -1 +1 @@ -MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged inopt[See other gamemode statistics]click STATISTICS navbar buttonaxios.get('/user/statistics/${username}')axios.get('/user/statistics/${username}')router.get('/user/statistics/:username')JSON responseshow user statisticsclick other mode statistics buttonshow the other mode statistics stored \ No newline at end of file +MariaDBUsersGateway ServiceWebappUserMariaDBUsersGateway ServiceWebappUserUser must be logged inalt[the user wants to see his statistics][the user wants to see a member of any of itsgroups' statistics]opt[See other gamemode statistics]click STATISTICS navbar buttonclick SEE STATISTICS in the group details viewaxios.get('/statistics/:username', {params:{loggedUser:username}})axios.get('/statistics/:username', {params:{loggedUser:username}})router.get('/user/statistics/:username')JSON responseshow user statisticsclick other mode statistics buttonshow the other mode statistics storedclick Show Questions Record buttonaxios.get('/questionsRecord/:username/:selectedMode', {params:{username, gameMode}})axios.get('/questionsRecord/:username/:selectedMode', {params:{username, gameMode}})router.get('/questionsRecord/:username/:selectedMode')JSON responseshow user questions record \ No newline at end of file diff --git a/docs/images/07_deployment_view.png b/docs/images/07_deployment_view.png new file mode 100644 index 00000000..d2b24b6c Binary files /dev/null and b/docs/images/07_deployment_view.png differ diff --git a/docs/images/07_development view.png b/docs/images/07_development view.png deleted file mode 100644 index c75f8364..00000000 Binary files a/docs/images/07_development view.png and /dev/null differ diff --git a/docs/images/08_domain_model_1.png b/docs/images/08_domain_model_1.png index 816240c3..adc8d316 100644 Binary files a/docs/images/08_domain_model_1.png and b/docs/images/08_domain_model_1.png differ diff --git a/docs/images/08_domain_model_2.png b/docs/images/08_domain_model_2.png index e27fabd1..5938ba90 100644 Binary files a/docs/images/08_domain_model_2.png and b/docs/images/08_domain_model_2.png differ diff --git a/docs/images/08_homepage.png b/docs/images/08_homepage.png index 97382487..106c4f81 100644 Binary files a/docs/images/08_homepage.png and b/docs/images/08_homepage.png differ diff --git a/docs/images/10_quality_tree.PNG b/docs/images/10_quality_tree.PNG new file mode 100644 index 00000000..c1461f65 Binary files /dev/null and b/docs/images/10_quality_tree.PNG differ diff --git a/docs/images/13_conf_recording_gatling_1.png b/docs/images/13_conf_recording_gatling_1.png new file mode 100644 index 00000000..e9abe251 Binary files /dev/null and b/docs/images/13_conf_recording_gatling_1.png differ diff --git a/docs/images/13_responses_per_second_2.png b/docs/images/13_responses_per_second_2.png new file mode 100644 index 00000000..d640e935 Binary files /dev/null and b/docs/images/13_responses_per_second_2.png differ diff --git a/docs/images/13_responses_per_seconds_1.png b/docs/images/13_responses_per_seconds_1.png new file mode 100644 index 00000000..ab4bee7a Binary files /dev/null and b/docs/images/13_responses_per_seconds_1.png differ diff --git a/docs/images/13_results_gatling_1.png b/docs/images/13_results_gatling_1.png new file mode 100644 index 00000000..4ad26ca0 Binary files /dev/null and b/docs/images/13_results_gatling_1.png differ diff --git a/docs/images/13_results_gatling_2.png b/docs/images/13_results_gatling_2.png new file mode 100644 index 00000000..29885e67 Binary files /dev/null and b/docs/images/13_results_gatling_2.png differ diff --git a/docs/images/13_statistics_1.png b/docs/images/13_statistics_1.png new file mode 100644 index 00000000..66629e78 Binary files /dev/null and b/docs/images/13_statistics_1.png differ diff --git a/docs/images/13_statistics_2.png b/docs/images/13_statistics_2.png new file mode 100644 index 00000000..a710fad3 Binary files /dev/null and b/docs/images/13_statistics_2.png differ diff --git a/docs/images/14_coverage.png b/docs/images/14_coverage.png new file mode 100644 index 00000000..59fd7060 Binary files /dev/null and b/docs/images/14_coverage.png differ diff --git a/docs/images/14_graphic_sonarcloud.png b/docs/images/14_graphic_sonarcloud.png new file mode 100644 index 00000000..81ba6ac7 Binary files /dev/null and b/docs/images/14_graphic_sonarcloud.png differ diff --git a/docs/images/15_monitoring_1.png b/docs/images/15_monitoring_1.png new file mode 100644 index 00000000..4f438157 Binary files /dev/null and b/docs/images/15_monitoring_1.png differ diff --git a/docs/images/15_monitoring_graphics.png b/docs/images/15_monitoring_graphics.png new file mode 100644 index 00000000..24bfd67f Binary files /dev/null and b/docs/images/15_monitoring_graphics.png differ diff --git a/docs/images/15_monitoring_metrics.png b/docs/images/15_monitoring_metrics.png new file mode 100644 index 00000000..f22df51e Binary files /dev/null and b/docs/images/15_monitoring_metrics.png differ diff --git a/docs/index.adoc b/docs/index.adoc index 76d53d68..4c789923 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -96,4 +96,14 @@ include::src/11_technical_risks.adoc[] // 12. Glossary include::src/12_glossary.adoc[] +<<<< +// 13. Appendix I: Load tests +include::src/13_appendix1_load_tests.adoc[] + +<<<< +// 14. Appendix I: Other tests +include::src/14_appendix2_testing.adoc[] +<<<< +// 15. Appendix I: Monitoring +include::src/15_appendix3_monitoring.adoc[] diff --git a/docs/src/01_introduction_and_goals.adoc b/docs/src/01_introduction_and_goals.adoc index 0db57d9e..4d90339d 100644 --- a/docs/src/01_introduction_and_goals.adoc +++ b/docs/src/01_introduction_and_goals.adoc @@ -22,7 +22,7 @@ One of the most relevant requirement is that the questions are generated from Wi To do the game we are going to develop a web application that will be available to enter from any device with internet connection. Regarding quality requirements, the goal is to achieve an optimal level, especially in terms of usability, - maintainability, efficiency, security, and testability. + maintainability, efficiency, and testability. The main stakeholders in the project are several. Firstly, Professor José Emilio Labra, who teaches the subject. Also, the students and members of the HappySoftware development team. Lastly, potential users of @@ -92,7 +92,7 @@ A table with quality goals and concrete scenarios, ordered by priorities |Quality goal|Concrete scenario |Usability|It must be easy to use the app, thus everybody could use it |Availability|The system should be available the most time possible -|Security|The user's data must remain confidential. +|Testability|Functionalities must be covered with tests to ensure correct behavior |Performance|Using the system must be as smooth as possible. Especially, the question generation must be fast. |=== diff --git a/docs/src/04_solution_strategy.adoc b/docs/src/04_solution_strategy.adoc index abe90be0..6100a318 100644 --- a/docs/src/04_solution_strategy.adoc +++ b/docs/src/04_solution_strategy.adoc @@ -39,6 +39,7 @@ To develop the app we will use the following technologies: * Docker Compose to deploy all the microservices * GitHub for version control * WikiData API to obtain question and answer information +* ExpressJS to build the backend We have considered the trade-offs that belong to each technology, such as SpringBoot or PHP for the backend of the app. However, JavaScript was the language that adapted better to our requirements due to the simplicity of the language and its @@ -105,14 +106,14 @@ Quality goals are explained in detail in point 10. |Quality goal| Decisions to achieve it. |Usability| We are going to use real users to test the app interface and improve it according to their feedback. |Availability| Docker Compose will be helpful to avoid problems with the deploy of the app. In addition we will use web hosting to expose it to the internet. -|Security| A login system with encrypted password storage will be used to protect the user data. +|Testability| We created unit and e2e (integration) test set to test the application |Performance| We will use the minimum required calls to the APIs to mantain the minimum time response, for example, with bulk requests. |=== === Relevant organizational decisions -Our framework will be based on working every week with two weekly meetings, one will be held during lab time in order to assign tasks and make minor decisions. -On the other hand, the weekend meeting will be intended for more thorough reviews as well as more significant decisions. +Our framework will be based on working every week with meetings when necessary, one will be held always during lab time in order to assign tasks and make minor decisions. +On the other hand, further meetings will be intended for more thorough reviews as well as more significant decisions. Each assigned task will be created as an Issue in GitHub to track the progress done. In addition, we are going to use GitHub Projects to organize the workflow of the team. To merge the code to the develop branch we are going to use Pull Requests in order to be approved by every person of the team. diff --git a/docs/src/05_building_block_view.adoc b/docs/src/05_building_block_view.adoc index e74a36b1..1842fe54 100644 --- a/docs/src/05_building_block_view.adoc +++ b/docs/src/05_building_block_view.adoc @@ -108,11 +108,11 @@ Motivation:: First decomposition of the system. Contained Building Blocks:: -* **Web App**: It is the main module of the application. -* **Gateway Service**: Handles the communication between the user service and question service modules with the web app service. -* **Question Generation Service**: Gets questions from Wikidata and handles their loading into the database. -* **User Service**: Handles the user management. -* **Multiplayer Service**: Handles the multiplayer management. +* **webapp**: It is the main module of the application. +* **gateway**: Handles the communication between the user service and question service modules with the web app service. Is is the API REST. +* **questions**: Gets questions from Wikidata and handles their loading into the database. +* **users**: Handles the user management. +* **multiplayer**: Handles the multiplayer management. * **MongoDB**: MongoDB database. * **MariaDB**: MaiaDB database. @@ -125,13 +125,13 @@ Other Important Interfaces:: **** Here you can specify the inner structure of (some) building blocks from level 1 as white boxes. **** -==== White Box Users Service +==== White Box users -image:05_level2_userService.png["Diagram White Box Users Service"] +image:05_level2_userService.png["Diagram White users"] Motivation:: -Decomposition of the User Service black box from level 1 system. +Decomposition of the users black box from level 1 system. Contained Building Blocks:: * **Routes**: Contains route handlers for the users. @@ -140,55 +140,55 @@ Contained Building Blocks:: Other Important Interfaces:: * **index**: Define the entry point of the User Service. -==== White Box Question Generation Service +==== White Box questions -image:05_level2_questionGenerationService.png["Diagram White Box Question Generation Service"] +image:05_level2_questionGenerationService.png["Diagram White Box questions"] Motivation:: -Decomposition of the Question Generation Service black box from level 1 system. +Decomposition of the questions black box from level 1 system. Contained Building Blocks:: * **Routes**: Contains route handlers for the questions. * **Services**: Contains data logic. Other Important Interfaces:: -* **index**: Define the entry point of the Question Generation Service. +* **index**: Define the entry point of the questions. * **utils**: Define auxiliar functions and questions structure. ==== White Box Web App -image:05_level2_webApp.png["Diagram White Box Web App"] +image:05_level2_webApp.png["Diagram White webapp"] Motivation:: -Decomposition of the Web App black box from level 1 system. +Decomposition of the webapp black box from level 1 system. Contained Building Blocks:: * **public**: Contains image and audio files. * **src**: Contains the components, pages and data of the front-end application. -==== White Box GatewayService Service +==== White Box gateway -image:05_level2_gatewayService.png["Diagram White Box GatewayService Service"] +image:05_level2_gatewayService.png["Diagram White Box gateway"] Motivation:: -Decomposition of the Gateway Service black box from level 1 system. +Decomposition of the gateway black box from level 1 system. Contained Building Blocks:: * **gateway-service**: Define the routes for handling the communication between the user service and question service modules with the web app service. - +* **prometheus**: Contains the configuration of grapfana and prometheus Other Important Interfaces:: * **monitoring**: Uses Grafana and Prometheus to monitor the application. -==== White Box Multiplayer Service +==== White Box multiplayer -image:05_level2_multiplayerService.png["Diagram White Box Multiplayer Service"] +image:05_level2_multiplayerService.png["Diagram White Box multiplayer"] Motivation:: -Decomposition of the Multiplayer Service black box from level 1 system. +Decomposition of the multiplayer black box from level 1 system. Contained Building Blocks:: * **index**: Handles the multiplayer management. @@ -199,68 +199,67 @@ Contained Building Blocks:: **** Here you can specify the inner structure of (some) building blocks from level 2 as white boxes. **** -==== White Box routes from User Service +==== White Box routes from users -image:05_level3_routesUserService.png["Diagram White Box routes from User Service"] +image:05_level3_routesUserService.png["Diagram White Box routes from users"] Motivation:: -Decomposition of the black box routes from User Service white box from level 2 system. +Decomposition of the black box routes from users white box from level 2 system. Contained Building Blocks:: -* **user-routes**: Contains route handlers for the register, ranking,groups management and statistics management. +* **user-routes**: Contains route handlers for the register, ranking, groups management, statistics management and questions record management. * **auth-routes**: Contains route handlers for the login. -==== White Box services from User Service +==== White Box services from users -image:05_level3_servicesUserService.png["Diagram White Box services from User Service"] +image:05_level3_servicesUserService.png["Diagram White Box services from users"] Motivation:: -Decomposition of the black box services from User Service white box from level 2 system. +Decomposition of the black box services from users white box from level 2 system. Contained Building Blocks:: * **user-model**: Define the User, Statistics and Group database schemas. -* **authVerifyMiddleWare**: Authentication Middleware. -==== White Box routes from Question Generation Service +==== White Box routes from questions -image:05_level3_routesQuestionGenerationService.png["Diagram White Box routes from Question Generation Service"] +image:05_level3_routesQuestionGenerationService.png["Diagram White Box routes from questions"] Motivation:: -Decomposition of the black box routes from Question Generation Service white box from level 2 system. +Decomposition of the black box routes from questions white box from level 2 system. Contained Building Blocks:: * **question-routes**: Contains route handlers for the questions management. -==== White Box services from Question Generation Service +==== White Box services from questions -image:05_level3_servicesQuestionGenerationService.png["Diagram White Box services from Question Generation Service"] +image:05_level3_servicesQuestionGenerationService.png["Diagram White Box services from questions"] Motivation:: -Decomposition of the black box routes from Question Generation Service white box from level 2 system. +Decomposition of the black box routes from questions white box from level 2 system. Contained Building Blocks:: * **question-data-model**: Define the Question database schema. * **question-data-service**: Responsible for managing questions in the database. * **wikidata-service**: Responsible for getting questions from Wikidata. -==== White Box src from Web App +==== White Box src from webapp -image:05_level3_srcWebApp.png["Diagram White Box src from Web App"] +image:05_level3_srcWebApp.png["Diagram White Box src from webapp"] Motivation:: -Decomposition of the black box src from Web App white box from level 2 system. +Decomposition of the black box src from webapp white box from level 2 system. Contained Building Blocks:: * **components**: Defines common elements in the pages like the nav-bar, footer, etc. * **pages**: Defines the different screens of the application. * **data**: It contains the data used by the pages. +* **App**: Main entry point for the application logic. Defines the application's theme and navbar routes. +* **index**: Initializes the application and renders the main component (App.js) to the DOM. -Other Important Interfaces:: -* **index**: Define the entry point of the Web app. diff --git a/docs/src/06_runtime_view.adoc b/docs/src/06_runtime_view.adoc index 5dc3f02e..453d1b9d 100644 --- a/docs/src/06_runtime_view.adoc +++ b/docs/src/06_runtime_view.adoc @@ -58,6 +58,11 @@ image:06_statistics_seq.svg["See User Statistics Sequence"] image:06_instructions_seq.svg["See Games Instrucions Sequence"] +=== See Users and Groups Ranking + +image:06_ranking_seq.svg["See Ranking Sequence"] + + === Groups ==== Group List and Creation @@ -67,6 +72,10 @@ image:06_create_a_group_seq.svg["Group Creation Sequence"] image:06_join_a_group_seq.svg["Group Joining Sequence"] +==== Group Exiting/Deletion + +image:06_exit_a_group_seq.svg["Group Exiting Sequence"] + ==== Group Details image:06_group_details_seq.svg["Group Details Sequence"] @@ -74,4 +83,9 @@ image:06_group_details_seq.svg["Group Details Sequence"] === Play Games -image:06_play_seq.svg["Play Games Sequence"] \ No newline at end of file +image:06_play_seq.svg["Play Games Sequence"] + + +=== See and edit your profile + +image:06_profile_seq.svg["See Profile Sequence"] \ No newline at end of file diff --git a/docs/src/07_deployment_view.adoc b/docs/src/07_deployment_view.adoc index ab3a82b9..550d708a 100644 --- a/docs/src/07_deployment_view.adoc +++ b/docs/src/07_deployment_view.adoc @@ -42,16 +42,18 @@ See https://docs.arc42.org/section-7/[Deployment View] in the arc42 documentatio **** -We have several services deployed in a single containter using Docker Compose, this eases the deployment. +We have several services deployed in a single virtual machine using containers and Docker Compose, this eases the deployment. This are the different container and their relations: - WebApp: The web page. Gets data from Gateway Service. - Gateway Service: data access interface for services. -- AuthService and UserService: manages authentication and login systems. -- MongoDB: persistance system used in the application. +- Users: manages authentication and statistics about users. +- MariaDB: persistance system used in the users data. +- MongoDB: persistance system used in the questions data. - Grafana and Prometeus: code monitoring systems. -- QuestionGenerationSystem: generates questions to use in the game. +- Questions: generates questions to use in the game. +- Multiplayer: permits users to play multiplayer games. We are going to use an Azure VM to deploy all this services. -image::07_development view["Deployment View"] +image:07_deployment_view.png["Deployment View"] diff --git a/docs/src/08_concepts.adoc b/docs/src/08_concepts.adoc index 444b085c..30ba6ef3 100644 --- a/docs/src/08_concepts.adoc +++ b/docs/src/08_concepts.adoc @@ -71,38 +71,51 @@ At the moment, the application follows this schema: image::08_domain_model_1.png["Current version of the domain model"] * **User**: it is the person that uses the application. There can be multiple Users at the same time using the app. -* **Contest**: The contest is the part that the User can see. It contains everything the user can do, such as play games o look for rankings and statistics. -* **Game**: The User can play different games, including _The Challenge_, _Wise Men Stack_ or even a multiplayer mode, which enables various Users to play together. Games consist of several questions to which users have to answer. Down below there is an schema that show the different game modes that will we available. -* **Question**: Each question has different answers but only one of them is correct. Answering correctly to the questions rewards users with _money_. +* **Contest**: The contest is the part that the User can see. It contains everything the user can do, such as play games, be part of groups or look for rankings and statistics. +* **Game**: The User can play different games, including _The Challenge_, _Wise Men Stack_ or even a multiplayer mode, which enables various Users to play together. Games consist of several questions to which users have to answer. Down below there is an schema that show the different game modes that are available. +* **Question**: Each question has different answers but only one of them is correct. Answering correctly to the questions rewards users with _points_. * **Statistics**: Each user has statistics that show different aspects of their profile, such as the time they invested on each game mode, correct and incorrect questions, etc. -* **Profile**: User's profile has data such as their username and the amount on _money_ they have earned. +* **Profile**: User's profile has data such as their username and the amount on _points_ they have earned. They can also choose a profile picture from between some given avatars. * **Group**: Users are able to join or create groups, that way the can get to a groups ranking. image::08_domain_model_2.png["Current version of the game modes"] +* **Wise Men Stack**: The player chooses a topic from the available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they move on to the next question. +* **Warm Question**: It consists of some topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly. +* **Discovering Cities**: The contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test. Time and number of questions are fixed. +* **The Challenge**: It is the quintessential game mode, as it allows you to customize the match to your liking. This game mode is tailored for those who wish to practice certain game formats before engaging in our various other game modes. Number of questions, time per question and category can be set. +* **Multiplayer**: Create a room and share the room code with other players to play. It also has a room chat. + + === User Experience (UX) -* https://arquisoft.github.io/wiq_es04a/#_technical_terms[**Frontend**]: the frontend of this application consists of a deployed web app which will be deployed. The user can register and log in with accounts already created on an intuitive page. They can also play different game modes and to consult their historial record, statistics, and even a ranking. +* https://arquisoft.github.io/wiq_es04a/#_technical_terms[**Frontend**]: the frontend of this application consists of a deployed web app which is deployed. The user can register or log in with accounts already created on an intuitive page. They can also play different game modes and to consult their historial record, statistics, and even some rankings. As it can be seen down below, the homepage is bright and appealing, which leads to a better user experience. Users can easily choose the game mode they prefer and play. image::08_homepage.png["Current homepage window"] -* https://arquisoft.github.io/wiq_es04a/#_technical_terms[**Internationalization**] The application will likely be available in various languages, including English as the main language. This would provide a better user experience as users could better tailor the application to their personal preferences. +* https://arquisoft.github.io/wiq_es04a/#_technical_terms[**Internationalization**] The application is available in various languages, including English as the main language. This would provide a better user experience as users could better tailor the application to their personal preferences. === Operation Concepts -* **Usability**: The application should be easy to use. For this reason, we had some people try our application. This way we can know its strengths and weaknesses and improve them. Usability affects User Experience as well, so it is an important aspect of the application. Up to this moment, usability testing has helped with the color palette chosen the application. +* **Usability**: We tried for the application to be easy to use. For this reason, we had some people try our application. This way we can know its strengths and weaknesses and improve them. Usability affects User Experience as well, so it is an important aspect of the application. Up to this moment, usability testing has helped with the color palette chosen the application. + +We have also taken into account certain aspects that could difficult a person to use our application properly. For instance, we have stablished tics and crosses as well as colours to know if questions are correct or not. For this reason, a daltonic person would get to know if they got the answer correct or not easily. + === Security -At the moment, security mechanisms are not the main focus of the application, but some decisions have already been made and will be implemented soon. +We have implemented some security in the application. We have blocked access to certain directions if you are not logged in. This way, we avoid external people to be able to access our application as it could lead to other security issues. +We have also stablished that passwords need to follow a certain security level. They need to be at least 8 characters longs and they must contain upper and lower letters, numbers and special characters. Also, passwords are stored encrypted. In case that the batabase is stolen, data would still be secure. === Architecture and Design Patterns * https://arquisoft.github.io/wiq_es04a/#_technical_terms[**Microservice**]: In this application there are some microservices such as the User Management, which involves signing up, logging in and everything related to the points and timing of the user. Microservices provide an easy way of creating a complex application composed by independent systems. Another important microservice is the questions generation system. It creates infinite questions related to varios topics. Thats to this, users can never get bored of the game, as questions do not repeat themselves. The webapp microservice includes everything related to the graphic interface. Users are able to communicate with the application thats to this service. +All of the architectural decisions that have taken place through the application creation are specified in the https://github.com/Arquisoft/wiq_es04a/wiki[repository Wiki section] === Development Concepts * **Testing**: Numerous use-cases are studied so as to provide a solid and easy-to-use application. There are unitary tests related to every functionality of the project, as well as e2e tests regarding the main game. + * https://arquisoft.github.io/wiq_es04a/#_acronyms[**CI/CD**]: The application is in continuous integration and deployment. Team members commit frequently into the repository where the project is stored. This makes it easier when assembling project parts involving collaboration from different team members. -image::08_mindmap_concepts.png["Initial version of cross-cutting concepts"] +image::08_mindmap_concepts.png["cross-cutting concepts"] diff --git a/docs/src/10_quality_requirements.adoc b/docs/src/10_quality_requirements.adoc index ac8accf6..623bd45b 100644 --- a/docs/src/10_quality_requirements.adoc +++ b/docs/src/10_quality_requirements.adoc @@ -5,10 +5,10 @@ ifndef::imagesdir[:imagesdir: ../images] The main quality goals are: -* **Usability**: The interface should be intuitive, with clear instructions and an accessible design. This will allow users of all abilities to navigate and use the application effortlessly. +* **Usability**: The interface should be intuitive, with clear instructions and an accessible design. This will allow users of all abilities to navigate and use the application effortlessly. It must mantain usability in mobile devices. * **Availability**: The system should aim for maximum availability, ensuring it is accessible around the clock. This will guarantee uninterrupted access most of the time, regardless of their time zone or schedule. -* **Security**: User data confidentiality must be upheld as a top priority, ensuring sensitive information remains secure and protected at all times. This commitment to privacy instills trust and confidence among users, fostering a safe and secure environment for their data. -* **Performance**: Efficiency is paramount in system usage, particularly in swift question generation, ensuring a seamless experience for users. Streamlining processes enhances overall usability, enabling swift and effortless interaction with the system. +* **Testability**: The code must be tested and should be easy to test (for instance with datatest-id to reference elements). The tests ease the implementation of new features because they make sure old functionality never stops working. +* **Performance**: Efficiency is a priority in system usage, particularly in quick question generation, ensuring a good experience for users. [role="arc42help"] @@ -34,7 +34,7 @@ See https://docs.arc42.org/section-10/[Quality Requirements] in the arc42 docume === Quality Tree -image::Quality_Tree.png["Quality Tree"] +image:10_quality_tree.PNG["Quality Tree"] [role="arc42help"] **** @@ -74,7 +74,7 @@ Change Scenario table: [options="header",cols="1,2"] |=== |Change Scenario|System Reaction -|Adding an additional login system to access the account not only through email but also through the username. |The system should be capable of adapting to provide this functionality without affecting the existing ones. +|Adding an additional login system to access the account not only through username but also through the email. |The system should be capable of adapting to provide this functionality without affecting the existing ones. The tests verify that the old login is not affected. |Adding a new game mode or functionality.|When adding a new feature, the application's usage methodology should not be distorted, ensuring it can still be used in the same manner. |Adding a new game language.|When adding a new game language, the system should continue to function smoothly. |=== diff --git a/docs/src/11_technical_risks.adoc b/docs/src/11_technical_risks.adoc index 20f6890c..c0eb3c5f 100644 --- a/docs/src/11_technical_risks.adoc +++ b/docs/src/11_technical_risks.adoc @@ -39,4 +39,9 @@ To assess the relevance level of the following risks, we will use number 1 to in | Technical Debt | Considerations | Low-quality code | The use of new technologies and languages can lead to poorly written or poorly designed code. To address this issue, we will use pull requests to ensure that the code is reviewed by multiple team members. | Deployment issues | Having not worked with Docker and other deployment tools before may cause problems when deploying the application. For this reason, we are trying to put our best into learning these new technologies. +| Dependency with Wikidata | It is a requirement so we need to depend on it. However, we have created a questions database so in case that Wikidata was not working, the application would continue to work for some time. +| Filtering questions and answers | Given the structure of Wikidata there are sometimes where questions and answers do not have the proper label we are looking for. Although we have stablished filters and different strategies, sometimes it fails. +| Changes in database model | Changing the model of a relational database makes it necessary to create a new database, losing all of the data we had before. +| Game duplication code | Due to our little knowledge about JavaScript, we have not found a way of optimizing code from different game modes. It would be necessary to search for another way of doing it, as this is not maintenable. +| Non-expiring session token | Once someone has logged in in the application, session token does not expire. That way session does not finish unless you explicitly log out. |=== diff --git a/docs/src/13_appendix1_load_tests.adoc b/docs/src/13_appendix1_load_tests.adoc new file mode 100644 index 00000000..9bd81aaa --- /dev/null +++ b/docs/src/13_appendix1_load_tests.adoc @@ -0,0 +1,55 @@ +ifndef::imagesdir[:imagesdir: ../images] + +[[section-load-tests]] +== Appendix I: Load tests + +We conducted load tests on our application using Gatling. This type of testing allows us to know how __strong__ our application is in relation with the amount of users that interact with it at the same time. Initially, we recorded the specific functionalities we intended to test, and then we configured the tests accordingly. Our primary focus was on testing the game component, as it constitutes the core aspect of our application. After recording the functionality to be tested, we increased the number of requests to 1,000 and established that these requests be made gradually, simulating a real-life scenario. + +image::13_conf_recording_gatling_1.png["Configuration for gatling recording 1"] + +After setting this, me executed the load tests, obtaining results that were not too bad. However, as it is shown in the next picture, more than 25% of the requests failed. This means that there is a possibility that the game fails when playing, which is not acceptable in an application of this kind. Even most of the requests have a response we will try to reduce the amount of failed requests. In addition, instant response of petitions has not the highest priority for us. It is more important that as many requests as possible and responded correctly. + +=== Test 1: 1000 users with poor question generation algorithm + +image::13_results_gatling_1.png["Results for gatling recording test 1"] + +After this load test, we tried to improve the question generation so as to avoid the failures mentioned above. +We tested our application again, obtaining new results. We believe it is important to mention that even if the settings where the same in both tests, the application had more game modalities and new functionalities, which may affect to the number of requests and the time needed for each one. + +=== Test 2: 1000 users with new question generation algorithm + +image::13_results_gatling_2.png["Results for gatling recording test 2"] + +As it can be seen in the picture above, the results have changed noticeably. From our point of view, there are two main aspects which seem remarkable. On one hand, the drastic decrease in the number of failed requests. The failed requests have decreased to 2% compared to 27% in the first test. This demonstrates that the changes made to the application have achieved their objective. +On the other hand, the overall increase in the time it takes to respond to a request catches the eye as well. Nevertheless, given that the difference in time is milliseconds and it is not a real-time critical application, we consider that the objective of these tests has been fulfilled. + +=== Users distribution along the simulation and response time distribution +We think that it is useful to compare some of the statistics that these tests have providad us with. For instance, seeing __Users distribution along the simulation__ graphic allows us to see if the stablished settings get to a gradual users interaction with the application. Also, the __Response time distribution__ graphic is very visual so as to see the average time requests take to respond. + +==== Test 1 +image::13_statistics_1.png["Some statistics for test 1"] + +==== Test 2 +image::13_statistics_2.png["Some statistics for test 2"] + + +=== Number of responses per second +Finally, we would like to compare another graph because we believe it illustrates the number of responses per second in a straightforward manner during the tests. This allows us to observe the percentage of failed requests each second. As mentioned earlier, our primary goal after the initial test was to reduce the number of failed requests, even if it meant slightly increasing the average response time for each request. + +==== Test 1 +image::13_responses_per_seconds_1.png["Responses per second 1"] + +==== Test 2 +image::13_responses_per_second_2.png["Responses per second 2"] + +As we can see, the second test shows a much more equilibrated graphic. Responses are distributed better in time and failures are a minimum percentage of the total responses. + +For these reasons, the load tests have motivated us to develop a more stable question generation algorithm. This reduces the likelihood of requests failing when users are interacting with our application. This ultimately leads to a better user experience, which is a crucial aspect of application development. + + + + + + + + diff --git a/docs/src/14_appendix2_testing.adoc b/docs/src/14_appendix2_testing.adoc new file mode 100644 index 00000000..6ed9e4ad --- /dev/null +++ b/docs/src/14_appendix2_testing.adoc @@ -0,0 +1,32 @@ +ifndef::imagesdir[:imagesdir: ../images] + +[[section-other-tests]] +== Appendix II: Other tests + +=== Unitary tests +We did unitary tests through the whole application. These tests were useful when getting to know if what we had just implemented worked properly or not. It is also a very esasy way of checking if you changed something without noticing. For example, if anything were modified, unitary tests where adapted to this new functionality. However, the changes made in the application may affect some parts that we were not expecting to be affected. This way we could be able to garantee that the application continued to work properly and to check if there were some parts that depended on others when they should not. For these reasons, it is very important to have as much as possible of our code covered. We have achieved a coverage percentage greater than 80%. + +==== SonarCloud +The picture below shows an overview done with SonarCloud to our repository. As it can be seen, all of the diferent services reach 80% or more, some of them reaaching almost 90%. The total coverage of our project is around 82%. However, it is important to remember that coverage is not only about numbers but about testing projects in a good way. + +image::14_coverage.png["Code coverage"] + +We would also like to mention that SonarCloud offers a graphic where risks in different part of the code are displayed. Bubbles in the top right side of the graphic means that the longer-term health may be at risk. Green bubbles at the bottom-left are best. Down below there is the graphic of our project, which shows all bubbles in green and most of them are in the left-bottom part. + +image::14_graphic_sonarcloud.png["Risks graphic"] + +=== Acceptance tests +Acceptance tests are also important. They do not focus on the functionality of the application but on the user experience. This way we were able to know easily if elements where rendered quickly. In addition, this tests let us measure how long it takes for an interaction with the application. +For this tests we used a MariaDB database created only for these tests. We used MariaDB because we needed to get information from users, which are https://github.com/Arquisoft/wiq_es04a/wiki/ADR-04-‐-Users-and-Groups-with-MariaDB[ stored in this type of relational database.] + +We focused in testing the different games available, as it is the core part of the application. It is important to mention that e2e tests can be executed two ways. The first way, using a graphic interface, which easier for the developer. We are able to see how the tests are executing so we may see some issues that otherwise we would not be able to detect. +The second way is without graphic interface. It is done this way when e2e tests are executed through Github actions in deployment, not locally. It is sometimes more difficult to detect the issues but there is still a great overview of the tests execution which helps to detect problems. + +=== Usability tests +One of our quality goals is 'Usability', we always have it in mind when we are developing new features. However, we need to check if we are doing it good. To test the usability of the application we made some rudimentary usability tests. + +We got three users to test the application in different stages of the development, helping us to change things that we did not notice when developing it: + +For instance, one user told us that our 'Play' button in home page always redirected to login, even if you were logged in. It caused confusion to the users so we changed it to redirect to game selection page if logged in. + +Other usability test was made with the Android application. The user noticed that some times the nav bar had a strange behaviour that messed up the entire interface. We adapted to the comments made by adapting the navbar. \ No newline at end of file diff --git a/docs/src/15_appendix3_monitoring.adoc b/docs/src/15_appendix3_monitoring.adoc new file mode 100644 index 00000000..69b7384f --- /dev/null +++ b/docs/src/15_appendix3_monitoring.adoc @@ -0,0 +1,14 @@ +ifndef::imagesdir[:imagesdir: ../images] + +[[section-monitoring]] +== Appendix III: Application monitoring + +Monitoring an application is a crucial part of it. It is an easy way of knowing how well a web application is working through different graphics and metrics. For this, we adapted the monitoring system that was given to us and we personalized it. That means we are using Prometheus as well as Grafana to monitorize our project. Prometheus intercepts every request that reaches our application's gateway. Grafana takes those data at paints them in easy-understanding graphics. We have a dashboard in Grafana to display some aspects that we consider relevant. + +The dashboard that we are using to monitorize our project is called _Wiq_es04 Dashboard_ and it has 3 different panels. The first of them shows the number of requests through time. The second one shows requests that accessed pages that were not found. The third one paints a graphic of the average time each request takes. + +image::15_monitoring_graphics.png["Grafana dashboard"] + +We would like to mention that this monitorization is set to be available both in production and deployment environments. For this reason, our Grafana dashboard is link:http://20.19.89.97:9091/d/1DYaynomMk/wiq-es04-dashboard?orgId=1[ accesible ]. + +To see how Grafana works we have used Apache. We have stablished the number of requests and how fast we want them to execute. This lets us check easily how our project treats requests. Also, another interesting thing to mention are the http://20.19.89.97:8000/metrics[ metrics ]. It shows every different request on the application and its status code. \ No newline at end of file diff --git a/docs/src_mermaid/05_level1 b/docs/src_mermaid/05_level1 index 51ed0f68..c41ca18f 100644 --- a/docs/src_mermaid/05_level1 +++ b/docs/src_mermaid/05_level1 @@ -1,12 +1,12 @@ flowchart TD subgraph Whitebox Wikidata Infinite Quest - sis1 -.->|Get/post| sis3(Gateway Service) - sis3 -.->|Get/post| sis4(User service) - sis3 -.->|Get| sis5(Question Generation Service) - sis1 -.-> sis7(Multiplayer Service) + sis1 -.->|Get/post| sis3(gateway) + sis3 -.->|Get/post| sis4(users) + sis3 -.->|Get| sis5(questions) + sis1 -.-> sis7(multiplayer) sis7 -.->|Get| sis3 sis4 -.-> db1[(MariaDB)] sis5 -.-> db2[(MongoDB)] end - el1(User) -.->|Interacts with| sis1(Web App) + el1(User) -.->|Interacts with| sis1(webapp) sis5 -.-> |Search for questions| sis6(WikiData) \ No newline at end of file diff --git a/docs/src_mermaid/05_level2_gatewayService b/docs/src_mermaid/05_level2_gatewayService index 2045df5b..49726d3a 100644 --- a/docs/src_mermaid/05_level2_gatewayService +++ b/docs/src_mermaid/05_level2_gatewayService @@ -1,9 +1,10 @@ flowchart TD - subgraph Whitebox Gateway Service + subgraph Whitebox gateway sis2(gateway-service) + sis6(prometheus) end - sis1(Web app) -.->|Get/post| sis2 - sis3(Multiplayer Service) -.->|Get| sis2 - sis2 -.->|Get| sis4(Question Generation Service) - sis2 -.->|Get/post| sis5(User Service) \ No newline at end of file + sis1(webapp) -.->|Get/post| sis2 + sis3(multiplayer) -.->|Get| sis2 + sis2 -.->|Get| sis4(questions) + sis2 -.->|Get/post| sis5(users) \ No newline at end of file diff --git a/docs/src_mermaid/05_level2_multiplayerService b/docs/src_mermaid/05_level2_multiplayerService index c5a6c654..7359354e 100644 --- a/docs/src_mermaid/05_level2_multiplayerService +++ b/docs/src_mermaid/05_level2_multiplayerService @@ -1,7 +1,7 @@ flowchart TD - subgraph Whitebox Multiplayer Service + subgraph Whitebox multiplayer sis2(index) end - sis1(Web app) -.-> sis2 - sis2 -.->|Get| sis3(GatewayService) \ No newline at end of file + sis1(webapp) -.-> sis2 + sis2 -.->|Get| sis3(gateway) \ No newline at end of file diff --git a/docs/src_mermaid/05_level2_questionGenerationService b/docs/src_mermaid/05_level2_questionGenerationService index fad3707c..a234902b 100644 --- a/docs/src_mermaid/05_level2_questionGenerationService +++ b/docs/src_mermaid/05_level2_questionGenerationService @@ -1,9 +1,9 @@ flowchart TD - subgraph Whitebox Question Generation Service + subgraph Whitebox questions sis2(routes) sis3(services) sis2 -.-> sis3 end - sis1(Gateway service) -.->|Get questions| sis2 + sis1(gateway) -.->|Get questions| sis2 sis3 -.->|Insert/delete/get questions| db1[(MongoDB)] sis3 -.->|Search for questions| sis4(Wikidata) \ No newline at end of file diff --git a/docs/src_mermaid/05_level2_userService b/docs/src_mermaid/05_level2_userService index b4bb4b41..b0a28405 100644 --- a/docs/src_mermaid/05_level2_userService +++ b/docs/src_mermaid/05_level2_userService @@ -1,10 +1,10 @@ flowchart TD - subgraph Whitebox User Service + subgraph Whitebox users sis2(routes) sis3(services) sis2 -.-> sis3 end - sis1(Gateway service) -.->|Login/register user| sis2 + sis1(gateway) -.->|Login/register user| sis2 sis1 -.->|Get statistics/group| sis2 sis1 -.->|Create/join group| sis2 sis1 -.->|Modify statistics| sis2 diff --git a/docs/src_mermaid/05_level2_webApp b/docs/src_mermaid/05_level2_webApp index 7ffd5c8b..d31b87e9 100644 --- a/docs/src_mermaid/05_level2_webApp +++ b/docs/src_mermaid/05_level2_webApp @@ -1,9 +1,9 @@ flowchart TD - subgraph Whitebox Web App + subgraph Whitebox webapp sis2(src) sis3(public) end sis1(User) -.->|Interacts with| sis2 - sis2 -.->|Get| sis4(Multiplayer Service) - sis2 -.->|Get/post| sis5(GatewayService Service) \ No newline at end of file + sis2 -.->|Get| sis4(multiplayer) + sis2 -.->|Get/post| sis5(gateway) \ No newline at end of file diff --git a/docs/src_mermaid/05_level3_routesQuestionGenerationService b/docs/src_mermaid/05_level3_routesQuestionGenerationService index 4a0ad508..608b285b 100644 --- a/docs/src_mermaid/05_level3_routesQuestionGenerationService +++ b/docs/src_mermaid/05_level3_routesQuestionGenerationService @@ -2,5 +2,5 @@ flowchart TD subgraph Whitebox routes sis2(question-routes) end - sis1(GateawayService) -.-> sis2 + sis1(gateaway) -.-> sis2 sis2 -.-> sis3(services) \ No newline at end of file diff --git a/docs/src_mermaid/05_level3_routesUserService b/docs/src_mermaid/05_level3_routesUserService index c528d1a8..23c236e2 100644 --- a/docs/src_mermaid/05_level3_routesUserService +++ b/docs/src_mermaid/05_level3_routesUserService @@ -3,9 +3,9 @@ flowchart TD sis2(user-routes) sis3(auth-routes) end - sis1(Gateway Service) -.->|Insert/get/edit group| sis2 - sis1(Gateway Service) -.->|Insert/get/edit statistics| sis2 - sis1(Gateway Service) -.->|Insert user| sis2 + sis1(gateway) -.->|Insert/get/edit group| sis2 + sis1 -.->|Insert/get/edit statistics| sis2 + sis1 -.->|Insert user| sis2 sis1 -.->|Get user| sis3 sis2 -.-> sis4(services) sis3 -.-> sis4 \ No newline at end of file diff --git a/docs/src_mermaid/05_level3_servicesUserService b/docs/src_mermaid/05_level3_servicesUserService index a4d45946..e12bf6d6 100644 --- a/docs/src_mermaid/05_level3_servicesUserService +++ b/docs/src_mermaid/05_level3_servicesUserService @@ -1,7 +1,6 @@ flowchart TD subgraph Whitebox services sis2(user-model) - sis3(authVerifyMiddleWare) end sis1(routes) -.-> sis2 sis2 -.->|Insert/get user| db1[(MariaDB)] diff --git a/docs/src_mermaid/05_level3_srcWebApp b/docs/src_mermaid/05_level3_srcWebApp index df684152..6c8ab7ac 100644 --- a/docs/src_mermaid/05_level3_srcWebApp +++ b/docs/src_mermaid/05_level3_srcWebApp @@ -5,8 +5,11 @@ flowchart TD sis4(pages) sis2 -.-> sis4 sis3 -.-> sis4 + sis7(App) + sis8(index) + sis7 -.-> sis4 end sis1(User) -.->|Interacts with| sis4 - sis4 -.->|Get/post| sis5(Gateway Service) - sis4 -.->|Get| sis6(Multiplayer Service) \ No newline at end of file + sis4 -.->|Get/post| sis5(gateway) + sis4 -.->|Get| sis6(multiplayer) \ No newline at end of file diff --git a/docs/src_mermaid/06_create_a_group b/docs/src_mermaid/06_create_a_group index bf301b1a..1dd55f8c 100644 --- a/docs/src_mermaid/06_create_a_group +++ b/docs/src_mermaid/06_create_a_group @@ -1,10 +1,10 @@ sequenceDiagram Note over User,Webapp: User must be logged in User ->>+ Webapp: click GROUPS navbar button - Webapp ->> Gateway Service: axios.get('/user/group/list') - Gateway Service ->> Users: axios.get('/user/group/list') + Webapp ->> Gateway Service: axios.get('/user/group') + Gateway Service ->> Users: axios.get('/user/group') - Users ->> MariaDB: router.get('/user/group/list') + Users ->> MariaDB: router.get('/user/group') MariaDB -->> Users: JSON response Users -->> Gateway Service: @@ -13,13 +13,13 @@ sequenceDiagram loop Create a group User ->>+ Webapp: insert a group name and click CREATE button - Webapp ->> Gateway Service: axios.post('/group/add') - Gateway Service ->> Users: axios.post('/group/add') + Webapp ->> Gateway Service: axios.post('/group') + Gateway Service ->> Users: axios.post('/user/group') - Users ->> MariaDB: router.post('/user/group/add') + Users ->> MariaDB: router.post('/user/group') MariaDB -->> Users: JSON response Users -->> Gateway Service: Gateway Service -->> Webapp: - Webapp -->>- User: update the groups view + Webapp -->>- User: update the groups list view end \ No newline at end of file diff --git a/docs/src_mermaid/06_exit_a_group b/docs/src_mermaid/06_exit_a_group new file mode 100644 index 00000000..d012ccf3 --- /dev/null +++ b/docs/src_mermaid/06_exit_a_group @@ -0,0 +1,18 @@ +sequenceDiagram + Note over User,Webapp: User must be logged in and in the group list view + Note over User,Webapp: User must belong to a group + + alt the user created the group + User ->>+ Webapp: search group in list and click DELETE button + else the user just belongs to the group + User ->> Webapp: search group in list and click EXIT IT! button + end + Webapp ->> Gateway Service: axios.put(`/group/:name/exit`, { username }); + Gateway Service ->> Users: axios.post(`/group/:name/exit`, { username }); + + Users ->> MariaDB: router.post('/group/:name/exit') + MariaDB -->> Users: JSON response + + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp -->>- User: update the groups list view diff --git a/docs/src_mermaid/06_group_details b/docs/src_mermaid/06_group_details index a9f16629..b6ebc2e2 100644 --- a/docs/src_mermaid/06_group_details +++ b/docs/src_mermaid/06_group_details @@ -3,10 +3,10 @@ sequenceDiagram alt There is a group in the list User ->>+ Webapp: search group in list and click SEE MEMBERS button - Webapp ->> Gateway Service: axios.get('/group/${groupName}') - Gateway Service ->> Users: axios.get('/group/${groupName}') + Webapp ->> Gateway Service: axios.get('/group/:groupName', {params:{username:username}}) + Gateway Service ->> Users: axios.get('/group/:groupName' {params:{username:username}}) - Users ->> MariaDB: router.get('/user/group/:name/') + Users ->> MariaDB: router.get('/user/group/:name') MariaDB -->> Users: JSON response Users -->> Gateway Service: diff --git a/docs/src_mermaid/06_join_a_group b/docs/src_mermaid/06_join_a_group index 18713a3d..c5ec3841 100644 --- a/docs/src_mermaid/06_join_a_group +++ b/docs/src_mermaid/06_join_a_group @@ -3,10 +3,10 @@ sequenceDiagram alt A group is available to join User ->>+ Webapp: search group in list and click JOIN IT! button - Webapp ->> Gateway Service: axios.post('/group/${name}/join') - Gateway Service ->> Users: axios.post('/user/group/${name}/join') + Webapp ->> Gateway Service: axios.put('/group/:name') + Gateway Service ->> Users: axios.post('/user/group/:name') - Users ->> MariaDB: router.post('/user/group/:name/join') + Users ->> MariaDB: router.post('/user/group/:name') MariaDB -->> Users: JSON response Users -->> Gateway Service: diff --git a/docs/src_mermaid/06_login b/docs/src_mermaid/06_login index bfe444fc..7d5a5677 100644 --- a/docs/src_mermaid/06_login +++ b/docs/src_mermaid/06_login @@ -2,8 +2,8 @@ sequenceDiagram User ->>+ Webapp: click login link or play button Webapp -->>- User: redirect to login view User ->>+ Webapp: complete form and click - Webapp ->> Gateway Service: axios.post('/login') - Gateway Service ->> Users: axios.post('/login') + Webapp ->> Gateway Service: axios.post('/login',{username,password}) + Gateway Service ->> Users: axios.post('/login',{username,password}) Users ->> MariaDB: router.post('/login') MariaDB -->> Users: JSON response diff --git a/docs/src_mermaid/06_play b/docs/src_mermaid/06_play index a36017fb..9cd2db0c 100644 --- a/docs/src_mermaid/06_play +++ b/docs/src_mermaid/06_play @@ -7,16 +7,21 @@ sequenceDiagram User ->>+ Webapp: click a mode game and PLAY buttons loop search questions - Webapp ->> Gateway Service: axios.get('/questions') - Gateway Service ->> Questions: axios.get('/questions') - Questions ->> Questions: router.get('/questions') + Webapp ->> Gateway Service: axios.get('/questions/:lang') + + alt game mode + Gateway Service ->> Questions: axios.get('/questions/:lang') + Questions ->> Questions: router.get('/questions/:lang') + else + Gateway Service ->> Questions: axios.get('/questions/:lang/:category') + Questions ->> Questions: router.get('/questions/:lang/:category') + end + Questions ->> MongoDB: generateQuestions() MongoDB -->> Questions: - alt questions>10 - Questions ->> Wikidata: getRandomEntity(), getProperties(), convertUrlsToLabels() - Wikidata -->> Questions: - end + Questions ->> Wikidata: getRandomEntity(), getProperties(), convertUrlsToLabels() + Wikidata -->> Questions: Questions ->> MongoDB: getQuestion() MongoDB -->> Questions: JSON response Questions -->> Gateway Service: JSON response @@ -31,15 +36,15 @@ sequenceDiagram alt last question Webapp -->>- User: show homepage - Webapp ->> Gateway Service: axios.post('/statistics/edit') - Gateway Service ->> Users: axios.post('/user/statistics/edit') - Users ->> MariaDB: router.post('/user/statistics/edit') + Webapp ->> Gateway Service: axios.put('/statistics') + Gateway Service ->> Users: axios.post('/user/statistics') + Users ->> MariaDB: router.post('/user/statistics') MariaDB -->> Users: Users -->> Gateway Service: JSON response Gateway Service -->> Webapp: - Webapp ->> Gateway Service: axios.post('/user/questionsRecord') - Gateway Service ->> Users: axios.post('/user/questionsRecord') + Webapp ->> Gateway Service: axios.post('/questionsRecord') + Gateway Service ->> Users: axios.post('user/questionsRecord') Users ->> MariaDB: router.post('/user/questionsRecord') MariaDB -->> Users: JSON response Users -->> Gateway Service: diff --git a/docs/src_mermaid/06_profile b/docs/src_mermaid/06_profile new file mode 100644 index 00000000..0fd459bb --- /dev/null +++ b/docs/src_mermaid/06_profile @@ -0,0 +1,25 @@ +sequenceDiagram + Note over User,Webapp: User must be logged in + User ->>+ Webapp: click his name/photo in the navbar + Webapp ->> Gateway Service: axios.get('/profile', {params:{username:username}}) + Gateway Service ->> Users: axios.get('/user/profile', {params:{username:username}}) + + Users ->> MariaDB: router.get('/user/profile') + MariaDB -->> Users: JSON response + + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp -->>- User: show the users ranking + + opt Update your user avatar + User ->>+ Webapp: click any avatar of your choice + User ->>+ Webapp: click Confirm change button + Webapp ->> Gateway Service: axios.put('/profile/:username', {imageUrl:currentSelectedAvatar}) + Gateway Service ->> Users: axios.get('/user/profile/:username', {imageUrl:currentSelectedAvatar}) + + Users ->> MariaDB: router.get('/user/profile/:username') + MariaDB -->> Users: JSON response + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp -->>- User: update the navbar avatar and profile view + end \ No newline at end of file diff --git a/docs/src_mermaid/06_ranking b/docs/src_mermaid/06_ranking new file mode 100644 index 00000000..bb5d58de --- /dev/null +++ b/docs/src_mermaid/06_ranking @@ -0,0 +1,25 @@ +sequenceDiagram + Note over User,Webapp: User must be logged in + User ->>+ Webapp: click RANKING navbar button + Webapp ->> Gateway Service: axios.get('/ranking') + Gateway Service ->> Users: axios.get('/user/ranking') + + Users ->> MariaDB: router.get('/user/ranking') + MariaDB -->> Users: JSON response + + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp -->>- User: show the users ranking + + opt See groups ranking + User ->>+ Webapp: click ranking view GROUPS button + Webapp ->> Gateway Service: axios.get('/group/ranking') + Gateway Service ->> Users: axios.get('/user/group/ranking') + + Users ->> MariaDB: router.get('/user/group/ranking') + MariaDB -->> Users: JSON response + + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp -->>- User: show the group ranking + end \ No newline at end of file diff --git a/docs/src_mermaid/06_register b/docs/src_mermaid/06_register index 44cf8d8e..45bb9724 100644 --- a/docs/src_mermaid/06_register +++ b/docs/src_mermaid/06_register @@ -2,12 +2,18 @@ sequenceDiagram User ->>+ Webapp: click register link in login view Webapp -->>- User: redirect to register view User ->>+ Webapp: complete form and click - Webapp ->> Gateway Service: axios.post('/user/add') - Gateway Service ->> Users: axios.post('/user/add') + Webapp ->> Gateway Service: axios.post('/user', {username,password}) + Gateway Service ->> Users: axios.post('/user', {username,password}) - Users ->> MariaDB: router.post('/user/add') - MariaDB -->> Users: JSON response + Users ->> MariaDB: router.post('/user') + MariaDB -->> Users: JSON response with user info + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp ->> Gateway Service: axios.post('/login',{username,password}) + Gateway Service ->> Users: axios.post('/login',{username,password}) + Users ->> MariaDB: router.post('/login') + MariaDB -->> Users: JSON response Users -->> Gateway Service: Gateway Service -->> Webapp: Webapp -->>- User: show homepage \ No newline at end of file diff --git a/docs/src_mermaid/06_statistics b/docs/src_mermaid/06_statistics index 78953284..e6cf8ffc 100644 --- a/docs/src_mermaid/06_statistics +++ b/docs/src_mermaid/06_statistics @@ -1,8 +1,13 @@ sequenceDiagram Note over User,Webapp: User must be logged in - User ->>+ Webapp: click STATISTICS navbar button - Webapp ->> Gateway Service: axios.get('/user/statistics/${username}'); - Gateway Service ->> Users: axios.get('/user/statistics/${username}'); + alt the user wants to see his statistics + User ->>+ Webapp: click STATISTICS navbar button + else the user wants to see a member of any of its groups' statistics + User ->> Webapp: click SEE STATISTICS in the group details view + end + + Webapp ->> Gateway Service: axios.get('/statistics/:username', {params:{loggedUser:username}}); + Gateway Service ->> Users: axios.get('/statistics/:username', {params:{loggedUser:username}}); Users ->> MariaDB: router.get('/user/statistics/:username') MariaDB -->> Users: JSON response @@ -15,4 +20,15 @@ sequenceDiagram User ->>+ Webapp: click other mode statistics button Webapp -->>- User: show the other mode statistics stored end + + User ->>+ Webapp: click Show Questions Record button + Webapp ->> Gateway Service: axios.get('/questionsRecord/:username/:selectedMode', {params:{username, gameMode}}); + Gateway Service ->> Users: axios.get('/questionsRecord/:username/:selectedMode', {params:{username, gameMode}}); + + Users ->> MariaDB: router.get('/questionsRecord/:username/:selectedMode') + MariaDB -->> Users: JSON response + + Users -->> Gateway Service: + Gateway Service -->> Webapp: + Webapp -->>- User: show user questions record \ No newline at end of file diff --git a/docs/src_mermaid/07_Deployment_View_1 b/docs/src_mermaid/07_Deployment_View_1 index 74464856..bb0901ef 100644 --- a/docs/src_mermaid/07_Deployment_View_1 +++ b/docs/src_mermaid/07_Deployment_View_1 @@ -11,41 +11,38 @@ left to right direction node AzureServer{ - node DockerContainer1 { - component MongoDB + node MongoDB { + } - - node DockerContainer2 { - component AuthService - } - + + node MariaDB { + } - node DockerContainer3{ - component UserService - } + node Users{ + } - node DockerContainer7{ - component GatewayService + node GatewayService{ + } - node DockerContainer4 { - component WebApp + node WebApp { + } -node DockerContainer8{ - component QuestionGenerationSystem +node Questions{ + } -node DockerContainer9{ - component Multiplayer +node Multiplayer{ + } - node DockerContainer5{ - component Grafana + node Grafana{ + } - node DockerContainer6{ - component Prometheus + node Prometheus{ + } } @@ -68,6 +65,6 @@ User -l-> WebBrowser : "uses" WebApp -d-> WebBrowser : "shows on user device" -QuestionGenerationSystem -d-> Wikidata : "Fetches data" +Questions -d-> Wikidata : "Fetches data" @enduml ''' diff --git a/docs/src_mermaid/08_domain_model_1 b/docs/src_mermaid/08_domain_model_1 index 8ba746a7..eeedad0f 100644 --- a/docs/src_mermaid/08_domain_model_1 +++ b/docs/src_mermaid/08_domain_model_1 @@ -18,7 +18,7 @@ classDiagram } class Profile { - String username - - int money + - int points } class ProfileStatistics { - List~Question~ right @@ -28,4 +28,3 @@ classDiagram } class Group { } - diff --git a/docs/src_mermaid/08_domain_model_2 b/docs/src_mermaid/08_domain_model_2 index 65f0fb9c..3538a18d 100644 --- a/docs/src_mermaid/08_domain_model_2 +++ b/docs/src_mermaid/08_domain_model_2 @@ -3,7 +3,7 @@ classDiagram Game <|-- WarmQuestion Game <|-- TheChallenge Game <|-- DiscoveringCities - Game <|-- OnlineMode + Game <|-- Multiplayer class Game { } class WiseMenStack { @@ -14,6 +14,6 @@ classDiagram } class DiscoveringCities { } - class OnlineMode { + class Multiplayer { } \ No newline at end of file diff --git a/docs/src_mermaid/10_quality_tree b/docs/src_mermaid/10_quality_tree new file mode 100644 index 00000000..87b36288 --- /dev/null +++ b/docs/src_mermaid/10_quality_tree @@ -0,0 +1,5 @@ +flowchart TD + C{Quality requirements} --> Usability + C --> Availability + C --> Testability + C --> Performance \ No newline at end of file diff --git a/gatewayservice/apis/questions-api.yaml b/gatewayservice/apis/questions-api.yaml new file mode 100644 index 00000000..cb3637b4 --- /dev/null +++ b/gatewayservice/apis/questions-api.yaml @@ -0,0 +1,118 @@ +openapi: 3.0.0 +info: + title: Questionservice API + description: Question OpenAPI specification + version: 1.0.0 +servers: + # Added by API Auto Mocking Plugin + - description: SwaggerHub API Auto Mocking + url: https://virtserver.swaggerhub.com/UO288347_1/questions-api/1.0.0 + - url: http://localhost:8000 + description: Development server + - url: http://20.19.89.97:8000 + description: Production server +paths: + /questions/:lang: + get: + summary: Gests a question from the database. + operationId: questions + description: Gests a question from the database. If there are not enough questions, it asks wikidata for more + responses: + '200': + description: Returned question succesfully + content: + application/json: + schema: + type: object + properties: + _id: + type: string + description: Unique identification + example: 660549daa56f92f15a73f89c + question: + type: string + options: + type: array + items: + type: string + correctAnswer: + type: string + categories: + type: array + items: + type: string + language: + type: string + '400': + description: Failed to get question + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error while getting a question. + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error + /questions/:lang/:category: + get: + summary: Gests a question from a specified category. + operationId: questionsByCategory + description: Gests a question from the database of a specified category. If there are not enough questions, it asks wikidata for more + responses: + '200': + description: Returned question succesfully + content: + application/json: + schema: + type: object + properties: + _id: + type: string + description: Unique identification + example: 660549daa56f92f15a73f89c + question: + type: string + options: + type: array + items: + type: string + correctAnswer: + type: string + categories: + type: array + items: + type: string + language: + type: string + '400': + description: Failed to get question + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error while getting a question. + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error \ No newline at end of file diff --git a/gatewayservice/apis/users-api.yaml b/gatewayservice/apis/users-api.yaml new file mode 100644 index 00000000..1d3214da --- /dev/null +++ b/gatewayservice/apis/users-api.yaml @@ -0,0 +1,422 @@ +--- +openapi: 3.0.0 +info: + title: UsersService API + description: User OpenAPI specification + version: 1.0.0 +servers: +- url: https://virtserver.swaggerhub.com/UO289689_1/users-api/1.0.0 + description: SwaggerHub API Auto Mocking +- url: http://localhost:8000 + description: Development server +- url: http://20.19.89.97:8000 + description: Production server +paths: + /user: + get: + summary: Get all users + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200' + "400": + description: Bad request + post: + summary: Add a new user + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/user_body' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_1' + "500": + description: Internal Server Error + /user/{username}: + get: + summary: Get user by username + parameters: + - name: username + in: path + description: Username + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_2' + "400": + description: Bad request + /ranking: + get: + summary: Get users sorted by correct answers + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200' + "400": + description: Bad request + /statistics/{username}: + get: + summary: Get user statistics by username + parameters: + - name: username + in: path + description: Username + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_3' + "400": + description: Bad request + /statistics: + put: + summary: Update user statistics + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/statistics_body' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_4' + "500": + description: Internal Server Error + /questionsRecord/{username}/{gameMode}: + get: + summary: Get users questions record by username and game mode + parameters: + - name: username + in: path + description: Username + required: true + style: simple + explode: false + schema: + type: string + - name: gameMode + in: path + description: Game mode + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_5' + "500": + description: Internal Server Error + /questionsRecord: + put: + summary: Update users questions record + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/questionsRecord_body' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/questionsRecord_body' + "500": + description: Internal Server Error + /group: + get: + summary: Get all users groups + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_6' + "500": + description: Internal Server Error + post: + summary: Add a new group + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/group_body' + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_7' + "400": + description: Bad request + /group/{name}: + get: + summary: Get group by name + parameters: + - name: name + in: path + description: Group name + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/inline_response_200_7' + "404": + description: Group not found + "400": + description: Bad request +components: + schemas: + inline_response_200: + type: object + properties: + users: + type: array + items: + type: object + properties: + username: + type: string + password: + type: string + name: + type: string + surname: + type: string + createdAt: + type: string + imageUrl: + type: string + user_body: + type: object + properties: + username: + type: string + password: + type: string + name: + type: string + surname: + type: string + inline_response_200_1: + type: object + properties: + username: + type: string + password: + type: string + name: + type: string + surname: + type: string + createdAt: + type: string + inline_response_200_2: + type: object + properties: + username: + type: string + password: + type: string + name: + type: string + surname: + type: string + createdAt: + type: string + imageUrl: + type: string + inline_response_200_3: + type: object + properties: + username: + type: string + the_callenge_earned_money: + type: integer + the_callenge_correctly_answered_questions: + type: integer + the_callenge_incorrectly_answered_questions: + type: integer + the_callenge_total_time_played: + type: integer + the_callenge_games_played: + type: integer + wise_men_stack_earned_money: + type: integer + wise_men_stack_correctly_answered_questions: + type: integer + wise_men_stack_incorrectly_answered_questions: + type: integer + wise_men_stack_games_played: + type: integer + warm_question_earned_money: + type: integer + warm_question_correctly_answered_questions: + type: integer + warm_question_incorrectly_answered_questions: + type: integer + warm_question_passed_questions: + type: integer + warm_question_games_played: + type: integer + discovering_cities_earned_money: + type: integer + discovering_cities_correctly_answered_questions: + type: integer + discovering_cities_incorrectly_answered_questions: + type: integer + discovering_cities_games_played: + type: integer + statistics_body: + type: object + properties: + username: + type: string + the_callenge_earned_money: + type: integer + the_callenge_correctly_answered_questions: + type: integer + the_callenge_incorrectly_answered_questions: + type: integer + the_callenge_total_time_played: + type: integer + the_callenge_games_played: + type: integer + wise_men_stack_earned_money: + type: integer + wise_men_stack_correctly_answered_questions: + type: integer + wise_men_stack_incorrectly_answered_questions: + type: integer + wise_men_stack_games_played: + type: integer + warm_question_earned_money: + type: integer + warm_question_correctly_answered_questions: + type: integer + warm_question_incorrectly_answered_questions: + type: integer + warm_question_passed_questions: + type: integer + warm_question_games_played: + type: integer + discovering_cities_earned_money: + type: integer + discovering_cities_correctly_answered_questions: + type: integer + discovering_cities_incorrectly_answered_questions: + type: integer + discovering_cities_games_played: + type: integer + online_earned_money: + type: integer + online_correctly_answered_questions: + type: integer + online_incorrectly_answered_questions: + type: integer + online_total_time_played: + type: integer + online_games_played: + type: integer + inline_response_200_4: + type: object + properties: + username: + type: string + message: + type: string + inline_response_200_5: + type: object + properties: + username: + type: string + questionsRecord: + type: object + gameMode: + type: string + questionsRecord_body: + type: object + properties: + username: + type: string + questionsRecord: + type: object + gameMode: + type: string + inline_response_200_6: + type: object + properties: + groups: + type: array + items: + type: object + properties: + name: + type: string + creator: + type: string + createdAt: + type: string + group_body: + type: object + properties: + name: + type: string + username: + type: string + inline_response_200_7: + type: object + properties: + name: + type: string + creator: + type: string + createdAt: + type: string diff --git a/gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json b/gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json index 28caba1c..0e00fbf6 100644 --- a/gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json +++ b/gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json @@ -80,9 +80,9 @@ "steppedLine": false, "targets": [ { - "expr": "sum(increase(http_request_duration_seconds_count[1h]))", + "expr": "sum(increase(http_request_duration_seconds_count[1m]))", "interval": "", - "legendFormat": "Requests per hour", + "legendFormat": "Requests per minute", "refId": "A" } ], @@ -90,7 +90,7 @@ "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "Rate (R): Number of requests per hour", + "title": "Rate (R): Number of requests per minute", "tooltip": { "shared": true, "sort": 0, @@ -191,7 +191,7 @@ { "expr": "sum(increase(http_request_duration_seconds_count{status_code=~\"4.*\"}[1m]))", "interval": "", - "legendFormat": "", + "legendFormat": "Not found", "refId": "A" } ], @@ -199,7 +199,7 @@ "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "Errors (E): The number of failed requests", + "title": "Not found (NF): The number of not found requests", "tooltip": { "shared": true, "sort": 0, diff --git a/questions/__tests/services/question-generation-service.test.js b/questions/__tests/services/question-generation-service.test.js index a999966c..42cf160f 100644 --- a/questions/__tests/services/question-generation-service.test.js +++ b/questions/__tests/services/question-generation-service.test.js @@ -6,6 +6,7 @@ const generalQuestions = require('../../utils/generalQuestions'); jest.mock('../../utils/generalQuestions'); jest.mock('../../services/wikidata-service'); jest.mock('../../services/question-data-service', () => ({ + getQuestion: jest.fn(), addQuestion: jest.fn(), // Mockear la función addQuestion para que no haga nada })); @@ -15,15 +16,10 @@ const entity = { "properties": [ { "property": "P1082", - "template": - [{ - "lang": "es", - "question": "Cuál es la población de x" - }, - { - "lang": "en", - "question": "What is the population of x" - }], + "template": { + "es": "Cuál es la población de x", + "en": "What is the population of x" + }, "filter": ">1000000", "category": ["Geography"] } @@ -48,8 +44,9 @@ describe('Question generation', function() { wikidataService.getProperties.mockResolvedValue(['Barcelona', 'Paris', 'London']); wikidataService.convertUrlsToLabels.mockResolvedValue(['Barcelona', 'Paris', 'London','Madrid']); - generalQuestions.shuffleArray.mockResolvedValue(['Barcelona', 'Paris', 'London','Madrid']) + generalQuestions.shuffleArray.mockResolvedValue(['Barcelona', 'Paris', 'London','Madrid']); + dbService.getQuestion.mockResolvedValue("undefined"); dbService.addQuestion.mockResolvedValue(); // Llama a la función que deseas probar await generator.generateQuestions(1,"en","Geography"); diff --git a/questions/services/generate-questions-service.js b/questions/services/generate-questions-service.js index fe467689..f6b4d666 100644 --- a/questions/services/generate-questions-service.js +++ b/questions/services/generate-questions-service.js @@ -1,6 +1,6 @@ const utils = require('../utils/generalQuestions'); const wikidataService = require('./wikidata-service'); -const dbService = require('./question-data-service') +const dbService = require('./question-data-service'); /** * Asynchronously generates a specified number of questions using data from the JSON file and stores them in the DB. @@ -24,7 +24,7 @@ async function generateQuestions(n, language, questionCategory) { // get data for selected entity let pos = Math.floor(Math.random() * entity.properties.length); - + //use only property of that required category if(questionCategory) { const filteredProperties = []; @@ -33,6 +33,7 @@ async function generateQuestions(n, language, questionCategory) { filteredProperties.push(property); } }); + const randomPos = Math.floor(Math.random() * filteredProperties.length); const propertyJson = filteredProperties[randomPos]; pos = entity.properties.findIndex(prop => prop === propertyJson); @@ -41,19 +42,55 @@ async function generateQuestions(n, language, questionCategory) { const property = entity.properties[pos].property; const categories = entity.properties[pos].category; const filter = entity.properties[pos].filter; - const lang = language=="es"?0:1; //0 spanish, 1 english - const question = entity.properties[pos].template[lang].question; + // Now language is accessed directly: + const question = entity.properties[pos].template[language]; - //let [entityName, searched_property] = await wikidataService.getRandomEntity(instance, property, filter); - let [entityName, searched_property] = await wikidataService.getRandomEntity(entity, pos, language); + let [entityName, searched_property] = [null, null] + let invalidEntity = false; + while ((!entityName || !searched_property) && !invalidEntity) { + try { + // If result for the entity is invalid, stops and logs the entity + let response = await wikidataService.getRandomEntity(entity, pos, language); + if (response && response.length === 2) { + [entityName, searched_property] = response; + } else { + console.error(`Error: getRandomEntity returned an invalid response for the entity: ${entity.name}`); + invalidEntity = true; + } + } catch (error) { + console.error("Error generating label for the answer: ", error.message); + console.error("Line:", error.stack.split("\n")[1]); + } + } + if (invalidEntity) { + continue; + } if (searched_property !== null) { //This way we can ask questions with different structures const questionText = question.replace('x',entityName.charAt(0).toUpperCase() + entityName.slice(1)) +`?`; + // If that question is already in db, it goes on: + const questionAlreadyInDb = await dbService.getQuestion({"question": questionText}); + if (!questionAlreadyInDb === undefined) { + console.log(`Question ${questionText} already in db, skipping`); + continue; + } + let correctAnswer = searched_property; // options will contain 3 wrong answers plus the correct one - let options = await wikidataService.getProperties(property, language, filter); + let options; + try { + options = await wikidataService.getProperties(property, language, filter); + + } catch (error) { + console.error(`Error generating options for ${entityName}: `, error.message); + console.error("Line:", error.stack.split("\n")[1]); + continue; + } + if (!options) { + continue; + } options.push(correctAnswer); //If properties are entities diff --git a/questions/services/question-data-service.js b/questions/services/question-data-service.js index ef6a054e..1b17cdc8 100644 --- a/questions/services/question-data-service.js +++ b/questions/services/question-data-service.js @@ -20,7 +20,6 @@ module.exports = { }, - //TODO - Filter func not yet implemented /** * Returns a question from the database that could be filtered using a dictionary and removes it. * @param {dict} filter - The dict containing the filter options for mongoose. @@ -28,17 +27,13 @@ module.exports = { */ getQuestion : async function(filter = {}) { try { - //const question = await Question.findOne(filter); - //return question; - //if there is filter if (Object.keys(filter).length !== 0) { - + const q = await Question.aggregate([ { $match: filter }, { $sample: { size: 1 } } ]); - return q[0]; } else { //if not filter -> just random question diff --git a/questions/services/wikidata-service.js b/questions/services/wikidata-service.js index 9684d293..24d2ed26 100644 --- a/questions/services/wikidata-service.js +++ b/questions/services/wikidata-service.js @@ -1,6 +1,7 @@ const axios = require('axios'); async function getRandomEntity(entity, pos, language) { + const property = entity.properties[pos].property; const filt = entity.properties[pos].filter; var filter = ''; @@ -22,13 +23,12 @@ async function getRandomEntity(entity, pos, language) { `; // it is better to use the FILTER rather than SERVICE //SERVICE wikibase:label { bd:serviceParam wikibase:language "es". } - + const urlApiWikidata = 'https://query.wikidata.org/sparql'; const headers = { 'User-Agent': 'QuestionGeneration/1.0', 'Accept': 'application/json', }; - try { response = await axios.get(urlApiWikidata, { params: { @@ -37,14 +37,16 @@ async function getRandomEntity(entity, pos, language) { }, headers: headers, }); - + + const data = await response.data const entities = data.results.bindings; - + if (entities.length > 0) { const randomEntity = entities[Math.floor(Math.random() * entities.length)]; const entityName = randomEntity.entityLabel.value; const property = randomEntity.property.value; + console.log("ENTITY, PROPERTY: ",entityName, property); return [entityName, property]; } else { return null; @@ -80,7 +82,7 @@ async function getProperties(property, language, filt) { query: consultaSparql, format: 'json' }, - timeout: 15000 //means error + timeout: 30000 //means error }); const endTime = new Date(); const elapsedTime = endTime - startTime; @@ -95,11 +97,11 @@ async function getProperties(property, language, filt) { for(var i = 0; i < 3 ; i++) { properties[i] = list[Math.floor(Math.random() * list.length)].property.value; } + console.log("PROPERTIES: ",properties); return properties; } return null; } catch (error) { - console.error(error.stack); console.error(`Error obtaining properties: ${error.message}`); console.error("Line:", error.stack.split("\n")[1]); return null; @@ -118,7 +120,7 @@ async function getEntityLabel(entityUrl) { if(entity.labels.es) { return entity.labels.es.value; } - + return "no label (TEST)"; } diff --git a/questions/utils/question.json b/questions/utils/question.json index 9b48cb34..76023cdc 100644 --- a/questions/utils/question.json +++ b/questions/utils/question.json @@ -5,41 +5,30 @@ "properties": [ { "property": "P1082", - "template": - [{ - "lang": "es", - "question": "Cuál es la población de x" - }, - { - "lang": "en", - "question": "What is the population of x" - }], + "template": { + "es": "Cuál es la población de x", + "en": "What is the population of x", + "fr": "Quelle est la population de x" + }, "filter": ">4000000", "category": ["Geography", "Cities"] }, { "property": "P36", - "template": [{ - "lang": "es", - "question": "Cuál es la capital de x" - }, - { - "lang": "en", - "question": "What is the capital of x" - }], + "template": { + "es": "Cuál es la capital de x", + "en": "What is the capital of x", + "fr": "Quelle est la capitale de x" + }, "category": ["Geography", "Cities"] }, { "property": "P38", - "template": - [{ - "lang": "es", - "question": "Que moneda tiene x" - }, - { - "lang": "en", - "question": "What currency x has" - }], + "template": { + "es": "Que moneda tiene x", + "en": "What currency x has", + "fr": "Quelle est la devise de x" + }, "category": ["Political"] } ] @@ -50,28 +39,20 @@ "properties": [ { "property": "P115", - "template": - [{ - "lang": "es", - "question": "Cuál es el estadio del equipo x" - }, - { - "lang": "en", - "question": "What is the stadium of x team" - }], + "template": { + "es": "Cuál es el estadio del equipo x", + "en": "What is the stadium of x team", + "fr": "Quel est le stade de l'équipe x" + }, "category": ["Sports"] }, { "property": "P17", - "template": - [{ - "lang": "es", - "question": "Cuál es el pais del equipo x" - }, - { - "lang": "en", - "question": "What is the country of x team" - }], + "template": { + "es": "Cuál es el pais del equipo x", + "en": "What is the country of x team", + "fr": "Quel est le pays de l'équipe x" + }, "category": ["Sports", "Geography"] } ] @@ -82,15 +63,11 @@ "properties": [ { "property": "P205", - "template": - [{ - "lang": "es", - "question": "En qué país está el río x" - }, - { - "lang": "en", - "question": "What country is x river in" - }], + "template": { + "es": "En qué país está el río x", + "en": "What country is x river in", + "fr": "Dans quel pays se trouve le fleuve x" + }, "category": ["Geography"] } ] @@ -101,40 +78,20 @@ "properties": [ { "property": "P170", - "template": - [{ - "lang": "es", - "question": "Quien es el autor de x" - }, - { - "lang": "en", - "question": "Who is the author of x" - }], - "category": ["Art"] - }, - { - "property": "P571", - "template": [{ - "lang": "es", - "question": "Cual es el año de creacion de x" - }, - { - "lang": "en", - "question": "What is the year of creation of x" - }], + "template": { + "es": "Quien es el autor de x", + "en": "Who is the author of x", + "fr": "Qui est l'auteur de x" + }, "category": ["Art"] }, { "property": "P136", - "template": - [{ - "lang": "es", - "question": "Cual es el estilo artistico de x" - }, - { - "lang": "en", - "question": "What is the artistic style of x" - }], + "template": { + "es": "Cual es el estilo artistico de x", + "en": "What is the artistic style of x", + "fr": "Quel est le style artistique de x" + }, "category": ["Art"] } ] @@ -145,91 +102,35 @@ "properties": [ { "property": "P61", - "template": - [{ - "lang": "es", - "question": "Quien es el inventor de x" - }, - { - "lang": "en", - "question": "Who is the inventor of x" - }], - "category": ["Science"] - }, - { - "property": "P575", - "template": - [{ - "lang": "es", - "question": "En que año se inventó x" - }, - { - "lang": "en", - "question": "What was the year of invention of x" - }], + "template": { + "es": "Quien es el inventor de x", + "en": "Who is the inventor of x", + "fr": "Qui est l'inventeur de x" + }, "category": ["Science"] } ] }, - { - "name": "mountain", - "instance": "Q8502", - "properties": [ - { - "property": "P2044", - "template": - [{ - "lang": "es", - "question": "Cual es la altitud mxima del x" - }, - { - "lang": "en", - "question": "What is the maximum altitude of x" - }], - "category": ["Geography"] - } - ] - }, { "name": "book", - "instance": "Q571", + "instance": "Q7725634", "properties": [ { "property": "P50", - "template": - [{ - "lang": "es", - "question": "Quien es el autor de x" - }, - { - "lang": "en", - "question": "Who is the author of x" - }], - "category": ["Entertainment"] - }, - { - "property": "P577", - "template": [{ - "lang": "es", - "question": "Cual es el año de publicacion de x" - }, - { - "lang": "en", - "question": "What is the year of publication of x" - }], + "template": { + "es": "Quien es el autor de x", + "en": "Who is the author of x", + "fr": "Qui est l'auteur de x" + }, "category": ["Entertainment"] }, { "property": "P136", - "template": - [{ - "lang": "es", - "question": "Cual es el genero de x" - }, - { - "lang": "en", - "question": "What is the genre of x" - }], + "template": { + "es": "Cual es el genero de x", + "en": "What is the genre of x", + "fr": "Quel est le genre de x" + }, "category": ["Entertainment"] } ] @@ -240,40 +141,20 @@ "properties": [ { "property": "P176", - "template": - [{ - "lang": "es", - "question": "Quien es el fabricante de x" - }, - { - "lang": "en", - "question": "Who is the manufacturer of x" - }], + "template": { + "es": "Quien es el fabricante de x", + "en": "Who is the manufacturer of x", + "fr": "Qui est le fabricant de x" + }, "category": ["Cars"] }, { "property": "P3032", - "template": [{ - "lang": "es", - "question": "Cuál es la velocidad máxima de x" - }, - { - "lang": "en", - "question": "What is the maximum speed of x" - }], - "category": ["Cars"] - }, - { - "property": "P571", - "template": - [{ - "lang": "es", - "question": "En qué año fue introducido x al mercado" - }, - { - "lang": "en", - "question": "In what year was x introduced to the market" - }], + "template": { + "es": "Cuál es la velocidad máxima de x", + "en": "What is the maximum speed of x", + "fr": "Quelle est la vitesse maximale de x" + }, "category": ["Cars"] } ] @@ -282,29 +163,13 @@ "name": "dish", "instance": "Q746549", "properties": [ - { - "property": "P527", - "template": - [{ - "lang": "es", - "question": "Cuál es el ingrediente principal de x" - }, - { - "lang": "en", - "question": "What is the main ingredient of x" - }], - "category": ["Food"] - }, { "property": "P495", - "template": [{ - "lang": "es", - "question": "De qué región es originario x" - }, - { - "lang": "en", - "question": "What region is x originally from?" - }], + "template": { + "es": "De qué región es originario x", + "en": "What region is x originally from?", + "fr": "De quelle région x est-il originaire?" + }, "category": ["Food"] } ] @@ -315,70 +180,44 @@ "properties": [ { "property": "P84", - "template": - [{ - "lang": "es", - "question": "Quién fue el arquitecto de x" - }, - { - "lang": "en", - "question": "Who was the architect of x" - }], - "category": ["Art"] - }, - { - "property": "P571", - "template": [{ - "lang": "es", - "question": "En qué año se completó x" - }, - { - "lang": "en", - "question": "In what year was it completed x" - }], + "template": { + "es": "Quién fue el arquitecto de x", + "en": "Who was the architect of x", + "fr": "Qui était l'architecte de x" + }, "category": ["Art"] }, { "property": "P149", - "template": [{ - "lang": "es", - "question": "Cuál es el estilo arquitectónico de x" - }, - { - "lang": "en", - "question": "What is the architectural style of x" - }], + "template": { + "es": "Cuál es el estilo arquitectónico de x", + "en": "What is the architectural style of x", + "fr": "Quel est le style architectural de x" + }, "category": ["Art"] } ] }, { - "name": "television series", + "name": "television series", "instance": "Q5398426", "properties": [ { "property": "P178", - "template": - [{ - "lang": "es", - "question": "Quién es el creador de la serie x" - }, - { - "lang": "en", - "question": "Who is the creator of the series x" - }], + "template": { + "es": "Quién es el creador de la serie x", + "en": "Who is the creator of the series x", + "fr": "Qui est le créateur de la série x" + }, "category": ["Entertainment"] }, { "property": "P2437", - "template": [{ - "lang": "es", - "question": "Cuántas temporadas tiene la serie x" - }, - { - "lang": "en", - "question": "How many seasons does the series x have?" - }], + "template": { + "es": "Cuántas temporadas tiene la serie x", + "en": "How many seasons does the series x have?", + "fr": "Combien de saisons la série x a-t-elle?" + }, "category": ["Entertainment"] } ] @@ -389,27 +228,20 @@ "properties": [ { "property": "P178", - "template": - [{ - "lang": "es", - "question": "Quién desarrolló el software x" - }, - { - "lang": "en", - "question": "Who developed the software x" - }], + "template": { + "es": "Quién desarrolló el software x", + "en": "Who developed the software x", + "fr": "Qui a développé le logiciel x" + }, "category": ["Technology"] }, { "property": "P277", - "template": [{ - "lang": "es", - "question": "En qué lenguaje de programación está escrito x" - }, - { - "lang": "en", - "question": "What programming language is x written in?" - }], + "template": { + "es": "En qué lenguaje de programación está escrito x", + "en": "What programming language is x written in?", + "fr": "Dans quel langage de programmation x est-il écrit?" + }, "category": ["Technology"] } ] @@ -420,27 +252,20 @@ "properties": [ { "property": "P641", - "template": - [{ - "lang": "es", - "question": "En qué deporte compite x" - }, - { - "lang": "en", - "question": "Who developed the software x" - }], + "template": { + "es": "En qué deporte compite x", + "en": "In what sport does x compete", + "fr": "Dans quel sport x compétitionne-t-il" + }, "category": ["Sport"] }, { "property": "P27", - "template": [{ - "lang": "es", - "question": "Cuál es la nacionalidad del atleta x" - }, - { - "lang": "en", - "question": "What is the nationality of the athlete x" - }], + "template": { + "es": "Cuál es la nacionalidad del atleta x", + "en": "What is the nationality of the athlete x", + "fr": "Quelle est la nationalité de l'athlète x" + }, "category": ["Sport"] } ] @@ -451,39 +276,20 @@ "properties": [ { "property": "P178", - "template": - [{ - "lang": "es", - "question": "Quien es el desarrollador del videojuego x" - }, - { - "lang": "en", - "question": "Who is the developer of the video game x" - }], - "category": ["Games"] - }, - { - "property": "P577", - "template": [{ - "lang": "es", - "question": "En qué año fue lanzado el videojuego x" - }, - { - "lang": "en", - "question": "In what year was the video game x released?" - }], + "template": { + "es": "Quien es el desarrollador del videojuego x", + "en": "Who is the developer of the video game x", + "fr": "Qui est le développeur du jeu vidéo x" + }, "category": ["Games"] }, { "property": "P400", - "template": [{ - "lang": "es", - "question": "Cuáles son las plataformas compatibles con x" + "template": { + "es": "Cuáles son las plataformas compatibles con x", + "en": "What are the platforms supported by x", + "fr": "Quelles sont les plates-formes supportées par x" }, - { - "lang": "en", - "question": "What are the platforms supported by x" - }], "category": ["Games"] } ] @@ -494,27 +300,11 @@ "properties": [ { "property": "P287", - "template": - [{ - "lang": "es", - "question": "Quién es el diseñador de la marca x" - }, - { - "lang": "en", - "question": "Who is the designer of the brand x" - }], - "category": ["Fashion"] - }, - { - "property": "P571", - "template": [{ - "lang": "es", - "question": "Cuándo fue fundada la casa de moda x" - }, - { - "lang": "en", - "question": "When was the fashion house founded x" - }], + "template": { + "es": "Quién es el diseñador de la marca x", + "en": "Who is the designer of the brand x", + "fr": "Qui est le designer de la marque x" + }, "category": ["Fashion"] } ] @@ -525,48 +315,22 @@ "properties": [ { "property": "P2973", - "template": - [{ - "lang": "es", - "question": "Cuál es el hábitat natural de la especie x" - }, - { - "lang": "en", - "question": "What is the natural habitat of species x" - }], + "template": { + "es": "Cuál es el hábitat natural de la especie x", + "en": "What is the natural habitat of species x", + "fr": "Quel est l'habitat naturel de l'espèce x" + }, "category": ["Animals"] }, { "property": "P2670", - "template": [{ - "lang": "es", - "question": "Qué especies comparten el hábitat con x" - }, - { - "lang": "en", - "question": "What species share the habitat with x" - }], + "template": { + "es": "Qué especies comparten el hábitat con x", + "en": "What species share the habitat with x", + "fr": "Quelles espèces partagent l'habitat avec x" + }, "category": ["Animals"] } ] - }, - { - "name": "brands", - "instance": "Q431289", - "properties": [ - { - "property": "P571", - "template": - [{ - "lang": "es", - "question": "Cuándo fue fundada la marca x" - }, - { - "lang": "en", - "question": "When was the brand x founded?" - }], - "category": ["Brands"] - } - ] } -] \ No newline at end of file +] diff --git a/webapp/public/index.html b/webapp/public/index.html index 77cb33c0..74761f2c 100644 --- a/webapp/public/index.html +++ b/webapp/public/index.html @@ -15,29 +15,10 @@ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> - WIQ - Wikidata Infinite Quest
- diff --git a/webapp/src/__tests__/components/NavBar.test.js b/webapp/src/__tests__/components/NavBar.test.js index cd547778..8d400574 100644 --- a/webapp/src/__tests__/components/NavBar.test.js +++ b/webapp/src/__tests__/components/NavBar.test.js @@ -121,7 +121,7 @@ describe('NavBar component', () => { ); // Checks select menu is in the nav - const selectLang = screen.getByText("English"); + const selectLang = screen.getAllByText("English")[0]; await expect(selectLang).toBeInTheDocument(); // Click on it and check both options are there diff --git a/webapp/src/__tests__/pages/GroupDetails.test.js b/webapp/src/__tests__/pages/GroupDetails.test.js index 8add14e0..507e5368 100644 --- a/webapp/src/__tests__/pages/GroupDetails.test.js +++ b/webapp/src/__tests__/pages/GroupDetails.test.js @@ -1,12 +1,12 @@ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, screen } from '@testing-library/react'; import { BrowserRouter as Router, useParams } from 'react-router-dom'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import GroupDetails from '../../pages/GroupDetails'; import '../../localize/i18n'; import { SessionContext } from '../../SessionContext'; - + const mockAxios = new MockAdapter(axios); jest.mock('react-router-dom', () => ({ @@ -51,6 +51,23 @@ describe('GroupDetails component', () => { }); }); + it('debe reproducir el video a una velocidad de 0.85', async () => { + render( + + + + + + ); + + await waitFor(() => { + const videoElement = screen.getByTestId('video'); + expect(videoElement).toBeInTheDocument(); + expect(videoElement.playbackRate).toBe(0.85); + }); + + }); + it('should render error message when failed to fetch group information', async () => { mockAxios.onGet(`http://localhost:8000/group/NonExistentGroup`).reply(404); diff --git a/webapp/src/__tests__/pages/Groups.test.js b/webapp/src/__tests__/pages/Groups.test.js index 1b131899..3f90b486 100644 --- a/webapp/src/__tests__/pages/Groups.test.js +++ b/webapp/src/__tests__/pages/Groups.test.js @@ -31,6 +31,14 @@ describe('Groups component', () => { }); }; + it('debe reproducir el video a una velocidad de 0.85', async () => { + renderGroupsComponent(); + const videoElement = screen.getByTestId('video'); + expect(videoElement).toBeInTheDocument(); + expect(videoElement.playbackRate).toBe(0.85); + + }); + it('should render groups list and creation elements', async () => { // It mocks a succesful request getting two groups from the database. mockAxios.onGet('http://localhost:8000/group').reply(200, { groups: [{ name: 'Group 1' }, { name: 'Group 2' }] }); diff --git a/webapp/src/__tests__/pages/Profile.test.js b/webapp/src/__tests__/pages/Profile.test.js index 3f11cc61..3872bbd7 100644 --- a/webapp/src/__tests__/pages/Profile.test.js +++ b/webapp/src/__tests__/pages/Profile.test.js @@ -117,7 +117,7 @@ describe('Profile component', () => { fireEvent.click(screen.getByTestId('andina-button')); fireEvent.click(screen.getByTestId('samu-button')); fireEvent.click(screen.getByTestId('barrero-button')); - fireEvent.click(screen.getByTestId('maite-button')); + fireEvent.click(screen.getByTestId('teresa-button')); fireEvent.click(screen.getByTestId('confirm-button')); await waitFor(() => { diff --git a/webapp/src/__tests__/pages/Ranking.test.js b/webapp/src/__tests__/pages/Ranking.test.js index 76a32d0f..bebeab4c 100644 --- a/webapp/src/__tests__/pages/Ranking.test.js +++ b/webapp/src/__tests__/pages/Ranking.test.js @@ -32,6 +32,17 @@ describe('Ranking Component', () => { }); + it('debe reproducir el video a una velocidad de 0.85', async () => { + render(); + + setTimeout(() => { + const videoElement = screen.getByTestId('video'); + expect(videoElement).toBeInTheDocument(); + expect(videoElement.playbackRate).toBe(0.85); + }, 15); + + }); + it('should display group ranking when "GROUPS" button is clicked', async () => { const groupRanking = { data: { rank: [ diff --git a/webapp/src/__tests__/pages/Statistics.test.js b/webapp/src/__tests__/pages/Statistics.test.js index ed24cbd5..9600c720 100644 --- a/webapp/src/__tests__/pages/Statistics.test.js +++ b/webapp/src/__tests__/pages/Statistics.test.js @@ -136,6 +136,20 @@ describe('Statistics component', () => { expect(screen.getByText('2')).toBeInTheDocument(); }); + it('debe reproducir el video a una velocidad de 0.85', async () => { + render( + + + + + + ); + const videoElement = screen.getByTestId('video'); + expect(videoElement).toBeInTheDocument(); + expect(videoElement.playbackRate).toBe(0.85); + + }); + it('should render Statistics component with correct user statistics for Wise Men Stack mode', async () => { render( diff --git a/webapp/src/components/NavBar.js b/webapp/src/components/NavBar.js index 1de1d56d..a4a81f98 100644 --- a/webapp/src/components/NavBar.js +++ b/webapp/src/components/NavBar.js @@ -103,6 +103,15 @@ function NavBar() { ))} + + + + { logo } @@ -130,8 +139,9 @@ function NavBar() { )} - {/* Internacionalization */} - + + {/* Internacionalization */} + - {isLoggedIn ? ( <> diff --git a/webapp/src/data/gameInfo.json b/webapp/src/data/gameInfo.json index 012e3110..8808363f 100644 --- a/webapp/src/data/gameInfo.json +++ b/webapp/src/data/gameInfo.json @@ -2,16 +2,16 @@ { "nombre": "Wise Men Stack", "descripcion": - "The player chooses a topic from five available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they move on to the next question.", + "The player chooses a topic from the available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they move on to the next question.", "foto": "../instructions/foto0.png", - "card":"The player chooses a topic from five available options and must answer a battery of questions related to it within 60 seconds.", + "card":"The player chooses a topic from three available options and must answer a battery of questions related to it within 60 seconds.", "cardFoto": "../homePage/foto0.png" }, { "nombre": "Warm Question", - "descripcion": "It consists of ten topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly.", + "descripcion": "It consists of some topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly.", "foto": "../instructions/foto1.jpg", - "card":"It consists of ten topics of varied themes.", + "card":"It consists of ten questions of varied themes.", "cardFoto": "../homePage/foto1.jpg" }, { diff --git a/webapp/src/data/icons.js b/webapp/src/data/icons.js index 2da14712..dcd5fb63 100644 --- a/webapp/src/data/icons.js +++ b/webapp/src/data/icons.js @@ -22,8 +22,8 @@ const getBarrero = () => { return "barreroIcon.jpg"; } -const getMaite = () => { +const getTeresa = () => { return "teresaIcon.jpg"; } -export { getHugo, getAlberto, getWiffo, getAndina, getSamu, getBarrero, getMaite }; \ No newline at end of file +export { getHugo, getAlberto, getWiffo, getAndina, getSamu, getBarrero, getTeresa }; \ No newline at end of file diff --git a/webapp/src/index.css b/webapp/src/index.css index d0d08a2c..5bcaa367 100644 --- a/webapp/src/index.css +++ b/webapp/src/index.css @@ -1,4 +1,5 @@ body { + background-image: url('../public/hurta2.jpg'); background-size: cover; background-repeat: no-repeat; margin: 0; diff --git a/webapp/src/localize/en.json b/webapp/src/localize/en.json index 9af93067..b2845352 100644 --- a/webapp/src/localize/en.json +++ b/webapp/src/localize/en.json @@ -73,7 +73,12 @@ "categories": { "geography": "Geography", "political":"Political", - "sports":"Sports" + "sports":"Sports", + "cities": "Cities", + "art": "Art", + "entertainment": "Entertainment", + "games": "Games", + "animals": "Animals" } }, diff --git a/webapp/src/localize/es.json b/webapp/src/localize/es.json index b37511e8..3ecabaef 100644 --- a/webapp/src/localize/es.json +++ b/webapp/src/localize/es.json @@ -73,7 +73,12 @@ "categories": { "geography": "Geografía", "political":"Política", - "sports":"Deporte" + "sports":"Deporte", + "cities": "Ciudades", + "art": "Arte", + "entertainment": "Entretenimiento", + "games": "Juegos", + "animals": "Animales" } }, @@ -125,7 +130,7 @@ "title": "ESTADÍSTICAS", "game": "Partida", "table": { - "money": "Puntos Ganado", + "money": "Puntos Ganados", "questions_corr": "Preguntas Respondidas Correctamente", "questions_incorr": "Preguntas Respondidas Incorrectamente", "questions_pass": "Preguntas Pasadas", diff --git a/webapp/src/localize/fr.json b/webapp/src/localize/fr.json index 0eecd49c..ae89b9b7 100644 --- a/webapp/src/localize/fr.json +++ b/webapp/src/localize/fr.json @@ -72,9 +72,14 @@ "category": "Catégorie" }, "categories": { - "password": "Mot de passe", - "button": "Se connecter", - "register_link": "Vous n'avez pas de compte ? Inscrivez-vous ici." + "geography": "Gréographie", + "political":"Politique", + "sports":"Sport", + "cities": "Villes", + "art": "Art", + "entertainment": "Divertissement", + "games": "Jeux", + "animals": "Animaux" } }, @@ -86,7 +91,10 @@ "Login": { "title": "Connecter", - "username": "Nom d'utilisateur" + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "button": "Se connecter", + "register_link": "Vous n'avez pas de compte ? Inscrivez-vous ici." }, "Register": { diff --git a/webapp/src/pages/GroupDetails.js b/webapp/src/pages/GroupDetails.js index 621fa121..f84a2ff5 100644 --- a/webapp/src/pages/GroupDetails.js +++ b/webapp/src/pages/GroupDetails.js @@ -35,9 +35,31 @@ const GroupDetails = () => { navigate(`/statistics/${name}`); }; + //Video settings + const styles = { + video: { + position: "absolute", + top: "50%", + left: "50%", + width: "100%", + height: "100%", + transform: "translate(-50%, -50%)", + objectFit: "cover", + zIndex: '-1', + userSelect: 'none', + pointerEvents: 'none' + }, + }; + + const videoRef = React.useRef(null); + React.useEffect(() => {if (videoRef.current) {videoRef.current.playbackRate = 0.85;}}, []); + if (error || !groupInfo) { return ( + {error} ) @@ -50,6 +72,9 @@ const GroupDetails = () => { // Returns all group data including the creator, the creation date and the members list return ( + {groupInfo.name} diff --git a/webapp/src/pages/Groups.js b/webapp/src/pages/Groups.js index e30383fd..25b28419 100644 --- a/webapp/src/pages/Groups.js +++ b/webapp/src/pages/Groups.js @@ -95,8 +95,30 @@ const Groups = () => { navigate(`/group/${groupName}`); }; + //Video settings + const styles = { + video: { + position: "absolute", + top: "50%", + left: "50%", + width: "100%", + height: "100%", + transform: "translate(-50%, -50%)", + objectFit: "cover", + zIndex: '-1', + userSelect: 'none', + pointerEvents: 'none' + }, + }; + const videoRef = React.useRef(null); + React.useEffect(() => {if (videoRef.current) {videoRef.current.playbackRate = 0.85;}}, []); + return ( + + { t("Groups.title") } diff --git a/webapp/src/pages/Instructions.js b/webapp/src/pages/Instructions.js index 0308bef6..c2f22c68 100644 --- a/webapp/src/pages/Instructions.js +++ b/webapp/src/pages/Instructions.js @@ -54,8 +54,6 @@ const Instructions = () => { pointerEvents: 'none', top:'0', left:'0', - opacity:'0.5', - }, gameDisplayRow:{ diff --git a/webapp/src/pages/Profile.js b/webapp/src/pages/Profile.js index e7bdb2db..685eba50 100644 --- a/webapp/src/pages/Profile.js +++ b/webapp/src/pages/Profile.js @@ -2,7 +2,7 @@ import React, { useEffect, useState, useCallback, useContext } from 'react'; import axios from 'axios'; import { Button, Container, Typography, Divider, Snackbar } from '@mui/material'; import { SessionContext } from '../SessionContext'; -import { getHugo, getAlberto, getWiffo, getAndina, getSamu, getBarrero, getMaite } from '../data/icons'; +import { getHugo, getAlberto, getWiffo, getAndina, getSamu, getBarrero, getTeresa } from '../data/icons'; import { useTranslation } from 'react-i18next'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -83,7 +83,7 @@ const Profile = () => { + - diff --git a/webapp/src/pages/Ranking.js b/webapp/src/pages/Ranking.js index a26ec4c5..b231dbab 100644 --- a/webapp/src/pages/Ranking.js +++ b/webapp/src/pages/Ranking.js @@ -47,6 +47,21 @@ const Ranking = () => { { field: 'totalGamesPlayed', headerName: 'TOTAL GAMES', flex: 1, align: 'center', headerAlign: 'center' } ]; + //Video settings + const styles = { + video:{ + position: "absolute", + width: "100%", + height: "100%", + objectFit: "cover", + zIndex:'-1', + userSelect:'none', + pointerEvents: 'none' + }, + }; + const videoRef = React.useRef(null); + React.useEffect(() => {if (videoRef.current) {videoRef.current.playbackRate = 0.85;}}, []); + return ( { flexGrow: 1 }}> +< video data-testid="video" ref={videoRef} autoPlay muted loop style={{ ...styles.video}}> + + + RANKING diff --git a/webapp/src/pages/Statistics.js b/webapp/src/pages/Statistics.js index 79b90b51..83b6b14c 100644 --- a/webapp/src/pages/Statistics.js +++ b/webapp/src/pages/Statistics.js @@ -273,8 +273,30 @@ const Statistics = () => { } }; + //Video settings + const styles = { + video: { + position: "absolute", + top: "50%", + left: "50%", + width: "100%", + height: "100%", + transform: "translate(-50%, -50%)", + objectFit: "cover", + zIndex: '-1', + userSelect: 'none', + pointerEvents: 'none' + }, + }; + + const videoRef = React.useRef(null); + React.useEffect(() => {if (videoRef.current) {videoRef.current.playbackRate = 0.85;}}, []); + return ( + { t("Statistics.title") } diff --git a/webapp/src/pages/games/MultiplayerRoom.js b/webapp/src/pages/games/MultiplayerRoom.js index 423db081..efc201d6 100644 --- a/webapp/src/pages/games/MultiplayerRoom.js +++ b/webapp/src/pages/games/MultiplayerRoom.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useTheme, Button, TextField, Typography, Grid, Paper, List, ListItem, CircularProgress, Container, Box } from '@mui/material'; +import { useTheme, Button, TextField, Typography, Paper, List, ListItem, CircularProgress, Container, Box } from '@mui/material'; import io from 'socket.io-client'; import { useContext } from 'react'; import { SessionContext } from '../../SessionContext'; @@ -106,41 +106,49 @@ const MultiplayerRoom = () => { } return ( - - + + {roomCode && error === "" && ( + + )} + {t("Games.Multiplayer.name").toUpperCase()} {roomCode && error === "" ? ( - <> - - { t("Multiplayer.Room.code") }: - - - {roomCode} - - - - { t("Multiplayer.Room.participants") }: - - - {roomPlayers.map((player, index) => ( - - {player} - - ))} - - - {loadingQuestions && ( -
- - Loading questions... -
- )} - + + + + { t("Multiplayer.Room.code") }: + + + {roomCode} + + + + + { t("Multiplayer.Room.participants") }: + + + {roomPlayers.map((player, index) => ( + + {player} + + ))} + + + {loadingQuestions && ( +
+ + Loading questions... +
+ )} +
+
) : ( { )} - {roomCode && error === "" && ( - - - - )}
-
+ + {roomCode && error === "" && ( + + + + )} +
); } diff --git a/webapp/src/pages/games/TheChallengeGame.js b/webapp/src/pages/games/TheChallengeGame.js index 8ea6bf80..d9c9c440 100644 --- a/webapp/src/pages/games/TheChallengeGame.js +++ b/webapp/src/pages/games/TheChallengeGame.js @@ -279,6 +279,11 @@ const TheChallengeGame = () => { {t("Game.categories.geography")} {t("Game.categories.political")} {t("Game.categories.sports")} + {t("Game.categories.cities")} + {t("Game.categories.art")} + {t("Game.categories.entertainment")} + {t("Game.categories.games")} + {t("Game.categories.animals")}
@@ -353,7 +358,7 @@ const TheChallengeGame = () => { : // Cronómetro - selectResponse(-1, "FAILED")}> {({ remainingTime }) => { return ( diff --git a/webapp/src/pages/games/WiseMenStackGame.js b/webapp/src/pages/games/WiseMenStackGame.js index f056c511..7fab6f37 100644 --- a/webapp/src/pages/games/WiseMenStackGame.js +++ b/webapp/src/pages/games/WiseMenStackGame.js @@ -255,6 +255,11 @@ const WiseMenStackGame = () => { {t("Game.categories.geography")} {t("Game.categories.political")} {t("Game.categories.sports")} + {t("Game.categories.cities")} + {t("Game.categories.art")} + {t("Game.categories.entertainment")} + {t("Game.categories.games")} + {t("Game.categories.animals")}