diff --git a/questionsservice/questiongeneratorservice/questiongenerator-service.js b/questionsservice/questiongeneratorservice/questiongenerator-service.js new file mode 100644 index 00000000..b59b0c80 --- /dev/null +++ b/questionsservice/questiongeneratorservice/questiongenerator-service.js @@ -0,0 +1,170 @@ +const express = require('express'); +const cors = require('cors'); +const axios = require('axios'); +const mongoose = require('mongoose'); +const { Question } = require('./questiongenerator-model') + +const app = express(); +const port = 8006; + +const questionHistoryServiceUrl = process.env.STORE_QUESTION_SERVICE_URL || 'http://localhost:8004'; + +const WikiQueries = require('./wikidataExtractor/wikidataQueries'); +// Connect to MongoDB +const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/questions'; +mongoose.connect(mongoUri); +const db = mongoose.connection; +db.on('error', (error) => console.error(`MongoDB connection error: ${error}`)); +db.once('open', () => console.log("Connected to MongoDB: %s", mongoUri)); + +// Middleware to parse JSON in request body +app.use(express.json()); + +// Middleware to enable CORS (cross-origin resource sharing). In order for the API to be accessible by other origins (domains). +app.use(cors()); + +var mockedQuestions = []; +var isWikiChecked = false; +var elementos; + +function shuffle(array) { + let currentIndex = array.length; + let randomIndex; + + // Mientras queden elementos para mezclar. + while (currentIndex > 0) { + // Escoge un elemento aleatorio. + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + // Intercambia el elemento actual con el elemento aleatorio. + [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; + } + + return array; +} + +const generateQuestion = async () => { + if (!isWikiChecked) { + elementos = await WikiQueries.obtenerPaisYCapital(); + isWikiChecked = true; + } + elementos = shuffle(elementos); + mockedQuestions = [{ + pregunta: "¿Cual es la capital de " + elementos[0].countryLabel + "?", + respuesta_correcta: elementos[0].capitalLabel, + respuestas_incorrectas: [elementos[1].capitalLabel, elementos[2].capitalLabel, elementos[3].capitalLabel] + }]; + console.log(mockedQuestions); +} + + +// const mockedQuestions = [ +// { +// pregunta: "¿Cómo me llamo?", +// respuesta_correcta: "Abel", +// respuestas_incorrectas: ["Federico", "Eusebio", "Gervasio"] +// }, +// { +// pregunta: "¿Cuál es el río más largo del mundo?", +// respuesta_correcta: "Amazonas", +// respuestas_incorrectas: ["Nilo", "Misisipi", "Yangtsé"] +// }, +// { +// pregunta: "¿En qué año comenzó la Segunda Guerra Mundial?", +// respuesta_correcta: "1939", +// respuestas_incorrectas: ["1941", "1942", "1945"] +// }, +// { +// pregunta: "¿Quién escribió 'El Quijote'?", +// respuesta_correcta: "Miguel de Cervantes", +// respuestas_incorrectas: ["Garcilaso de la Vega", "Federico García Lorca", "Pablo Neruda"] +// }, +// { +// pregunta: "¿Cuál es el símbolo químico del oro?", +// respuesta_correcta: "Au", +// respuestas_incorrectas: ["Ag", "Fe", "Cu"] +// }, +// { +// pregunta: "¿Cuál es el planeta más grande del sistema solar?", +// respuesta_correcta: "Júpiter", +// respuestas_incorrectas: ["Saturno", "Marte", "Venus"] +// }, +// { +// pregunta: "¿Quién pintó la 'Mona Lisa'?", +// respuesta_correcta: "Leonardo da Vinci", +// respuestas_incorrectas: ["Pablo Picasso", "Vincent van Gogh", "Rembrandt"] +// }, +// { +// pregunta: "¿En qué país se encuentra la Torre Eiffel?", +// respuesta_correcta: "Francia", +// respuestas_incorrectas: ["Italia", "España", "Alemania"] +// }, +// { +// pregunta: "¿Qué año marcó el fin de la Segunda Guerra Mundial?", +// respuesta_correcta: "1945", +// respuestas_incorrectas: ["1943", "1944", "1946"] +// }, +// { +// pregunta: "¿Quién escribió 'Romeo y Julieta'?", +// respuesta_correcta: "William Shakespeare", +// respuestas_incorrectas: ["Jane Austen", "Charles Dickens", "F. Scott Fitzgerald"] +// }, +// { +// pregunta: "¿Qué inventó Thomas Edison?", +// respuesta_correcta: "Bombilla eléctrica", +// respuestas_incorrectas: ["Teléfono", "Automóvil", "Avión"] +// } +// ] + +// Function to generate the required number of questions +async function getQuestions(req) { + const { n_preguntas, n_respuestas, tema } = req.query; + var preguntas = Number(n_preguntas); + var respuestas = Number(n_respuestas); + var temad = String(tema); + + // if (isNaN(preguntas)) { + // generateQuestion() + // console.log("merda", mockedQuestions) + // return mockedQuestions.slice(0, 4); + // } + // const response = []; + await generateQuestion(); + // for (let i = 0; i < preguntas; i++) { + // response.push(mockedQuestions[i % 11]); + // } + return mockedQuestions; +} + +// Route for getting questions +app.get('/questions', async (req, res) => { + try { + // TODO: Implement logic to fetch questions from MongoDB and send response + // const questions = await Question.find() + const defaultQuestion = await getQuestions(req); + + const questionsHistoryResponse = await axios.post(questionHistoryServiceUrl + '/history/questions', defaultQuestion); + res.json(defaultQuestion); + } catch (error) { + // res.status(500).json({ message: error.message }) + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +app.use((err, req, res, next) => { + console.error(`An error occurred: ${err}`); + res.status(500).send(`An error occurred: ${err.message}`); +}); + +// Start the server +const server = app.listen(port, () => { + console.log(`Questions Service listening at http://localhost:${port}`); +}); + +server.on('close', () => { + // Close the Mongoose connection + mongoose.connection.close(); +}); + +module.exports = server diff --git a/storeQuestionService/store-q-service.js b/storeQuestionService/store-q-service.js new file mode 100644 index 00000000..3771937b --- /dev/null +++ b/storeQuestionService/store-q-service.js @@ -0,0 +1,105 @@ +const express = require('express'); +const cors = require('cors'); +const mongoose = require('mongoose'); +const Question = require('./store-q-model'); + +const app = express(); +const port = 8004; + +// Middleware to parse JSON in request body +app.use(express.json()); + +// Middleware to enable CORS (cross-origin resource sharing). In order for the API to be accessible by other origins (domains). +app.use(cors()); + +// Connect to MongoDB +const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/storedquestion'; +mongoose.connect(mongoUri); + +// Function to validate required fields in the request body +function validateRequiredFields(body, requiredFields) { + for (const field of requiredFields) { + if (!(field in body)) { + throw new Error(`Missing required field: ${field}`); + } + } +} + +app.post('/history/question', async (req, res) => { + try { + // Check if required fields are present in the request body + validateRequiredFields(req.body, ['pregunta', 'respuesta_correcta', 'respuestas_incorrectas']); + + const newQuestion = new Question({ + pregunta: req.body.pregunta, + respuesta_correcta: req.body.respuesta_correcta, + respuestas_incorrectas: req.body.respuestas_incorrectas, + createdAt: req.body.createdAt + }); + + await newQuestion.save(); + res.json(newQuestion); + } catch (error) { + res.status(400).json({ error: error.message }); + } +}); + +app.post('/history/questions', async (req, res) => { + try { + // Check if required fields are present in the request body + if (!Array.isArray(req.body)) { + throw new Error('Invalid request format. Expected an array of questions.'); + } + for (const question of req.body) { + validateRequiredFields(question, ['pregunta', 'respuesta_correcta', 'respuestas_incorrectas']); + } + const newQuestions = []; + + for (const questionData of req.body) { + const newQuestion = new Question({ + pregunta: questionData.pregunta, + respuesta_correcta: questionData.respuesta_correcta, + respuestas_incorrectas: questionData.respuestas_incorrectas, + createdAt: questionData.createdAt + }); + + await newQuestion.save(); + newQuestions.push(newQuestion); + } + + res.json(newQuestions); + } catch (error) { + res.status(400).json({ error: error.message }); + } +}); + + +app.get('/history/questions', async (req, res) => { + try { + const questions = await Question.find({}); // Get all questions + res.json(questions); + /*res.json([{ //FORMATO VIEJO + question: '¿Cuál es la capital de la comunidad autónoma de Casstilla y León?', + answers: ['Segovia','León','Valladolid','Ninguna'], + }]);*/ + /*res.json([{ + pregunta: '¿Cuál es la capital de la comunidad autónoma de Castilla y León?', + respuesta_correcta: 'Ninguna', + respuestas_incorrectas: ['Segovia','León','Valladolid'] + }]);*/ + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +const server = app.listen(port, () => { + console.log(`Store questions service listening at http://localhost:${port}`); +}); + +// Listen for the 'close' event on the Express.js server +server.on('close', () => { + // Close the Mongoose connection + mongoose.connection.close(); +}); + +module.exports = server; diff --git a/webapp/src/components/FirstGame.css b/webapp/src/components/FirstGame.css new file mode 100644 index 00000000..c68aafa6 --- /dev/null +++ b/webapp/src/components/FirstGame.css @@ -0,0 +1,35 @@ +button { + font-size: 20px; + width: 200px; + height: 50px; + justify-content: center; + margin-bottom: 15px; +} + +.questionStructure .answers { + display: flex; + justify-content: center; /* Alinea los elementos en el centro horizontal / + align-items: center; / Alinea los elementos en el centro vertical / + height: 100vh; / Ajusta la altura al 100% del viewport */ +} + +.allAnswers { + width:100%; + display: block; +} + +.asnwers { + width:100%; +} + + +.questionFirstGame { + height: 100%; + display: inline-block; +} + +.questionText { + display: flex; + margin-bottom: 25px; + +} \ No newline at end of file diff --git a/webapp/src/components/FirstGame.js b/webapp/src/components/FirstGame.js new file mode 100644 index 00000000..d9ca0222 --- /dev/null +++ b/webapp/src/components/FirstGame.js @@ -0,0 +1,143 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Typography } from '@mui/material'; +import './FirstGame.css'; +import { CircularProgressbar } from 'react-circular-progressbar'; +import 'react-circular-progressbar/dist/styles.css'; +import axios from 'axios'; +import { json } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; + + +const apiEndpoint = 'http://localhost:8007'; +const Quiz = () => { + + const navigation = useNavigate(); // Añade esto + + + var questions = useLocation().state.questions; + + const [currentQuestionIndex, setCurrentQuestionIndex] = React.useState(0); + const [selectedOption, setSelectedOption] = React.useState(null); + const [isCorrect, setIsCorrect] = React.useState(null); + + const esperar = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); + }; + + function secureRandomNumber(max) { + const randomBytes = new Uint32Array(1); + window.crypto.getRandomValues(randomBytes); + return randomBytes[0] % max; + } + + function shuffleArray(array) { + // Crea una copia del array original + const shuffledArray = [...array]; + + // Recorre el array desde el último elemento hasta el primero + for (let i = shuffledArray.length - 1; i > 0; i--) { + // Genera un índice aleatorio entre 0 y el índice actual + //const randomIndex = Math.floor(Math.random() * (i + 1)); + const randomIndex = secureRandomNumber(i + 1); + + // Intercambia el elemento actual con el elemento del índice aleatorio + const temp = shuffledArray[i]; + shuffledArray[i] = shuffledArray[randomIndex]; + shuffledArray[randomIndex] = temp; + } + + // Devuelve el array barajado + return shuffledArray; + } + + + const getQuestions = async () => { + try { + const response = await axios.get(`${apiEndpoint}/questions?n_preguntas=${1}`); + console.log(response.data.length) + for (var i = 0; i < response.data.length; i++) { + var possibleAnswers = [response.data[i].respuesta_correcta, response.data[i].respuestas_incorrectas[0], response.data[i].respuestas_incorrectas[1], response.data[i].respuestas_incorrectas[2]] + possibleAnswers = shuffleArray(possibleAnswers) + questions.push({ + question: response.data[i].pregunta, + options: possibleAnswers, + correctAnswer: response.data[i].respuesta_correcta + }) + } + } catch (error) { + console.error(error); + } + console.log(questions) +}; + + const checkAnswer = async (option) => { + getQuestions() + setIsCorrect(option === questions[currentQuestionIndex].correctAnswer); + setSelectedOption(option); + + const botonIncorrecta = document.getElementById('option-' + questions[currentQuestionIndex].options.indexOf(option)) + if (!isCorrect) { + botonIncorrecta.style.backgroundColor = 'red' + } + + const numberAnswer = questions[currentQuestionIndex].options.indexOf(questions[currentQuestionIndex].correctAnswer) + const botonCorrecta = document.getElementById('option-' + numberAnswer) + botonCorrecta.style.backgroundColor = 'green' + // Pasar a la siguiente pregunta después de responder + + await esperar(2000); // Espera 2000 milisegundos (2 segundos) + botonIncorrecta.style.backgroundColor = 'lightgrey' + botonCorrecta.style.backgroundColor = 'lightgrey' + if (questions.length-1 !== currentQuestionIndex) { + setCurrentQuestionIndex((prevIndex) => prevIndex + 1); + } + setIsCorrect(false) + + + }; + + const goBack = async () => { + navigation('/menu') + } + + return ( + +
+
+ + {questions[currentQuestionIndex].question} + +
+
+ {questions[currentQuestionIndex].options.map((option, index) => ( +
+ +
+ ) + )} +
+ +
+ + {/* {isCorrect !== null && ( +

{isCorrect ? '¡Respuesta correcta!' : 'Respuesta incorrecta.'}

+ )} */} +
+ ); +}; + +export default Quiz; diff --git a/webapp/src/components/Menu.js b/webapp/src/components/Menu.js new file mode 100644 index 00000000..59b05e16 --- /dev/null +++ b/webapp/src/components/Menu.js @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Typography } from '@mui/material'; +import './FirstGame.css'; +import 'react-circular-progressbar/dist/styles.css'; +import axios from 'axios'; +import { json } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; // Importa useHistory + +const apiEndpoint = 'http://localhost:8007'; + +var jsonApi = '' + +var isApiCalledRef = false; + + +const DatosContext = React.createContext(); + +var questions = [] + +function secureRandomNumber(max) { + const randomBytes = new Uint32Array(1); + window.crypto.getRandomValues(randomBytes); + return randomBytes[0] % max; +} + +function shuffleArray(array) { + // Crea una copia del array original + const shuffledArray = [...array]; + + // Recorre el array desde el último elemento hasta el primero + for (let i = shuffledArray.length - 1; i > 0; i--) { + // Genera un índice aleatorio entre 0 y el índice actual + //const randomIndex = Math.floor(Math.random() * (i + 1)); + const randomIndex = secureRandomNumber(i + 1); + + // Intercambia el elemento actual con el elemento del índice aleatorio + const temp = shuffledArray[i]; + shuffledArray[i] = shuffledArray[randomIndex]; + shuffledArray[randomIndex] = temp; + } + + // Devuelve el array barajado + return shuffledArray; +} + +// useEffect (() => { +// if (!isApiCalledRef) { +// getQuestions(); +// isApiCalledRef = true; +// } +// }, []); + + + + +const Menu = () => { + + const [n_preguntas, setn_preguntas] = useState(5); + + const navigation = useNavigate(); // Añade esto + + const initiateGame = async () => { + if (!isApiCalledRef) { + await getQuestions() + } + isApiCalledRef = true + navigation("/firstGame", {state: {questions}}) + } + + const getQuestions = async () => { + try { + setn_preguntas(5) + const response = await axios.get(`${apiEndpoint}/questions?n_preguntas=${n_preguntas}`); + console.log(response.data.length) + for (var i = 0; i < response.data.length; i++) { + var possibleAnswers = [response.data[i].respuesta_correcta, response.data[i].respuestas_incorrectas[0], response.data[i].respuestas_incorrectas[1], response.data[i].respuestas_incorrectas[2]] + possibleAnswers = shuffleArray(possibleAnswers) + questions.push({ + question: response.data[i].pregunta, + options: possibleAnswers, + correctAnswer: response.data[i].respuesta_correcta + }) + } + } catch (error) { + console.error(error); + } + console.log(questions) + }; + + const openStoredQuestions = async () => { + navigation("/appQuestion") + } + + + + return ( + +

Bienvenido a wiq_06c por favor seleccione un modo de juego para comenzar partida:

+ + + + + +
+ ); + +} + + +export default Menu; diff --git a/webapp/src/storeQuestion/App.jsx b/webapp/src/storeQuestion/App.jsx new file mode 100644 index 00000000..7be27bba --- /dev/null +++ b/webapp/src/storeQuestion/App.jsx @@ -0,0 +1,79 @@ +import Question from './components/Question'; +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import './css/questions.css'; +import { useNavigate } from 'react-router-dom'; // Importa useHistory + +function App(){ + /*const newQuestion = { + question: '¿Cuál es tu pregunta?', + answers: ['Respuesta 1', 'Respuesta 2', 'Respuesta 3', 'Respuesta 4'] + }; + + const newQuestion1 = { + question: '¿Cuál es la capital de la comunidad autónoma de Castilla y León?', + answers: ['Segovia','León','Valladolid','Ninguna'], + }; + const newQuestion2 = { + question: '¿Cuál es la capital Italia?', + answers: ['Roma','Nápoles','Florencia','Milán'], + }; + const newQuestion3 = { + question: '¿Cuál es el país mas poblado de la tierra?', + answers: ['China','Estados Unidos','Brazil','India'], + }; + + let preguntas = [newQuestion, newQuestion1, newQuestion2, newQuestion3]*/ + + const [preguntas, setPreguntas] = useState([]); + + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; + + const navigation = useNavigate(); + + useEffect(() => { + + const obtenerPreguntas = async () => { + try { + const response = await axios.get(`${apiEndpoint}/history/questions`) + setPreguntas(response.data); + } catch (error) { + console.error('Error al obtener las preguntas:', error.response.data.error); + } + }; + + obtenerPreguntas(); + //eslint-disable-next-line + }, []); + + const goBack = async () => { + navigation('/menu') + } + + return ( + <> +

Almacén de preguntas

+ +
+ {preguntas.map(question => ( + + ))} +
+ + ); + + /* + + + + + */ +} + +export default App; \ No newline at end of file