diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c474c2..48d5656 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,6 +105,10 @@ jobs: needs: [e2e-tests] steps: - uses: actions/checkout@v4 + - name: Update OpenAPI configuration + run: | + DEPLOY_HOST=${{ secrets.DEPLOY_HOST }} + sed -i "s/SOMEIP/${DEPLOY_HOST}/g" gatewayservice/openapi.yaml - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@v5 with: diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 8ad137f..7a8ba89 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -9,7 +9,6 @@ const port = 8000; const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001'; const authServiceUrl = process.env.AUTH_SERVICE_URL || 'http://localhost:8002'; const questionServiceUrl = process.env.QUESTION_SERVICE_URL || 'http://localhost:8003'; -const statServiceUrl = process.env.STAT_SERVICE_URL || 'http://localhost:8004'; app.use(cors()); app.use(express.json()); @@ -100,6 +99,22 @@ app.get('/getUserData', async (req, res) => { } }); +// Read the OpenAPI YAML file synchronously +openapiPath='./openapi.yaml' +if (fs.existsSync(openapiPath)) { + const file = fs.readFileSync(openapiPath, 'utf8'); + + // Parse the YAML content into a JavaScript object representing the Swagger document + const swaggerDocument = YAML.parse(file); + + // Serve the Swagger UI documentation at the '/api-doc' endpoint + // This middleware serves the Swagger UI files and sets up the Swagger UI page + // It takes the parsed Swagger document as input + app.use('/api-doc', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +} else { + console.log("Not configuring OpenAPI. Configuration file not present.") +} + // Start the gateway service const server = app.listen(port, () => { console.log(`Gateway Service listening at http://localhost:${port}`); diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml new file mode 100644 index 0000000..d75f6a1 --- /dev/null +++ b/gatewayservice/openapi.yaml @@ -0,0 +1,414 @@ +openapi: 3.0.0 +info: + title: Gatewayservice API + description: Gateway OpenAPI specification. + version: 0.1.0 +servers: + - url: http://localhost:8000 + description: Development server + - url: http://SOMEIP:8000 + description: Production server +paths: + /adduser: + post: + summary: Add a new user to the database. + operationId: addUser + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + description: User ID. + example: student + password: + type: string + description: User password. + example: pass + responses: + '200': + description: User added successfully. + content: + application/json: + schema: + type: object + properties: + username: + type: string + description: User ID + password: + type: string + description: Hashed password + example: $2b$10$ZKdNYLWFQxzt5Rei/YTc/OsZNi12YiWz30JeUFHNdAt7MyfmkTuvC + _id: + type: string + description: Identification + example: 65f756db3fa22d227a4b7c7d + createdAt: + type: string + description: Creation date. + example: '2024-03-17T20:47:23.935Z' + ___v: + type: integer + example: '0' + '400': + description: Failed to add user. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: getaddrinfo EAI_AGAIN mongodb + /health: + get: + summary: Check the health status of the service. + operationId: checkHealth + responses: + '200': + description: Service is healthy. + content: + application/json: + schema: + type: object + properties: + status: + type: string + description: Health status. + example: OK + /login: + post: + summary: Log in to the system. + operationId: loginUser + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + description: User ID. + example: student + password: + type: string + description: User password. + example: pass + responses: + '200': + description: Login successful. Returns user token, username, and creation date. + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: User token. + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWY3NTZkYjNmYTIyZDIyN2E0YjdjN2QiLCJpYXQiOjE3MTA3MDg3NDUsImV4cCI6MTcxMDcxMjM0NX0.VMG_5DOyQ4GYlJQRcu1I6ICG1IGzuo2Xuei093ONHxw + username: + type: string + description: Username. + example: student + createdAt: + type: string + description: Creation date. + example: '2024-03-17T20:47:23.935Z' + '401': + description: Invalid credentials. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Shows the error info.. + example: Invalid credentials + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error + /pregunta: + get: + summary: Generate a question. + operationId: pregunta + responses: + '200': + description: Returns the generated question with a right and three incorrect answers. + content: + application/json: + schema: + type: object + properties: + question: + type: string + description: Generated question. + example: ¿Cuál es la capital de España? + answerGood: + type: string + description: Respuesta correcta a la pregunta generada. + example: Madrid + answers: + type: array + description: Respuestas correcta e incorrectas. + items: + type: string + example: + - Madrid + - Abu Dabi + - Washinton D. C. + - Tokyo + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error + /updateCorrectAnswers: + get: + summary: Updates the data of the user, increasing his number of correct answers. + operationId: updateCorrectAnswers + parameters: + in: path + name: params + required: true + description: Parameters for the update (username and numAnswers) + schema: + type: object + properties: + username: + type: string + numAnswers: + type: integer + responses: + '200': + description: Updates the data correctly. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + message: + type: string + example: Respuestas correctas actualizada con éxito + '404': + description: The user is not found. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + example: Usuario no encontrado + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + description: Error information. + example: Error al actualizar las respuestas correctas + /updateIncorrectAnswers: + get: + summary: Updates the data of the user, increasing his number of incorrect answers. + operationId: updateIncorrectAnswers + parameters: + in: path + name: params + required: true + description: Parameters for the update (username and numAnswers) + schema: + type: object + properties: + username: + type: string + numAnswers: + type: integer + responses: + '200': + description: Updates the data correctly. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + message: + type: string + example: Respuestas incorrectas actualizada con éxito + '404': + description: The user is not found. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + example: Usuario no encontrado + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + description: Error information. + example: Error al actualizar las respuestas incorrectas + /updateCompletedGames: + get: + summary: Update the number of completed games by the user. + operationId: updateCompletedGames + parameters: + in: path + name: username + required: true + description: Username for the update of data + schema: + type: string + responses: + '200': + description: Finds the data of the user correctly. + content: + application/json: + schema: + type: user + properties: + success: + type: boolean + example: true + message: + type: string + example: Juegos completados actualizado con éxito + '404': + description: The user is not found. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + example: Usuario no encontrado + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + description: Error information. + example: Error al actualizar Juegos completados + /getUserData: + get: + summary: Gets the data of an user. + operationId: getUserData + parameters: + in: path + name: username + required: true + description: Username for the search of data + schema: + type: string + responses: + '200': + description: Finds the data of the user correctly. + content: + application/json: + schema: + type: user + properties: + username: + type: string + example: Pablo + password: + type: integer + example: pass + createdAt: + type: date + correctAnswers: + type: integer + incorrectAnswers: + type: integer + completedGames: + type: integer + averageTime: + type: integer + '404': + description: The user is not found. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + example: Usuario no encontrado + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: false + message: + type: string + description: Error information. + example: Error al obtener los datos de usuario + diff --git a/questionservice/server.js b/questionservice/server.js index 0b38cac..16884dc 100644 --- a/questionservice/server.js +++ b/questionservice/server.js @@ -14,55 +14,59 @@ const questions = JSON.parse(fs.readFileSync('questions.json', 'utf8')); // Definimos una ruta GET en '/pregunta' app.get('/pregunta', async (req, res) => { - // Seleccionamos una consulta SPARQL de forma aleatoria del fichero de configuración - const questionItem = questions[Math.floor(Math.random() * questions.length)]; + try{ + // Seleccionamos una consulta SPARQL de forma aleatoria del fichero de configuración + const questionItem = questions[Math.floor(Math.random() * questions.length)]; - // URL del endpoint SPARQL de Wikidata - const url = "https://query.wikidata.org/sparql"; - // Consulta SPARQL seleccionada - const query = questionItem.query; + // URL del endpoint SPARQL de Wikidata + const url = "https://query.wikidata.org/sparql"; + // Consulta SPARQL seleccionada + const query = questionItem.query; - // Realizamos la solicitud HTTP GET al endpoint SPARQL con la consulta - const response = await axios.get(url, { params: { format: 'json', query } }); - // Extraemos los resultados de la consulta - const bindings = response.data.results.bindings; + // Realizamos la solicitud HTTP GET al endpoint SPARQL con la consulta + const response = await axios.get(url, { params: { format: 'json', query } }); + // Extraemos los resultados de la consulta + const bindings = response.data.results.bindings; - let wikidataCodePattern = /^Q\d+$/; - let correctAnswer = null; - let correctAnswerIndex = 0; + let wikidataCodePattern = /^Q\d+$/; + let correctAnswer = null; + let correctAnswerIndex = 0; - do { - // Seleccionamos un índice aleatorio para la respuesta correcta - correctAnswerIndex = Math.floor(Math.random() * bindings.length); - // Obtenemos la respuesta correcta - correctAnswer = bindings[correctAnswerIndex]; - } while (wikidataCodePattern.test(correctAnswer.questionSubjectLabel.value)); - - // Creamos la pregunta - console.log(questionItem.question); - const question = questionItem.question.replace('{sujetoPregunta}', correctAnswer.questionSubjectLabel.value); - - // Inicializamos las respuestas con la respuesta correcta - const answerGood = correctAnswer.answerSubjectLabel.value; - const answers = [answerGood]; - // Añadimos tres respuestas incorrectas - for (let i = 0; i < 3; i++) { - let randomIndex; do { - // Seleccionamos un índice aleatorio distinto al de la respuesta correcta - randomIndex = Math.floor(Math.random() * bindings.length); - } while (randomIndex === correctAnswerIndex); - // Añadimos la capital del país seleccionado aleatoriamente a las respuestas - answers.push(bindings[randomIndex].answerSubjectLabel.value); - } - // Mezclamos las respuestas - for (let i = answers.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - // Intercambiamos las respuestas en los índices i y j - [answers[i], answers[j]] = [answers[j], answers[i]]; + // Seleccionamos un índice aleatorio para la respuesta correcta + correctAnswerIndex = Math.floor(Math.random() * bindings.length); + // Obtenemos la respuesta correcta + correctAnswer = bindings[correctAnswerIndex]; + } while (wikidataCodePattern.test(correctAnswer.questionSubjectLabel.value)); + + // Creamos la pregunta + console.log(questionItem.question); + const question = questionItem.question.replace('{sujetoPregunta}', correctAnswer.questionSubjectLabel.value); + + // Inicializamos las respuestas con la respuesta correcta + const answerGood = correctAnswer.answerSubjectLabel.value; + const answers = [answerGood]; + // Añadimos tres respuestas incorrectas + for (let i = 0; i < 3; i++) { + let randomIndex; + do { + // Seleccionamos un índice aleatorio distinto al de la respuesta correcta + randomIndex = Math.floor(Math.random() * bindings.length); + } while (randomIndex === correctAnswerIndex); + // Añadimos la capital del país seleccionado aleatoriamente a las respuestas + answers.push(bindings[randomIndex].answerSubjectLabel.value); + } + // Mezclamos las respuestas + for (let i = answers.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + // Intercambiamos las respuestas en los índices i y j + [answers[i], answers[j]] = [answers[j], answers[i]]; + } + // Enviamos la pregunta y las respuestas como respuesta a la solicitud HTTP + res.status(200).json({ question: question, answerGood: answerGood, answers: answers }); + }catch(error){ + res.status(500).json({ error: 'Internal Server Error' }); } - // Enviamos la pregunta y las respuestas como respuesta a la solicitud HTTP - res.json({ question, answerGood, answers }); }); // Iniciamos el servidor en el puerto 8003 diff --git a/userservice/userservice/user-service.js b/userservice/userservice/user-service.js index 6df2365..99fb9a0 100644 --- a/userservice/userservice/user-service.js +++ b/userservice/userservice/user-service.js @@ -85,6 +85,20 @@ app.get('/updateIncorrectAnswers', async (req,res) => { } }) +app.get('/getUserData', async (req, res) => { + const { username } = req.query; + try { + const user = await User.findOne({ username }); + if (!user) { + return res.status(404).json({ success: false, message: 'Usuario no encontrado' }); + } + return res.status(200).json({ user }); + } catch (error) { + console.error('Error al obtener los datos de usuario:', error); + return res.status(500).json({ success: false, message: 'Error al obtener los datos de usuario' }); + } +}); + app.get('/updateCompletedGames', async (req,res) => { console.log(req.query) @@ -104,25 +118,10 @@ app.get('/updateCompletedGames', async (req,res) => { }) - const server = app.listen(port, () => { console.log(`User Service listening at http://localhost:${port}`); }); -app.get('/getUserData', async (req, res) => { - const { username } = req.query; - try { - const user = await User.findOne({ username }); - if (!user) { - return res.status(404).json({ success: false, message: 'Usuario no encontrado' }); - } - return res.status(200).json({ user }); - } catch (error) { - console.error('Error al obtener los datos de usuario:', error); - return res.status(500).json({ success: false, message: 'Error al obtener los datos de usuario' }); - } -}); - // Listen for the 'close' event on the Express.js server server.on('close', () => { // Close the Mongoose connection