diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9265703..f8297df5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,10 +17,18 @@ jobs: node-version: 20 - run: npm --prefix users/authservice ci - run: npm --prefix users/userservice ci + - run: npm --prefix questions/answerservice ci + - run: npm --prefix questions/createservice ci + - run: npm --prefix questions/generatedquestservice ci + - run: npm --prefix questions/recordservice ci - run: npm --prefix gatewayservice ci - run: npm --prefix webapp ci - run: npm --prefix users/authservice test -- --coverage - run: npm --prefix users/userservice test -- --coverage + - run: npm --prefix questions/answerservice test -- --coverage + - run: npm --prefix questions/createservice test -- --coverage + - run: npm --prefix questions/generatedquestservice test -- --coverage + - run: npm --prefix questions/recordservice test -- --coverage - run: npm --prefix gatewayservice test -- --coverage - run: npm --prefix webapp test -- --coverage - name: Analyze with SonarCloud diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1fe99e8..eeec170f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,18 @@ jobs: node-version: 20 - run: npm --prefix users/authservice ci - run: npm --prefix users/userservice ci + - run: npm --prefix questions/answerservice ci + - run: npm --prefix questions/createservice ci + - run: npm --prefix questions/generatedquestservice ci + - run: npm --prefix questions/recordservice ci - run: npm --prefix gatewayservice ci - run: npm --prefix webapp ci - run: npm --prefix users/authservice test -- --coverage - run: npm --prefix users/userservice test -- --coverage + - run: npm --prefix questions/answerservice test -- --coverage + - run: npm --prefix questions/createservice test -- --coverage + - run: npm --prefix questions/generatedquestservice test -- --coverage + - run: npm --prefix questions/recordservice test -- --coverage - run: npm --prefix gatewayservice test -- --coverage - run: npm --prefix webapp test -- --coverage - name: Analyze with SonarCloud @@ -35,6 +43,10 @@ jobs: node-version: 20 - run: npm --prefix users/authservice install - run: npm --prefix users/userservice install + - run: npm --prefix questions/answerservice install + - run: npm --prefix questions/createservice install + - run: npm --prefix questions/generatedquestservice install + - run: npm --prefix questions/recordservice install - run: npm --prefix gatewayservice install - run: npm --prefix webapp install - run: npm --prefix webapp run build @@ -93,6 +105,74 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io workdir: users/userservice + docker-push-answerservice: + name: Push answer service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es6b/answerservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: questions/answerservice + docker-push-createservice: + name: Push create service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es6b/createservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: questions/createservice + docker-push-recordservice: + name: Push record service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es6b/recordservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: questions/recordservice + docker-push-generatedquestservice: + name: Push generated quest service Docker Image to GitHub Packages + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [e2e-tests] + steps: + - uses: actions/checkout@v4 + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@v5 + with: + name: arquisoft/wiq_es6b/generatedquestservice + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + workdir: questions/generatedquestservice docker-push-gatewayservice: name: Push gateway service Docker Image to GitHub Packages runs-on: ubuntu-latest @@ -113,7 +193,7 @@ jobs: deploy: name: Deploy over SSH runs-on: ubuntu-latest - needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp] + needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp,docker-push-createservice,docker-push-answerservice,docker-push-recordservice,docker-push-generatedquestservice] steps: - name: Deploy over SSH uses: fifsky/ssh-action@master diff --git a/docker-compose.yml b/docker-compose.yml index 45ffcac7..6fe8fde4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,34 +11,33 @@ services: networks: - mynetwork - recordservice: - container_name: recordservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es6b/recordservice:latest + authservice: + container_name: authservice-${teamname:-defaultASW} + image: ghcr.io/arquisoft/wiq_es6b/authservice:latest profiles: ["dev", "prod"] - build: ./questions/recordservice + build: ./users/authservice depends_on: - mongodb ports: - - "8006:8006" + - "8002:8002" networks: - mynetwork environment: - MONGODB_URI: mongodb://mongodb:27017/recorddb + MONGODB_URI: mongodb://mongodb:27017/userdb - createservice: - container_name: createservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es6b/createservice:latest + userservice: + container_name: userservice-${teamname:-defaultASW} + image: ghcr.io/arquisoft/wiq_es6b/userservice:latest profiles: ["dev", "prod"] - build: ./questions/createservice + build: ./users/userservice depends_on: - mongodb ports: - - "8005:8005" + - "8001:8001" networks: - mynetwork environment: - MONGODB_URI: mongodb://mongodb:27017/questiondb - + MONGODB_URI: mongodb://mongodb:27017/userdb answerservice: container_name: answerservice-${teamname:-defaultASW} @@ -54,34 +53,33 @@ services: environment: MONGODB_URI: mongodb://mongodb:27017/questiondb - - authservice: - container_name: authservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es6b/authservice:latest + createservice: + container_name: createservice-${teamname:-defaultASW} + image: ghcr.io/arquisoft/wiq_es6b/createservice:latest profiles: ["dev", "prod"] - build: ./users/authservice + build: ./questions/createservice depends_on: - mongodb ports: - - "8002:8002" + - "8005:8005" networks: - mynetwork environment: - MONGODB_URI: mongodb://mongodb:27017/userdb + MONGODB_URI: mongodb://mongodb:27017/questiondb - userservice: - container_name: userservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_es6b/userservice:latest + recordservice: + container_name: recordservice-${teamname:-defaultASW} + image: ghcr.io/arquisoft/wiq_es6b/recordservice:latest profiles: ["dev", "prod"] - build: ./users/userservice + build: ./questions/recordservice depends_on: - mongodb ports: - - "8001:8001" + - "8006:8006" networks: - mynetwork environment: - MONGODB_URI: mongodb://mongodb:27017/userdb + MONGODB_URI: mongodb://mongodb:27017/recorddb generatedquestservice: container_name: generatedquestservice-${teamname:-defaultASW} @@ -106,6 +104,10 @@ services: - mongodb - userservice - authservice + - answerservice + - createservice + - recordservice + - generatedquestservice ports: - "8000:8000" networks: @@ -113,8 +115,8 @@ services: environment: AUTH_SERVICE_URL: http://authservice:8002 USER_SERVICE_URL: http://userservice:8001 - QUES_SERVICE_URL: http://createservice:8005 ANSW_SERVICE_URL: http://answerservice:8004 + QUES_SERVICE_URL: http://createservice:8005 REC_SERVICE_URL: http://recordservice:8006 GEN_SERVICE_URL: http://generatedquestservice:8007 diff --git a/docs/package-lock.json b/docs/package-lock.json index ab1646f2..1d49f5c9 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,5 +1,8 @@ { "name": "docs", + + + "version": "1.0.0", "lockfileVersion": 3, "requires": true, diff --git a/questions/answerservice/package.json b/questions/answerservice/package.json index 9d763cc3..cc51fad4 100644 --- a/questions/answerservice/package.json +++ b/questions/answerservice/package.json @@ -4,7 +4,7 @@ "description": " Authentication service, in charge of authenticating users in the application", "main": "service.js", "scripts": { - "start": "node auth-service.js", + "start": "node answer-service.js", "test": "jest" }, "repository": { diff --git a/questions/createservice/create-service.test.js b/questions/createservice/create-service.test.js new file mode 100644 index 00000000..b7c15dcb --- /dev/null +++ b/questions/createservice/create-service.test.js @@ -0,0 +1,49 @@ +const request = require('supertest'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const bcrypt = require('bcrypt'); +const Question = require('./create-model'); + +let mongoServer; +let app; + +//test question +const questionTest = { + questionBody: '¿Cuál es la capital de ', + typeQuestion: 'pais', + typeAnswer: 'capital' +}; + +async function addQuestion(questionTest){ + const newQuestion = new Question({ + questionBody: questionTest.questionBody, + typeQuestion: questionTest.typeQuestion, + typeAnswer: questionTest.typeAnswer + }); + + await newQuestion.save(); +} + +beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + process.env.MONGODB_URI = mongoUri; + app = require('./create-service'); + //Load database with initial conditions + await addQuestion(questionTest); +}); + +afterAll(async () => { + app.close(); + await mongoServer.stop(); +}); + +describe('Create Service', () => { + it('Should perform an addRecord operation /addQuestion', async () => { + const response = await request(app).post('/addQuestion').send(questionTest); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('questionBody', '¿Cuál es la capital de '); + expect(response.body).toHaveProperty('typeQuestion', 'pais'); + expect(response.body).toHaveProperty('typeAnswer', 'capital'); + }); + +}); diff --git a/questions/createservice/package.json b/questions/createservice/package.json index c1843205..efa5b9e6 100644 --- a/questions/createservice/package.json +++ b/questions/createservice/package.json @@ -4,7 +4,7 @@ "description": " Creacion de preguntas automaticas", "main": "service.js", "scripts": { - "start": "node auth-service.js", + "start": "node create-service.js", "test": "jest" }, "repository": { diff --git a/questions/generatedquestservice/generatedquest-service.test.js b/questions/generatedquestservice/generatedquest-service.test.js new file mode 100644 index 00000000..8437deb7 --- /dev/null +++ b/questions/generatedquestservice/generatedquest-service.test.js @@ -0,0 +1,46 @@ +const request = require('supertest'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const bcrypt = require('bcrypt'); +const GeneratedQuestion = require('./generatedquest-model'); + +let mongoServer; +let app; + +//test generated question +const generatedQuestionTest = { + generatedQuestionBody: '¿Cuál es la capital de España?', + correctAnswer: 'Madrid', +}; + +async function addGeneratedQuestion(generatedQuestionTest){ + const newGeneratedQuestion = new GeneratedQuestion({ + generatedQuestionBody: generatedQuestionTest.generatedQuestionBody, + correctAnswer: generatedQuestionTest.correctAnswer + }); + + await newGeneratedQuestion.save(); +} + +beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + process.env.MONGODB_URI = mongoUri; + app = require('./generatedquest-service'); + //Load database with initial conditions + await addGeneratedQuestion(generatedQuestionTest); +}); + +afterAll(async () => { + app.close(); + await mongoServer.stop(); +}); + +describe('Generatedquest Service', () => { + it('Should perform an addRecord operation /addGeneratedQuestion', async () => { + const response = await request(app).post('/addGeneratedQuestion').send(generatedQuestionTest); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('generatedQuestionBody', '¿Cuál es la capital de España?'); + expect(response.body).toHaveProperty('correctAnswer', 'Madrid'); + }); + +}); diff --git a/questions/generatedquestservice/package.json b/questions/generatedquestservice/package.json index fcb8c1a6..2d569a3e 100644 --- a/questions/generatedquestservice/package.json +++ b/questions/generatedquestservice/package.json @@ -4,7 +4,7 @@ "description": "Almacenamiento de las preguntas automaticas generadas y su respuesta correcta correspondiente", "main": "service.js", "scripts": { - "start": "node auth-service.js", + "start": "node generatedquest-service.js", "test": "jest" }, "repository": { diff --git a/questions/recordservice/package.json b/questions/recordservice/package.json index 1f7395b5..4a238fd7 100644 --- a/questions/recordservice/package.json +++ b/questions/recordservice/package.json @@ -4,7 +4,7 @@ "description": " Creacion de preguntas automaticas", "main": "service.js", "scripts": { - "start": "node auth-service.js", + "start": "node record-service.js", "test": "jest" }, "repository": { diff --git a/questions/recordservice/record-service.test.js b/questions/recordservice/record-service.test.js index 8ba4eca2..9cd49fc4 100644 --- a/questions/recordservice/record-service.test.js +++ b/questions/recordservice/record-service.test.js @@ -51,11 +51,14 @@ describe('Record Service', () => { }); it('Should get user records by userId /getRecords/:userId', async () => { - const response = await request(app).get('/getRecords/${record.userId}'); - + const response = await request(app).get(`/getRecords/${record.userId}`); + expect(response.status).toBe(200); expect(response.body.some(record => record.userId === 'testuserid')).toBe(true); - expect(response.body[0]).toMatchObject(record); + + // Convert the date to a string before comparing + const expectedRecord = { ...record, date: record.date.toISOString() }; + expect(response.body[0]).toMatchObject(expectedRecord); }); }); diff --git a/webapp/e2e/test-environment-setup.js b/webapp/e2e/test-environment-setup.js index 81f40b74..fcc529d5 100644 --- a/webapp/e2e/test-environment-setup.js +++ b/webapp/e2e/test-environment-setup.js @@ -7,6 +7,8 @@ let authservice; let gatewayservice; let createservice; let answerservice; +let recordservice; +let generatedquestservice; async function startServer() { console.log('Starting MongoDB memory server...'); diff --git a/webapp/src/components/Game.js b/webapp/src/components/Game.js index b6eace0e..644a13db 100644 --- a/webapp/src/components/Game.js +++ b/webapp/src/components/Game.js @@ -1,37 +1,24 @@ // src/components/Game.js import axios from 'axios'; -import React, { useState, useEffect } from 'react'; -import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +//import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; +import {Typography, Button } from '@mui/material'; -import Link from '@mui/material/Link'; +//import Link from '@mui/material/Link'; const Game=() =>{ const [questionBody, setQuestionBody] = useState('');//pregunta aleatoria cuerpo const [informacionWikidata, setInformacionWikidata] = useState(''); const [respuestaCorrecta, setRespuestaCorrecta] = useState(''); - const [questionType, setQuestionType] = useState('');//para el tipo de pregunta a buscar - const [answerType, setAnswerType] = useState('');//para el tipo de respuesta a buscar + //const [questionType, setQuestionType] = useState('');//para el tipo de pregunta a buscar + //const [answerType, setAnswerType] = useState('');//para el tipo de respuesta a buscar const [numberClics, setNumberClics] = useState(1); const [timer, setTimer] = useState(0); // estado con el temporizador iniciado a 0 seg const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; - - //useEffect(() => { - // obtenerPreguntaAleatoria(); - //}, []); - // se ejecuta una vez cuando se cargue el componente y llena la BD con las plantillas posibles - // además de generar la pregunta nº1 - useEffect(() => { - const fetchData = async () => { - await peticionPOST(); // Espera a que la primera función se complete - obtenerPreguntaAleatoria(); // Luego ejecuta la segunda función - }; - fetchData(); // Llamada a la función async - }, []); - // se ejecuta una vez cuando se cargue el componente y establece aumentar "timer" en una // unidad cada 1000ms @@ -42,8 +29,56 @@ const Game=() =>{ return () => clearInterval(interval); }, []); + + // Diccionario con el tipo de pregunta y la consulta SPARQL correspondiente + const questionTypes = useMemo(() => ({ + "pais": { + query: ` + SELECT ?country ?countryLabel ?capital ?capitalLabel + WHERE { + ?country wdt:P31 wd:Q6256. + ?country wdt:P36 ?capital. + SERVICE wikibase:label { + bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". + } + } + ORDER BY RAND() + LIMIT 150 + `, + questionLabel: 'countryLabel', + answerLabel: 'capitalLabel' + }, + // Añadir el resto de tipos de preguntas + }), []); + + // Obtener info de wikidata segun el tipo de la pregunta y la respuesta para esa pregunta + const obtenerDatos = useCallback(async (questionType) => { + try { + const { query, questionLabel, answerLabel } = questionTypes[questionType]; + + const apiUrl = `https://query.wikidata.org/sparql?query=${encodeURIComponent(query)}`; + const headers = { "Accept": "application/json" }; + + const respuestaWikidata = await fetch(apiUrl, {headers}); + + if (respuestaWikidata.ok) { + const data = await respuestaWikidata.json(); + const numEles = data.results.bindings.length; + const index = Math.floor(Math.random() * numEles); + const result = data.results.bindings[index]; + + setInformacionWikidata(result[questionLabel].value + '?'); + setRespuestaCorrecta(result[answerLabel].value); + } else { + console.error("Error al realizar la consulta en Wikidata. Estado de respuesta:", respuestaWikidata.status); + } + } catch (error) { + console.error("Error al realizar la consulta en Wikidata", error); + } + }, [questionTypes, setInformacionWikidata, setRespuestaCorrecta]); + // Función para realizar la petición POST para cargar los tipos de pregunta en la base de datos de mongo - const peticionPOST = async () => { + const peticionPOST = useCallback(async () => { try { const response = await axios.post(`${apiEndpoint}/addQuestion`, { questionBody: '¿Cuál es la capital de ', @@ -54,74 +89,41 @@ const Game=() =>{ } catch (error) { console.error('Error en la petición POST:', error); } - }; - + }, [apiEndpoint]); //para el tipo de respuesta a buscar - // Obtener pregunta una pregunta aleatoria al acceder a la url - const obtenerPreguntaAleatoria = async () => { - try { - - const response = await axios.post(`${apiEndpoint}/getQuestionBody`); - - setQuestionBody(response.data.questionBody);//obtengo los datos del cuerpo de la pregunta - setQuestionType(response.data.typeQuestion); - setAnswerType(response.data.typeAnswer); - - obtenerDatos(response.data.typeQuestion); - - } catch (error) { - console.error("Error al obtener la pregunta aleatoria", error); - } + // Obtener pregunta una pregunta aleatoria al acceder a la url + const obtenerPreguntaAleatoria = useCallback(async () => { + try { + + const response = await axios.post(`${apiEndpoint}/getQuestionBody`); + + setQuestionBody(response.data.questionBody);//obtengo los datos del cuerpo de la pregunta + //setQuestionType(response.data.typeQuestion); + //setAnswerType(response.data.typeAnswer); + + obtenerDatos(response.data.typeQuestion); + + } catch (error) { + console.error("Error al obtener la pregunta aleatoria", error); + } + }, [apiEndpoint, obtenerDatos]); + + //useEffect(() => { + // obtenerPreguntaAleatoria(); + //}, []); + // se ejecuta una vez cuando se cargue el componente y llena la BD con las plantillas posibles + // además de generar la pregunta nº1 + useEffect(() => { + const fetchData = async () => { + await peticionPOST(); // Espera a que la primera función se complete + obtenerPreguntaAleatoria(); // Luego ejecuta la segunda función }; + fetchData(); // Llamada a la función async + }, [obtenerPreguntaAleatoria, peticionPOST]); - // Diccionario con el tipo de pregunta y la consulta SPARQL correspondiente - const questionTypes = { - "pais": { - query: ` - SELECT ?country ?countryLabel ?capital ?capitalLabel - WHERE { - ?country wdt:P31 wd:Q6256. - ?country wdt:P36 ?capital. - SERVICE wikibase:label { - bd:serviceParam wikibase:language "[AUTO_LANGUAGE],es". - } - } - ORDER BY RAND() - LIMIT 150 - `, - questionLabel: 'countryLabel', - answerLabel: 'capitalLabel' - }, - // Añadir el resto de tipos de preguntas - }; - // Obtener info de wikidata segun el tipo de la pregunta y la respuesta para esa pregunta - const obtenerDatos = async (questionType) => { - try { - const { query, questionLabel, answerLabel } = questionTypes[questionType]; - - const apiUrl = `https://query.wikidata.org/sparql?query=${encodeURIComponent(query)}`; - const headers = { "Accept": "application/json" }; - - const respuestaWikidata = await fetch(apiUrl, {headers}); - - if (respuestaWikidata.ok) { - const data = await respuestaWikidata.json(); - const numEles = data.results.bindings.length; - const index = Math.floor(Math.random() * numEles); - const result = data.results.bindings[index]; - - setInformacionWikidata(result[questionLabel].value + '?'); - setRespuestaCorrecta(result[answerLabel].value); - } else { - console.error("Error al realizar la consulta en Wikidata. Estado de respuesta:", respuestaWikidata.status); - } - } catch (error) { - console.error("Error al realizar la consulta en Wikidata", error); - } - }; const handleButtonClick = () => { setNumberClics(numberClics + 1); @@ -160,9 +162,8 @@ const Game=() =>{ )} -); + ); - - } +} export default Game; diff --git a/webapp/src/components/Login.js b/webapp/src/components/Login.js index a32c94e0..386d87ee 100644 --- a/webapp/src/components/Login.js +++ b/webapp/src/components/Login.js @@ -7,7 +7,7 @@ import { Container, Typography, TextField, Button, Snackbar } from '@mui/materia import Game from './Game'; import UsersList from './UsersList'; -import Link from '@mui/material/Link'; +//import Link from '@mui/material/Link'; const Login = () => { const [username, setUsername] = useState(''); diff --git a/webapp/src/components/UsersList.js b/webapp/src/components/UsersList.js index 2600867e..4507c93c 100644 --- a/webapp/src/components/UsersList.js +++ b/webapp/src/components/UsersList.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import axios from 'axios'; -import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; +//import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; const UsersList = () => { @@ -30,7 +30,7 @@ const UsersList = () => { }; fetchUsers(); - }, []); + }, [apiEndpoint]); return (