diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 410b5a8..f0b7ef3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,15 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + - uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0' - run: npm --prefix gatewayservice ci - run: npm --prefix webapp ci + - run: npm --prefix authservice ci + - run: npm --prefix userservice ci + - run: dotnet build ./wikidata_service/WikidataService.csproj - run: npm --prefix gatewayservice test -- --coverage - run: npm --prefix webapp test -- --coverage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fd6ca88..dcd39ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,47 +5,12 @@ on: types: [published] jobs: - unit-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: npm --prefix users/authservice ci - - run: npm --prefix users/userservice 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 gatewayservice test -- --coverage - - run: npm --prefix webapp test -- --coverage - - name: Analyze with SonarCloud - uses: sonarsource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - e2e-tests: - needs: [unit-tests] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: npm --prefix users/authservice install - - run: npm --prefix users/userservice install - - run: npm --prefix gatewayservice install - - run: npm --prefix webapp install - - run: npm --prefix webapp run build - - run: npm --prefix webapp run test:e2e docker-push-webapp: name: Push webapp 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 @@ -58,48 +23,24 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io workdir: webapp - buildargs: API_URI - docker-push-authservice: - name: Push auth service Docker Image to GitHub Packages + buildargs: | + API_URI + docker-push-api: + name: Push api 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_en3a/authservice + name: arquisoft/wiq_en3a/api username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io - workdir: users/authservice - docker-push-userservice: - name: Push user 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_en3a/userservice - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - registry: ghcr.io - workdir: users/userservice + workdir: api docker-push-gatewayservice: name: Push gateway 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 @@ -113,10 +54,12 @@ 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-gatewayservice,docker-push-webapp, docker-push-api] steps: - name: Deploy over SSH uses: fifsky/ssh-action@master + env: + API_URI: ${{ secrets.DEPLOY_HOST }} with: host: ${{ secrets.DEPLOY_HOST }} user: ${{ secrets.DEPLOY_USER }} @@ -124,5 +67,5 @@ jobs: command: | wget https://raw.githubusercontent.com/arquisoft/wiq_en3a/master/docker-compose.yml -O docker-compose.yml wget https://raw.githubusercontent.com/arquisoft/wiq_en3a/master/.env - docker compose down - docker compose --profile prod up -d + docker compose --profile prod down + docker compose --profile prod up -d --pull always diff --git a/users/.dockerignore b/api/.dockerignore similarity index 100% rename from users/.dockerignore rename to api/.dockerignore diff --git a/users/Dockerfile b/api/Dockerfile similarity index 89% rename from users/Dockerfile rename to api/Dockerfile index 3004db1..4898e96 100644 --- a/users/Dockerfile +++ b/api/Dockerfile @@ -2,13 +2,13 @@ FROM node:20 # Set the working directory in the container -WORKDIR /usr/src/users +WORKDIR /usr/src/api # Copy package.json and package-lock.json to the working directory COPY package*.json ./ # Install app dependencies -RUN npm install +RUN npm install --no-cache # Copy the app source code to the working directory COPY . . @@ -18,3 +18,4 @@ EXPOSE 8002 # Define the command to run your app CMD ["node", "index.js"] + diff --git a/api/authservice/Dockerfile b/api/authservice/Dockerfile new file mode 100644 index 0000000..ee88493 --- /dev/null +++ b/api/authservice/Dockerfile @@ -0,0 +1,17 @@ +# Use an official Node.js runtime as a parent image +FROM node:20 + +# Set the working directory in the container +WORKDIR /usr/src/authservice + +# Copy package.json and package-lock.json to the working directory +COPY package*.json ./ + +# Install app dependencies +RUN npm install + +# Copy the app source code to the working directory +COPY . . + +# Define the command to run your app +CMD ["node", "auth-service.js"] diff --git a/users/authservice/auth-model.js b/api/authservice/auth-model.js similarity index 100% rename from users/authservice/auth-model.js rename to api/authservice/auth-model.js diff --git a/users/authservice/auth-service.js b/api/authservice/auth-service.js similarity index 100% rename from users/authservice/auth-service.js rename to api/authservice/auth-service.js diff --git a/users/authservice/auth-service.test.js b/api/authservice/auth-service.test.js similarity index 100% rename from users/authservice/auth-service.test.js rename to api/authservice/auth-service.test.js diff --git a/users/authservice/package-lock.json b/api/authservice/package-lock.json similarity index 100% rename from users/authservice/package-lock.json rename to api/authservice/package-lock.json diff --git a/users/authservice/package.json b/api/authservice/package.json similarity index 100% rename from users/authservice/package.json rename to api/authservice/package.json diff --git a/users/authservice/test-login.js b/api/authservice/test-login.js similarity index 100% rename from users/authservice/test-login.js rename to api/authservice/test-login.js diff --git a/users/index.js b/api/index.js similarity index 90% rename from users/index.js rename to api/index.js index cc30a54..3066caa 100644 --- a/users/index.js +++ b/api/index.js @@ -22,6 +22,7 @@ mongoose.connect(mongoUri).then( // Routes const authRoutes = require('./authservice/auth-service.js'); const userRoutes = require('./userservice/user-service.js'); +const wikidataRoutes = require('./wikidataservice/wikidata-service.js'); // Middlewares added to the application @@ -30,8 +31,7 @@ app.use(bodyParser.json()); // Routes middlewares to be used app.use('/auth', authRoutes); app.use('/user', userRoutes); - - +app.use('/wikidata', wikidataRoutes); // Start the server diff --git a/users/package-lock.json b/api/package-lock.json similarity index 99% rename from users/package-lock.json rename to api/package-lock.json index fb819e5..dc0aea1 100644 --- a/users/package-lock.json +++ b/api/package-lock.json @@ -1,14 +1,15 @@ { - "name": "users", + "name": "api", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "users", + "name": "api", "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.6.8", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", @@ -1414,8 +1415,17 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } }, "node_modules/b4a": { "version": "1.6.4", @@ -1846,7 +1856,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2018,7 +2027,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -2400,10 +2408,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "dev": true, + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -2423,7 +2430,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -4673,6 +4679,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/users/package.json b/api/package.json similarity index 83% rename from users/package.json rename to api/package.json index bc6d132..056f451 100644 --- a/users/package.json +++ b/api/package.json @@ -1,7 +1,7 @@ { - "name": "users", + "name": "api", "version": "1.0.0", - "description": "User service, in charge of handling users in the application", + "description": "API service, in charge of handling the different clases of the API in the application", "main": "service.js", "scripts": { "start": "node index.js", @@ -18,6 +18,7 @@ }, "homepage": "https://github.com/arquisoft/wiq_en3a#readme", "dependencies": { + "axios": "^1.6.7", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", diff --git a/api/userservice/Dockerfile b/api/userservice/Dockerfile new file mode 100644 index 0000000..cea2bbe --- /dev/null +++ b/api/userservice/Dockerfile @@ -0,0 +1,21 @@ +# Use an official Node.js runtime as a parent image +FROM node:20 + +# Set the working directory in the container +WORKDIR /usr/src/userservice + +# Copy package.json and package-lock.json to the working directory +COPY package*.json ./ + +# Install app dependencies +RUN npm install + +# Copy the app source code to the working directory +COPY . . + +# Expose the port the app runs on +EXPOSE 8001 + +# Define the command to run your app +CMD ["node", "user-service.js"] + diff --git a/users/userservice/package-lock.json b/api/userservice/package-lock.json similarity index 100% rename from users/userservice/package-lock.json rename to api/userservice/package-lock.json diff --git a/users/userservice/package.json b/api/userservice/package.json similarity index 100% rename from users/userservice/package.json rename to api/userservice/package.json diff --git a/users/userservice/test-addUser.js b/api/userservice/test-addUser.js similarity index 100% rename from users/userservice/test-addUser.js rename to api/userservice/test-addUser.js diff --git a/users/userservice/test-editUser.js b/api/userservice/test-editUser.js similarity index 100% rename from users/userservice/test-editUser.js rename to api/userservice/test-editUser.js diff --git a/users/userservice/user-model.js b/api/userservice/user-model.js similarity index 100% rename from users/userservice/user-model.js rename to api/userservice/user-model.js diff --git a/users/userservice/user-service.js b/api/userservice/user-service.js similarity index 97% rename from users/userservice/user-service.js rename to api/userservice/user-service.js index ca8fdc1..388cce4 100644 --- a/users/userservice/user-service.js +++ b/api/userservice/user-service.js @@ -9,7 +9,7 @@ const User = require('./user-model') // GET route to retrieve an specific user by username // 'http://localhost:8002/getOneUser?username=nombre_de_usuario' -router.get('/getUser', async (req, res) => { +router.get('/getuser', async (req, res) => { try { // access to the database @@ -76,7 +76,7 @@ router.post('/adduser', async (req, res) => { // edit a user to update the total and correct question answered -router.post('/editUser', async (req, res) => { +router.post('/edituser', async (req, res) => { try { // --- find the user to be updated diff --git a/users/userservice/user-service.test.js b/api/userservice/user-service.test.js similarity index 100% rename from users/userservice/user-service.test.js rename to api/userservice/user-service.test.js diff --git a/api/wikidataservice/models/wikidata-model.js b/api/wikidataservice/models/wikidata-model.js new file mode 100644 index 0000000..e69de29 diff --git a/api/wikidataservice/package-lock.json b/api/wikidataservice/package-lock.json new file mode 100644 index 0000000..8644ca9 --- /dev/null +++ b/api/wikidataservice/package-lock.json @@ -0,0 +1,102 @@ +{ + "name": "wikidataservice", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^1.6.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } +} diff --git a/api/wikidataservice/package.json b/api/wikidataservice/package.json new file mode 100644 index 0000000..ccbfb54 --- /dev/null +++ b/api/wikidataservice/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "axios": "^1.6.7" + } +} diff --git a/api/wikidataservice/wikidata-service.js b/api/wikidataservice/wikidata-service.js new file mode 100644 index 0000000..1439170 --- /dev/null +++ b/api/wikidataservice/wikidata-service.js @@ -0,0 +1,127 @@ +const express = require('express'); +const router = express.Router(); +const axios = require('axios'); + +// Route for user login +router.get('/getCapitalsQuestions', async (req, res) => { + try { + // Obtiene las preguntas de la fuente de datos + const jsonQuestions = await getQuestions(getCapitalsQuery()); + + // Genera las preguntas sobre las capitales + const questions = generateQuestionsCapitalsOf(jsonQuestions.results.bindings); + + // Envía las preguntas como respuesta HTTP + res.status(200).json(questions); + } catch (error) { + // Maneja cualquier error y envía una respuesta de error HTTP + console.error('Error:', error); + res.status(500).send('Internal Server Error'); + } +}); + +async function getQuestions(sparqlQuery) { + try { + const endpointUrl = "https://query.wikidata.org/sparql?query="; + const fullUrl = endpointUrl + encodeURIComponent(sparqlQuery); + + // Realiza la solicitud HTTP utilizando axios + const response = await axios.get(fullUrl, { + headers: { + 'User-Agent': 'Sergiollende/1.0', + 'Accept': 'application/sparql-results+json' + } + }); + + // Extrae los datos JSON de la respuesta + const jsonResult = response.data; + + return jsonResult; + } catch (error) { + // Maneja cualquier error que pueda ocurrir durante la solicitud HTTP + console.error('Error:', error.message); + throw new Error('Failed to fetch data from Wikidata'); + } +} + +function generateQuestionsCapitalsOf(capitalCountries, numberQuestions = 10) { + const questions = []; + + const ids = new Set(); + + // Genera un conjunto de índices únicos aleatorios + while (ids.size < numberQuestions) { + ids.add(Math.floor(Math.random()*capitalCountries.length+1)); + } + + const idsList = Array.from(ids); + + // Genera las preguntas + for (let j = 0; j < idsList.length; j++) { + const countryName = capitalCountries[idsList[j]].countryLabel.value; + const questionText = `What is the capital of ${countryName}?`; + const answers = []; + + // Índice de la respuesta correcta + let correctAnswer = 0; + + // Agrega la capital correcta en el primer lugar + answers[correctAnswer] = capitalCountries[idsList[j]].capitalLabel.value; + + // Obtiene 3 respuestas incorrectas aleatorias de todas las capitales + const wrongIds = new Set(); + for (let w = 1; w < 4; w++) { + let wrongId = Math.floor(Math.random()*capitalCountries.length+1); + while (idsList[j] === wrongId || wrongIds.has(wrongId)) { + wrongId = Math.floor(Math.random()*capitalCountries.length+1); + } + // Agrega el id de la respuesta incorrecta al conjunto + wrongIds.add(wrongId); + answers[w] = capitalCountries[wrongId].capitalLabel.value; + } + + questions[j] = { + text:questionText, + answers, + correctAnswer + }; + } + + return questions; +} + +function getCapitalsQuery() { + return ` + SELECT ?capitalLabel ?countryLabel WHERE { + ?capital wdt:P1376 ?country. + ?capital wdt:P31 wd:Q5119. + ?country wdt:P31 wd:Q3624078. + SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } + } + LIMIT 400 + `; +} + +// Define la función para obtener la consulta del director de la película +function getMovieDirectorQuery() { + return generateSparqlQuery("Q11424", "P57"); +} + +// Define la función para obtener la consulta del símbolo del elemento +function getElementSymbolQuery() { + return generateSparqlQuery("Q11344", "P246"); +} + +// Define la función para generar la consulta SPARQL +function generateSparqlQuery(themeId, attributeId, limit = 10) { + return ` + SELECT ?themeLabel ?attributeLabel WHERE { + ?theme wdt:P31 wd:${themeId}; + wdt:${attributeId} ?attribute. + SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } + } + LIMIT ${limit} + `; +} + +module.exports = router diff --git a/docker-compose.yml b/docker-compose.yml index 76c4b76..fa9e7b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,21 +11,11 @@ services: networks: - mynetwork - wikidata_service: - container_name: wikidata_service-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_en3a/wikidata_service:latest - build: ./wikidata_service/WikiDataTest + api: + container_name: api-${teamname:-defaultASW} + image: ghcr.io/arquisoft/wiq_en3a/api:latest profiles: ["dev", "prod"] - ports: - - "7259:7259" - networks: - - mynetwork - - authservice: - container_name: authservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_en3a/authservice:latest - profiles: ["dev", "prod"] - build: ./users/authservice + build: ./api depends_on: - mongodb ports: @@ -35,20 +25,6 @@ services: environment: MONGODB_URI: mongodb://mongodb:27017/userdb - userservice: - container_name: userservice-${teamname:-defaultASW} - image: ghcr.io/arquisoft/wiq_en3a/userservice:latest - profiles: ["dev", "prod"] - build: ./users/userservice - depends_on: - - mongodb - ports: - - "8001:8001" - networks: - - mynetwork - environment: - MONGODB_URI: mongodb://mongodb:27017/userdb - gatewayservice: container_name: gatewayservice-${teamname:-defaultASW} image: ghcr.io/arquisoft/wiq_en3a/gatewayservice:latest @@ -56,26 +32,26 @@ services: build: ./gatewayservice depends_on: - mongodb - - userservice - - authservice - - wikidata_service + - api ports: - "8000:8000" networks: - mynetwork environment: - AUTH_SERVICE_URL: http://authservice:8002 - USER_SERVICE_URL: http://userservice:8001 + API_SERVICE_URL: http://api:8002 webapp: container_name: webapp-${teamname:-defaultASW} image: ghcr.io/arquisoft/wiq_en3a/webapp:latest profiles: ["dev", "prod"] - build: ./webapp - depends_on: - - gatewayservice + build: + args: + REACT_APP_API_ENDPOINT: ${API_URI} + context: ./webapp + environment: + - REACT_APP_API_ENDPOINT=${API_URI} ports: - - "3000:3000" + - "3000:3000" prometheus: image: prom/prometheus diff --git a/docs/images/10-Quality-Tree-EN.png b/docs/images/10-Quality-Tree-EN.png deleted file mode 100644 index dbae1bf..0000000 Binary files a/docs/images/10-Quality-Tree-EN.png and /dev/null differ diff --git a/docs/src/04_solution_strategy.adoc b/docs/src/04_solution_strategy.adoc index e0b613c..e1d6478 100644 --- a/docs/src/04_solution_strategy.adoc +++ b/docs/src/04_solution_strategy.adoc @@ -33,13 +33,13 @@ See https://docs.arc42.org/section-4/[Solution Strategy] in the arc42 documentat === Technologies * *React*: JavaScript library for web and native user interfaces. It allows developers to create interactive web applications by breaking down the UI into reusable components. React uses a declarative approach to efficiently update and render components, resulting in faster and more maintainable code. It's widely adopted in the industry due to its simplicity, performance, and robustness. -* *Svelte*: modern JavaScript framework that compiles code at build time for efficient updates to the DOM. It emphasizes smaller bundle sizes and better performance, offering a simpler approach to building dynamic web applications compared to traditional frameworks like React or Vue. * *Node.js*: JavaScript runtime that enables running JavaScript code outside of web browsers. It's renowned for its event-driven architecture and extensive collection of packages, making it ideal for building scalable server-side applications. + *Express.js*: Express.js, often simply called Express, is a minimalist web application framework for Node.js. It simplifies the process of building web applications by providing a robust set of features, including middleware support, routing, and templating engines. Express is known for its flexibility, simplicity, and performance, making it a popular choice for developing web applications and APIs in Node.js. * *.NET*: versatile developer platform for creating web, mobile, desktop, and cloud applications. It supports multiple programming languages and provides a rich set of libraries and tools for building software solutions. With built-in support for creating APIs and consuming web services, .NET makes it simple to develop and integrate with backend systems and services. * *Wikidata*: Wikidata provides a REST API for retrieving information related to any topic. It helps us to dynamically generate questions for our game using it from any programming language. * *MongoDB*: popular NoSQL database known for its flexibility and scalability. It stores data in flexible JSON-like documents and is widely used in modern web development for its simplicity and ability to handle large volumes of data. * *Cucumber*: Testing tool that supports Behavior Driven Development (BDD) and allows us also to comply testability quality attribute. +* *SonarCloud*: Cloud-based service provided by SonarSource, which offers continuous code quality analysis and automated code reviews for software development projects. It helps developers identify and fix bugs, security vulnerabilities, and code smells in their codebase to improve overall software quality. * *Arc42*: framework (template) used for documenting and communicating software architectures. It provides a template for describing the architecture of a software system, covering aspects such as stakeholders, requirements, architecture decisions, components, interfaces, and quality attributes. arc42 helps teams create consistent and comprehensible architecture documentation, enabling better communication, understanding, and maintenance of software systems throughout their lifecycle. * *npm*: default package manager for Node.js, providing a command-line interface to install, manage, and publish JavaScript packages. With over a million packages available in its registry, npm simplifies adding functionality to Node.js projects by handling dependencies and providing tools for versioning and publishing packages. * *Docker*: platform that will be used for deploying our services inside containers. Containers are lightweight, portable, and self-sufficient units that contain everything needed to run an application, including the code, runtime, system tools, libraries, and settings. Docker enables developers to package their applications along with all dependencies into containers, ensuring consistency across different environments, such as development, testing, and production. diff --git a/docs/src/07_deployment_view.adoc b/docs/src/07_deployment_view.adoc index 66a39e3..2673084 100644 --- a/docs/src/07_deployment_view.adoc +++ b/docs/src/07_deployment_view.adoc @@ -98,28 +98,30 @@ General view of system's infrastructure [plantuml, target=deployment-diagram, format=png] .... actor client as "Client" -frame UbuntuServer { - frame Docker { +frame Azure { + frame UbuntuServer { + frame Docker { - frame WebAPP { - node React - } + frame WebAPP { + node React + } - frame NodeJSGateway { - node Authentication + frame NodeJSGateway { + node Authentication - node Users - } + node Users + } - database MongoDB { - node UsersDatabase + database MongoDB { + node UsersDatabase - node ResultsDatabase - } + node ResultsDatabase + } - frame .NET { - node RestAPI + frame .NET { + node RestAPI + } } } } @@ -130,6 +132,10 @@ frame GitHub{ node dockerCompose } + frame wiq_en3a{ + node project + } + } .NET -- ResultsDatabase : MongoDB (port 27017) @@ -138,7 +144,8 @@ WebAPP -- NodeJSGateway : (port 8000:8002) WebAPP -- .NET : .NET (port TBD) client -- WebAPP : Web Browser (port 3000) -Docker -- GitHubActions : on release +GitHubActions -- UbuntuServer : on release +Docker -- wiq_en3a .... diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 88b84c8..7d13f1e 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -6,8 +6,7 @@ const promBundle = require('express-prom-bundle'); const app = express(); const port = 8000; -const authServiceUrl = process.env.AUTH_SERVICE_URL || 'http://localhost:8002'; -const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001'; +const apiServiceUrl = process.env.API_SERVICE_URL || 'http://localhost:8002'; app.use(cors()); app.use(express.json()); @@ -24,20 +23,47 @@ app.get('/health', (_req, res) => { app.post('/login', async (req, res) => { try { // Forward the login request to the authentication service - const authResponse = await axios.post(authServiceUrl+'/login', req.body); + const authResponse = await axios.post(apiServiceUrl + '/auth/login', req.body); res.json(authResponse.data); } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + console.error(error); } }); app.post('/adduser', async (req, res) => { try { // Forward the add user request to the user service - const userResponse = await axios.post(userServiceUrl+'/adduser', req.body); + const userResponse = await axios.post(apiServiceUrl + '/user/adduser', req.body); res.json(userResponse.data); } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + console.error(error); + } +}); + +app.post('/edituser', async (req, res) => { + try { + // Forward the edit user request to the user service + const userResponse = await axios.post(apiServiceUrl + '/user/edituser', req.body); + res.json(userResponse.data); + } catch (error) { + console.error(error); + } +}); + +app.get('/GetCapitalsQuestions', async (_req, res) => { + try { + // Forward the edit user request to the user service + console.log(process.env.apiServiceUrl); + const wikiResponse = await axios.get(apiServiceUrl + '/wikidata/getCapitalsQuestions', { timeout: 5000 }); + if (wikiResponse.status !== 200) { + console.error('Error with the wikidata service:', wikiResponse.status); + res.status(wikiResponse.status).json({ error: 'Error with the wikidata service' }); + } else { + res.json(wikiResponse.data); + } + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Error with the gateway service' }); } }); diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml new file mode 100644 index 0000000..79a2a46 --- /dev/null +++ b/gatewayservice/openapi.yaml @@ -0,0 +1,39 @@ +openapi: 3.0.0 +info: + title: Gateway Service API + version: 0.0.1 + description: Gateway Service + +servers: + - url: http://localhost:8080 + description: Local server + +paths: + /adduser: + post: + summary: Add a new user + operationId: addUser + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '200': + description: User added successfully + content: + application/json: + schema: + type: object + properties: + username: + type: string + example: User ID + password: + type: string + example: Password + '400': + description: Invalid input + '409': + description: User already exists \ No newline at end of file diff --git a/webapp/.env b/webapp/.env index c810bde..e69de29 100644 --- a/webapp/.env +++ b/webapp/.env @@ -1 +0,0 @@ -REACT_APP_API_ENDPOINT=http://localhost:8000 \ No newline at end of file diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 3cbad8b..4865006 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -15,4 +15,4 @@ RUN npm install serve #Execute npm run prod to run the server CMD [ "npm", "run", "prod" ] -#CMD ["npm", "start"] \ No newline at end of file +#CMD ["npm", "start"] diff --git a/webapp/src/services/auth-service.ts b/webapp/src/services/auth-service.ts index f09a188..142af5f 100644 --- a/webapp/src/services/auth-service.ts +++ b/webapp/src/services/auth-service.ts @@ -3,7 +3,7 @@ import { jwtDecode } from "jwt-decode"; import { useUserStore } from '../stores/user-store'; import { useStats } from '../stores/playing-store'; -const API_URL = 'http://localhost:8002'; +const API_URL = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; export type JwtPayload = { username: string; @@ -22,7 +22,7 @@ export const loginWithToken = () => { export const login = async (username: string, password: string)=> { try { - const response = await axios.post(`${API_URL}/auth/login`, { username, password }); + const response = await axios.post(`${API_URL}/login`, { username, password }); //const response = await axios.post("http://localhost:8002/auth/login", { username, password }); const token = response.data.token; console.log('token:', token); @@ -37,7 +37,7 @@ export const login = async (username: string, password: string)=> { export const register = async (email:string, username: string, password: string) => { try { - const response = await axios.post(`${API_URL}/user/adduser`, { username, password, email }); + const response = await axios.post(`${API_URL}/adduser`, { username, password, email }); console.log('response:', response); const name = response.data; return name; @@ -50,7 +50,7 @@ export const register = async (email:string, username: string, password: string) export const updateStats = async (questions_answered: number, correctly_answered_questions: number) => { const username = getUsername(); try { - await axios.post(`${API_URL}/user/editUser`, { username, questions_answered, correctly_answered_questions }); + await axios.post(`${API_URL}/edituser`, { username, questions_answered, correctly_answered_questions }); updateStatsState(questions_answered, correctly_answered_questions); return true; } catch (error) { diff --git a/webapp/src/stores/playing-store.ts b/webapp/src/stores/playing-store.ts index b748e55..6f0133e 100644 --- a/webapp/src/stores/playing-store.ts +++ b/webapp/src/stores/playing-store.ts @@ -1,4 +1,5 @@ +import axios from 'axios'; import {create} from 'zustand'; import { getCorrectlyAnsweredQuestions, getQuestionsAnswered } from '../services/auth-service'; @@ -56,15 +57,29 @@ interface GameQuestions{ startGame: () => void } -const retrieveQuestions = () => { - return fetch('https://localhost:7259/WikiData/GetCapitalsQuestions') - .then((response) => response.json()) - .catch((error) => { - console.error('There was a problem with the questions:', error); - return []; // Return an empty array in case of an error - }); +const API_URL = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; + +export const retrieveQuestions = async () => { + try { + let response = await axios.get(`${API_URL}/GetCapitalsQuestions`); + console.log('response:', response); + return response.data; + } catch (error) { + console.error('There was a problem with the questions:', error); + return []; + } }; +// const retrieveQuestionss = () => { +// console.log(`${API_URL}`); +// return fetch(`${API_URL}/WikiData/GetCapitalsQuestions`) +// .then((response) => response.json()) +// .catch((error) => { +// console.error('There was a problem with the questions:', error); +// return []; // Return an empty array in case of an error +// }); +// }; + export const useGameQuestions = create((set) => ({ questions: [], setQuestions: (questions: any[]) => set({ questions: questions }), diff --git a/wikidata_service/WikiDataTest/Dockerfile b/wikidata_service/Dockerfile similarity index 58% rename from wikidata_service/WikiDataTest/Dockerfile rename to wikidata_service/Dockerfile index ab322a5..ec812db 100644 --- a/wikidata_service/WikiDataTest/Dockerfile +++ b/wikidata_service/Dockerfile @@ -1,11 +1,5 @@ -#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. - -#Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed. -#For more information, please see https://aka.ms/containercompat - FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app -EXPOSE 7259 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build ARG BUILD_CONFIGURATION=Release @@ -23,4 +17,6 @@ RUN dotnet publish "./WikiDataTest.csproj" -c %BUILD_CONFIGURATION% -o /app/publ FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "WikiDataTest.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "WikiDataTest.dll"] + +EXPOSE 7259 diff --git a/wikidata_service/WikiDataTest/Properties/launchSettings.json b/wikidata_service/WikiDataTest/Properties/launchSettings.json index ecc521d..0a8ea3d 100644 --- a/wikidata_service/WikiDataTest/Properties/launchSettings.json +++ b/wikidata_service/WikiDataTest/Properties/launchSettings.json @@ -8,7 +8,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7259;http://localhost:5276" + "applicationUrl": "http://localhost:7259" }, "IIS Express": { "commandName": "IISExpress",