From 5b0634e3ab1abb32ef564eca0b3b2cd39b9c77cb Mon Sep 17 00:00:00 2001 From: Abel Date: Mon, 8 Apr 2024 04:02:11 +0200 Subject: [PATCH 1/3] Fixed small bug on WikidataExtractor Endpoint not waiting for the results before returning a message --- questionsservice/wikidataExtractor/wikidataextractor-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/questionsservice/wikidataExtractor/wikidataextractor-service.js b/questionsservice/wikidataExtractor/wikidataextractor-service.js index 3586e4d2..9d7084d1 100644 --- a/questionsservice/wikidataExtractor/wikidataextractor-service.js +++ b/questionsservice/wikidataExtractor/wikidataextractor-service.js @@ -55,7 +55,7 @@ cron.schedule(`*/${minutes} * * * *`, () => { // Route for extracting countries app.get('/extract', async (req, res) => { try { - res.json(extractData()); + res.json(await extractData()); } catch (error) { res.status(500).json({ message: error.message }) // res.status(500).json({ error: 'Internal Server Error' }); From 255ef288552d987d3d06f8352ccb9c5aabd7d691 Mon Sep 17 00:00:00 2001 From: Abel Date: Mon, 8 Apr 2024 04:09:12 +0200 Subject: [PATCH 2/3] Question generator service major upgrade: Moved all the logic to an external file (questiongenerator.js), implemented Templates and the logic to generate different types of questions with multiple configuration params (number of questions, answers per question, topic...) --- .../questiongenerator-service.js | 63 +++---------- .../questiongenerator.js | 88 +++++++++++++++++++ 2 files changed, 102 insertions(+), 49 deletions(-) create mode 100644 questionsservice/questiongeneratorservice/questiongenerator.js diff --git a/questionsservice/questiongeneratorservice/questiongenerator-service.js b/questionsservice/questiongeneratorservice/questiongenerator-service.js index ca384971..1985174a 100644 --- a/questionsservice/questiongeneratorservice/questiongenerator-service.js +++ b/questionsservice/questiongeneratorservice/questiongenerator-service.js @@ -2,7 +2,7 @@ const express = require('express'); const cors = require('cors'); const axios = require('axios'); const mongoose = require('mongoose'); -const { Pais } = require('./questiongenerator-model') +const { QuestionGenerator } = require('./questiongenerator') const app = express(); const port = 8007; @@ -22,68 +22,33 @@ 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()); -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; -} - -async function generateQuestion() { - var elementos = await Pais.find({ capital: { $exists: true } }).exec(); - console.log("Find:\n" + elementos) - elementos = shuffle(elementos); - var mockedQuestions = [{ - pregunta: "¿Cual es la capital de " + elementos[0].pais + "?", - respuesta_correcta: elementos[0].capital, - respuestas_incorrectas: [elementos[1].capital, elementos[2].capital, elementos[3].capital] - }]; - console.log(mockedQuestions); - return mockedQuestions -} +// Only parse query parameters into strings, not objects (adds security) +app.set('query parser', 'simple'); // 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 temas = String(tema); + const preguntas = req.query.n_preguntas || 1; + const respuestas = req.query.n_respuestas || 4; + var temas = req.query.tema || []; + if (!Array.isArray(temas)) { + temas = Array.of(temas); + } - // if (isNaN(preguntas)) { - // generateQuestion() - // console.log("merda", mockedQuestions) - // return mockedQuestions.slice(0, 4); - // } - // const response = []; - // generateQuestion(); - // for (let i = 0; i < preguntas; i++) { - // response.push(mockedQuestions[i % 11]); - // } - return await generateQuestion(); + return await QuestionGenerator.generateQuestions(preguntas, respuestas, temas); } // 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 retQuestions = await getQuestions(req); try{ - const questionsHistoryResponse = await axios.post(questionHistoryServiceUrl + '/history/questions', defaultQuestion); + const questionsHistoryResponse = await axios.post(questionHistoryServiceUrl + '/history/questions', retQuestions); } catch (error) { console.error(`Error saving questions history: ${error}`); } - res.json(defaultQuestion); + res.json(retQuestions); } catch (error) { - // res.status(500).json({ message: error.message }) + console.error(`An error occurred: ${error.message}`) res.status(500).json({ error: 'Internal Server Error' }); } }); diff --git a/questionsservice/questiongeneratorservice/questiongenerator.js b/questionsservice/questiongeneratorservice/questiongenerator.js new file mode 100644 index 00000000..1b180ccc --- /dev/null +++ b/questionsservice/questiongeneratorservice/questiongenerator.js @@ -0,0 +1,88 @@ +const { Pais } = require('./questiongenerator-model') + +class QuestionGenerator { + + static temas = new Map([ + ["paises", [0, 1, 2]], + ['capital', [0]], + ["lenguaje", [1]] + ]); + ; + + static plantillas = [ + { + pregunta: (param) => `¿Cual es la capital de ${param}?`, + filtro: { capital: { $exists: true } }, + campo_pregunta: 'pais', + campo_respuesta: 'capital' + }, + { + pregunta: (param) => `¿Qué lengua se habla en ${param}?`, + filtro: { lenguaje: { $exists: true } }, + campo_pregunta: 'pais', + campo_respuesta: 'lenguaje' + } + // { + // pregunta: (param) => `¿Cuál es la bandera de ${param}?`, + // filtro: { bandera: { $exists: true } }, + // campo_pregunta: 'pais', + // campo_respuesta: 'bandera' + // } + ]; + + static async generateQuestion(plantilla, respuestas) { + console.log("\nPlantilla:"); + console.log(plantilla); + + const randomDocs = await Pais.aggregate([ + { $match: plantilla.filtro }, + { $sample: { size: Number(respuestas) } } + ]); + console.log("\nFind:"); + console.log(randomDocs); + + var retQuestion = { + pregunta: plantilla.pregunta(randomDocs[0][plantilla.campo_pregunta]), + respuesta_correcta: randomDocs[0][plantilla.campo_respuesta], + respuestas_incorrectas: Array.from({ length: respuestas-1 }, (_, i) => randomDocs[i+1][plantilla.campo_respuesta]) + }; + console.log("\nPregunta generada:"); + console.log(retQuestion); + + return retQuestion; + } + + static async generateQuestions(preguntas, respuestas, temas) { + console.log(temas); + const plantillasDisponibles = this.getAvailableTemplates(temas); + console.log(plantillasDisponibles); + var retQuestions = []; + for (let i = 0; i < preguntas; i++) { + let index = Math.floor(Math.random() * plantillasDisponibles.length); + retQuestions.push(await this.generateQuestion(this.plantillas[plantillasDisponibles[index]], respuestas)); + } + return retQuestions; + } + + static getAvailableTemplates(temas) { + if (temas.length == 0) { + return Array.from({ length: this.plantillas.length }, (_, i) => i); + } + var templates = []; + temas.forEach(tema => { + console.log(tema); + if (this.temas.has(tema)) { + templates = templates.concat(this.temas.get(tema)); + console.log(this.temas.get(tema)); + } + }); + console.log(templates); + console.log([...new Set(templates)]); + return [...new Set(templates)]; + } + +} + +module.exports = { + QuestionGenerator +}; \ No newline at end of file From e697ee1d3ada61a298a55cfdc69f3bce7d6ac20e Mon Sep 17 00:00:00 2001 From: Abel Date: Fri, 12 Apr 2024 00:12:04 +0200 Subject: [PATCH 3/3] Added exceptions and param validation --- .../questiongenerator-service.js | 43 ++++++++++++------- .../questiongenerator.js | 15 ++++++- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/questionsservice/questiongeneratorservice/questiongenerator-service.js b/questionsservice/questiongeneratorservice/questiongenerator-service.js index 1985174a..21c8721d 100644 --- a/questionsservice/questiongeneratorservice/questiongenerator-service.js +++ b/questionsservice/questiongeneratorservice/questiongenerator-service.js @@ -25,31 +25,44 @@ app.use(cors()); // Only parse query parameters into strings, not objects (adds security) app.set('query parser', 'simple'); -// Function to generate the required number of questions -async function getQuestions(req) { - const preguntas = req.query.n_preguntas || 1; - const respuestas = req.query.n_respuestas || 4; - var temas = req.query.tema || []; +function validateNumberInQuery(number, minValue, paramName, defValue) { + if (!(paramName in number)) return defValue; + n = Number(number[paramName]); + if (isNaN(n)) throw new Error(`A number was expected in param \'${paramName}\'`); + if (n < minValue) throw new Error(`\'${paramName}\' must be at least \'${minValue}\'`); + return n; +} + +// Function to validate required fields in the request body +function validateFields(query) { + const preguntas = validateNumberInQuery(query, 1, 'n_preguntas', 1); + const respuestas = validateNumberInQuery(query, 1, 'n_respuestas', 4); + var temas = query.tema || []; if (!Array.isArray(temas)) { temas = Array.of(temas); - } - - return await QuestionGenerator.generateQuestions(preguntas, respuestas, temas); + } + return { preguntas, respuestas, temas }; } // Route for getting questions app.get('/questions', async (req, res) => { try { - const retQuestions = await getQuestions(req); - try{ - const questionsHistoryResponse = await axios.post(questionHistoryServiceUrl + '/history/questions', retQuestions); + const { preguntas, respuestas, temas } = validateFields(req.query); + try { + const retQuestions = await QuestionGenerator.generateQuestions(preguntas, respuestas, temas); + try { + await axios.post(questionHistoryServiceUrl + '/history/questions', retQuestions); + } catch (error) { + console.error(`Error saving questions history: ${error}`); + } + res.json(retQuestions); } catch (error) { - console.error(`Error saving questions history: ${error}`); + console.error(`An error occurred: ${error.message}`); + res.status(500).json({ error: 'Internal Server Error' }); } - res.json(retQuestions); } catch (error) { - console.error(`An error occurred: ${error.message}`) - res.status(500).json({ error: 'Internal Server Error' }); + console.error(`Bad Request: ${error.message}`); + res.status(400).json({ message: error.message }); } }); diff --git a/questionsservice/questiongeneratorservice/questiongenerator.js b/questionsservice/questiongeneratorservice/questiongenerator.js index 1b180ccc..89206f31 100644 --- a/questionsservice/questiongeneratorservice/questiongenerator.js +++ b/questionsservice/questiongeneratorservice/questiongenerator.js @@ -36,8 +36,13 @@ class QuestionGenerator { const randomDocs = await Pais.aggregate([ { $match: plantilla.filtro }, - { $sample: { size: Number(respuestas) } } + { $sample: { size: respuestas } } ]); + if (randomDocs.length < respuestas) { + console.error(`Not enought data found to generate a question`); + throw new Error(`Not enought data found to generate a question`); + } + console.log("\nFind:"); console.log(randomDocs); @@ -75,7 +80,15 @@ class QuestionGenerator { templates = templates.concat(this.temas.get(tema)); console.log(this.temas.get(tema)); } + else { + console.error(`The topic \'${tema}\' is not currently defined`); + throw new Error(`The topic \'${tema}\' is not currently defined`); + } }); + if (templates.length == 0) { + console.error(`No correct topics were passed`); + throw new Error(`No correct topics were passed`); + } console.log(templates); console.log([...new Set(templates)]); return [...new Set(templates)];