diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a5ae50d6..02aae8da 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,6 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d97540f6..34a61687 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -140,6 +140,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:
@@ -160,7 +164,9 @@ jobs:
user: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
command: |
- wget https://raw.githubusercontent.com/arquisoft/wiq_es2b/master/docker-compose.yml -O docker-compose.yml
- wget https://raw.githubusercontent.com/arquisoft/wiq_es2b/master/.env -O .env
- docker compose down
- docker compose --profile prod up -d
+
+ wget https://raw.githubusercontent.com/arquisoft/wiq_0/master/docker-compose.yml -O docker-compose.yml
+ wget https://raw.githubusercontent.com/arquisoft/wiq_0/master/.env -O .env
+ docker compose --profile prod down
+ docker compose --profile prod up -d --pull always
+
diff --git a/README.md b/README.md
index dfea3335..924783d7 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,12 @@ and launch it with docker compose:
docker compose --profile dev up --build
```
+and tear it down:
+
+```sh
+docker compose --profile dev down
+```
+
### Starting Component by component
First, start the database. Either install and run Mongo or run it using docker:
@@ -100,10 +106,12 @@ deploy:
user: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
command: |
- wget https://raw.githubusercontent.com/arquisoft/wiq_es2b/master/docker-compose.yml -O docker-compose.yml
- wget https://raw.githubusercontent.com/arquisoft/wiq_es2b/master/.env -O .env
- docker compose down
- docker compose --profile prod up -d
+
+ wget https://raw.githubusercontent.com/arquisoft/wiq_0/master/docker-compose.yml -O docker-compose.yml
+ wget https://raw.githubusercontent.com/arquisoft/wiq_0/master/.env -O .env
+ docker compose --profile prod down
+ docker compose --profile prod up -d --pull always
+
```
This action uses three secrets that must be configured in the repository:
@@ -111,4 +119,5 @@ This action uses three secrets that must be configured in the repository:
- DEPLOY_USER: user with permission to execute the commands in the remote machine.
- DEPLOY_KEY: key to authenticate the user in the remote machine.
-Note that this action logs in the remote machine and downloads the docker-compose file from the repository and launches it. Obviously, previous actions have been executed which have uploaded the docker images to the GitHub Packages repository.
+Note that this action logs in the remote machine and downloads the docker-compose file from the repository and launches it.
+Obviously, previous actions have been executed which have uploaded the docker images to the GitHub Packages repository.
diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js
index a20b94cc..9399e3a8 100644
--- a/gatewayservice/gateway-service.js
+++ b/gatewayservice/gateway-service.js
@@ -2,6 +2,11 @@ const express = require('express');
const axios = require('axios');
const cors = require('cors');
const promBundle = require('express-prom-bundle');
+//libraries required for OpenAPI-Swagger
+const swaggerUi = require('swagger-ui-express');
+const fs = require("fs")
+const YAML = require('yaml')
+
const app = express();
const port = 8000;
@@ -41,6 +46,29 @@ app.post('/adduser', async (req, res) => {
}
});
+
+app.post('/addgame', async (req, res) => {
+ try {
+ const userResponse = await axios.post(userServiceUrl+'/addgame', req.body);
+ res.json(userResponse.data);
+ } catch (error) {
+ res.status(error.response.status).json({ error: error.response.data.error });
+ }
+});
+
+
+app.get('/getgamehistory/:username', async (req, res) => {
+ try {
+ const username = req.params.username;
+ const userResponse = await axios.get(`${userServiceUrl}/getgamehistory/${username}`);
+ res.json(userResponse.data);
+ } catch (error) {
+ res.status(error.response.status).json({ error: error.response.data.error });
+ }
+});
+
+
+
app.post('/createquestion', async (req, res) => {
try {
// Create a petition to the URL (le llegará a creation-service.js) with the option /createquestion and the req.body params
@@ -63,9 +91,29 @@ app.post('/getquestionshistory', 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}`);
});
+
module.exports = server
diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml
new file mode 100644
index 00000000..67bffccb
--- /dev/null
+++ b/gatewayservice/openapi.yaml
@@ -0,0 +1,144 @@
+openapi: 3.0.0
+info:
+ title: Gatewayservice API
+ description: Gateway OpenAPI specification.
+ version: 0.2.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
diff --git a/gatewayservice/package-lock.json b/gatewayservice/package-lock.json
index fc5f2d60..430cbe99 100644
--- a/gatewayservice/package-lock.json
+++ b/gatewayservice/package-lock.json
@@ -12,7 +12,10 @@
"axios": "^1.6.5",
"cors": "^2.8.5",
"express": "^4.18.2",
- "express-prom-bundle": "^7.0.0"
+ "express-openapi": "^12.1.3",
+ "express-prom-bundle": "^7.0.0",
+ "swagger-ui-express": "^5.0.0",
+ "yaml": "^2.4.1"
},
"devDependencies": {
"jest": "^29.7.0",
@@ -1277,6 +1280,37 @@
"node": ">= 0.6"
}
},
+ "node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -1333,7 +1367,6 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
@@ -1474,8 +1507,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/bintrees": {
"version": "1.0.2",
@@ -1510,7 +1542,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1751,8 +1782,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/content-disposition": {
"version": "0.5.4",
@@ -1942,6 +1972,14 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/difunc": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/difunc/-/difunc-0.0.4.tgz",
+ "integrity": "sha512-zBiL4ALDmviHdoLC0g0G6wVme5bwAow9WfhcZLLopXCAWgg3AEf7RYTs2xugszIGulRHzEVDF/SHl9oyQU07Pw==",
+ "dependencies": {
+ "esprima": "^4.0.0"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -2015,7 +2053,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
@@ -2121,6 +2158,21 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/express-normalize-query-params-middleware": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/express-normalize-query-params-middleware/-/express-normalize-query-params-middleware-0.5.1.tgz",
+ "integrity": "sha512-KUBjEukYL9KJkrphVX3ZgMHgMTdgaSJe+FIOeWwJIJpCw8UZQPIylt0MYddSyUwbms4LQ8RC4wmavcLUP9uduA=="
+ },
+ "node_modules/express-openapi": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/express-openapi/-/express-openapi-12.1.3.tgz",
+ "integrity": "sha512-F570dVC5ENSkLu1SpDFPRQ13Y3a/7Udh0rfHyn3O1QrE81fPmlhnAo1JRgoNtbMRJ6goHNymxU1TVSllgFZBlQ==",
+ "dependencies": {
+ "express-normalize-query-params-middleware": "^0.5.0",
+ "openapi-framework": "^12.1.3",
+ "openapi-types": "^12.1.3"
+ }
+ },
"node_modules/express-prom-bundle": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-7.0.0.tgz",
@@ -2138,6 +2190,11 @@
"prom-client": ">=15.0.0"
}
},
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2264,11 +2321,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/fs-routes": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/fs-routes/-/fs-routes-12.1.3.tgz",
+ "integrity": "sha512-Vwxi5StpKj/pgH7yRpNpVFdaZr16z71KNTiYuZEYVET+MfZ31Zkf7oxUmNgyZxptG8BolRtdMP90agIhdyiozg==",
+ "peerDependencies": {
+ "glob": ">=7.1.6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
@@ -2349,7 +2413,6 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -2526,7 +2589,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -2563,6 +2625,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-dir/-/is-dir-1.0.0.tgz",
+ "integrity": "sha512-vLwCNpTNkFC5k7SBRxPubhOCryeulkOsSkjbGyZ8eOzZmzMS+hSEO/Kn9ZOVhFNAlRZTFc4ZKql48hESuYUPIQ=="
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -3328,7 +3395,6 @@
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
- "dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -3355,6 +3421,11 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -3403,6 +3474,11 @@
"node": ">=8"
}
},
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3552,7 +3628,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -3643,7 +3718,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -3663,6 +3737,97 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/openapi-default-setter": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-default-setter/-/openapi-default-setter-12.1.3.tgz",
+ "integrity": "sha512-wHKwvEuOWwke5WcQn8pyCTXT5WQ+rm9FpJmDeEVECEBWjEyB/MVLYfXi+UQeSHTTu2Tg4VDHHmzbjOqN6hYeLQ==",
+ "dependencies": {
+ "openapi-types": "^12.1.3"
+ }
+ },
+ "node_modules/openapi-framework": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-framework/-/openapi-framework-12.1.3.tgz",
+ "integrity": "sha512-p30PHWVXda9gGxm+t/1X2XvEcufW1YhzeDQwc5SsgDnBXt8gkuu1SwrioGJ66wxVYEzfSRTTf/FMLhI49ut8fQ==",
+ "dependencies": {
+ "difunc": "0.0.4",
+ "fs-routes": "^12.1.3",
+ "glob": "*",
+ "is-dir": "^1.0.0",
+ "js-yaml": "^3.10.0",
+ "openapi-default-setter": "^12.1.3",
+ "openapi-request-coercer": "^12.1.3",
+ "openapi-request-validator": "^12.1.3",
+ "openapi-response-validator": "^12.1.3",
+ "openapi-schema-validator": "^12.1.3",
+ "openapi-security-handler": "^12.1.3",
+ "openapi-types": "^12.1.3",
+ "ts-log": "^2.1.4"
+ }
+ },
+ "node_modules/openapi-jsonschema-parameters": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-jsonschema-parameters/-/openapi-jsonschema-parameters-12.1.3.tgz",
+ "integrity": "sha512-aHypKxWHwu2lVqfCIOCZeJA/2NTDiP63aPwuoIC+5ksLK5/IQZ3oKTz7GiaIegz5zFvpMDxDvLR2DMQQSkOAug==",
+ "dependencies": {
+ "openapi-types": "^12.1.3"
+ }
+ },
+ "node_modules/openapi-request-coercer": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-request-coercer/-/openapi-request-coercer-12.1.3.tgz",
+ "integrity": "sha512-CT2ZDhBmAZpHhAzHhEN+/J5oMK3Ds99ayLLdXh2Aw1DCcn72EM8VuIGVwG5fSjvkMsgtn7FgltFosHqeM6PRFQ==",
+ "dependencies": {
+ "openapi-types": "^12.1.3",
+ "ts-log": "^2.1.4"
+ }
+ },
+ "node_modules/openapi-request-validator": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-request-validator/-/openapi-request-validator-12.1.3.tgz",
+ "integrity": "sha512-HW1sG00A9Hp2oS5g8CBvtaKvRAc4h5E4ksmuC5EJgmQ+eAUacL7g+WaYCrC7IfoQaZrjxDfeivNZUye/4D8pwA==",
+ "dependencies": {
+ "ajv": "^8.3.0",
+ "ajv-formats": "^2.1.0",
+ "content-type": "^1.0.4",
+ "openapi-jsonschema-parameters": "^12.1.3",
+ "openapi-types": "^12.1.3",
+ "ts-log": "^2.1.4"
+ }
+ },
+ "node_modules/openapi-response-validator": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-response-validator/-/openapi-response-validator-12.1.3.tgz",
+ "integrity": "sha512-beZNb6r1SXAg1835S30h9XwjE596BYzXQFAEZlYAoO2imfxAu5S7TvNFws5k/MMKMCOFTzBXSjapqEvAzlblrQ==",
+ "dependencies": {
+ "ajv": "^8.4.0",
+ "openapi-types": "^12.1.3"
+ }
+ },
+ "node_modules/openapi-schema-validator": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-12.1.3.tgz",
+ "integrity": "sha512-xTHOmxU/VQGUgo7Cm0jhwbklOKobXby+/237EG967+3TQEYJztMgX9Q5UE2taZKwyKPUq0j11dngpGjUuxz1hQ==",
+ "dependencies": {
+ "ajv": "^8.1.0",
+ "ajv-formats": "^2.0.2",
+ "lodash.merge": "^4.6.1",
+ "openapi-types": "^12.1.3"
+ }
+ },
+ "node_modules/openapi-security-handler": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-security-handler/-/openapi-security-handler-12.1.3.tgz",
+ "integrity": "sha512-25UTAflxqqpjCLrN6rRhINeM1L+MCDixMltiAqtBa9Zz/i7UkWwYwdzqgZY3Cx3vRZElFD09brYxo5VleeP3HQ==",
+ "dependencies": {
+ "openapi-types": "^12.1.3"
+ }
+ },
+ "node_modules/openapi-types": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
+ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -3753,7 +3918,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3886,6 +4050,14 @@
"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",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/pure-rand": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz",
@@ -3953,6 +4125,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -4171,8 +4351,7 @@
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"node_modules/stack-utils": {
"version": "2.0.6",
@@ -4389,6 +4568,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/swagger-ui-dist": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.12.0.tgz",
+ "integrity": "sha512-Rt1xUpbHulJVGbiQjq9yy9/r/0Pg6TmpcG+fXTaMePDc8z5WUw4LfaWts5qcNv/8ewPvBIbY7DKq7qReIKNCCQ=="
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.0.tgz",
+ "integrity": "sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==",
+ "dependencies": {
+ "swagger-ui-dist": ">=5.0.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0 || >=5.0.0-beta"
+ }
+ },
"node_modules/tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
@@ -4447,6 +4645,11 @@
"node": ">=0.6"
}
},
+ "node_modules/ts-log": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.5.tgz",
+ "integrity": "sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA=="
+ },
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -4523,6 +4726,14 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/url-value-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/url-value-parser/-/url-value-parser-2.2.0.tgz",
@@ -4605,8 +4816,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/write-file-atomic": {
"version": "4.0.2",
@@ -4636,6 +4846,17 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
+ "node_modules/yaml": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
+ "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/gatewayservice/package.json b/gatewayservice/package.json
index 87d5256c..779178c7 100644
--- a/gatewayservice/package.json
+++ b/gatewayservice/package.json
@@ -21,7 +21,10 @@
"axios": "^1.6.5",
"cors": "^2.8.5",
"express": "^4.18.2",
- "express-prom-bundle": "^7.0.0"
+ "express-openapi": "^12.1.3",
+ "express-prom-bundle": "^7.0.0",
+ "swagger-ui-express": "^5.0.0",
+ "yaml": "^2.4.1"
},
"devDependencies": {
"jest": "^29.7.0",
diff --git a/sonar-project.properties b/sonar-project.properties
index 8feb5c57..1807846d 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,15 +1,19 @@
+
sonar.projectKey=Arquisoft_wiq_es2b
sonar.organization=arquisoft
# This is the name and version displayed in the SonarCloud UI.
sonar.projectName=wiq_es2b
+
sonar.projectVersion=1.0
# Encoding of the source code. Default is default system encoding
sonar.host.url=https://sonarcloud.io
sonar.language=js
+
sonar.projectName=wiq_es2b
+
sonar.coverage.exclusions=**/*.test.js
sonar.sources=webapp/src/components,users/authservice,users/userservice,gatewayservice
sonar.sourceEncoding=UTF-8
diff --git a/users/userservice/playedGame-model.js b/users/userservice/playedGame-model.js
new file mode 100644
index 00000000..ef3f07cd
--- /dev/null
+++ b/users/userservice/playedGame-model.js
@@ -0,0 +1,17 @@
+const mongoose = require('mongoose');
+
+const gameSchema = new mongoose.Schema({
+ username: { type: String, required: true },
+ duration: Number,
+ questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }],
+ date: { type: Date, default: Date.now } ,
+ percentage: Number,
+ totalQuestions: Number,
+ correctAnswers: Number,
+ incorrectAnswers: Number
+});
+
+const Game = mongoose.model('Game', gameSchema);
+
+module.exports = Game;
+
diff --git a/users/userservice/question-model.js b/users/userservice/question-model.js
new file mode 100644
index 00000000..80b3edcd
--- /dev/null
+++ b/users/userservice/question-model.js
@@ -0,0 +1,11 @@
+const mongoose = require('mongoose');
+
+const questionSchema = new mongoose.Schema({
+ question: String,
+ correctAnswer: String,
+ userAnswer: String
+});
+
+const Question = mongoose.model('Question', questionSchema);
+
+module.exports = Question;
diff --git a/users/userservice/user-model.js b/users/userservice/user-model.js
index 71d81b5f..a1bd2e1b 100644
--- a/users/userservice/user-model.js
+++ b/users/userservice/user-model.js
@@ -4,6 +4,7 @@ const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
+ unique: true
},
password: {
type: String,
diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js
index be958427..9f083dba 100644
--- a/users/userservice/user-service.js
+++ b/users/userservice/user-service.js
@@ -4,6 +4,8 @@ const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const bodyParser = require('body-parser');
const User = require('./user-model')
+const Game = require('./playedGame-model')
+const Question = require('./question-model')
const app = express();
const port = 8001;
@@ -20,9 +22,9 @@ mongoose.connect(mongoUri);
// Function to validate required fields in the request body
function validateRequiredFields(req, requiredFields) {
for (const field of requiredFields) {
- if (!(field in req.body)) {
- throw new Error(`Missing required field: ${field}`);
- }
+ if (!(field in req.body)) {
+ throw new Error(`Missing required field: ${field}`);
+ }
}
}
@@ -42,17 +44,78 @@ app.post('/adduser', async (req, res) => {
await newUser.save();
res.json(newUser);
} catch (error) {
- res.status(400).json({ error: error.message });
- }});
+ res.status(400).json({
+ error: error.message
+ });
+ }
+});
+
+app.post('/addgame', async (req, res) => {
+ try {
+ // Obtener los datos del juego desde el cuerpo de la solicitud
+ const gameData = req.body;
+
+ // Convertir las preguntas del juego en ObjectId
+ const questionIds = await Promise.all(gameData.questions.map(async (question) => {
+ const existingQuestion = await Question.findOne({
+ question: question.question,
+ correctAnswer: question.correctAnswer,
+ userAnswer: question.userAnswer
+ });
+ if (existingQuestion) {
+ return existingQuestion._id;
+ } else {
+ const newQuestion = new Question(question);
+ await newQuestion.save();
+ return newQuestion._id;
+ }
+ }));
+
+ // Reemplazar las preguntas en el juego con sus ObjectId
+ gameData.questions = questionIds;
+
+ // Crear una nueva instancia del modelo de juego con los datos proporcionados
+ const newGame = new Game(gameData);
+
+ // Guardar el nuevo juego en la base de datos
+ await newGame.save();
+
+ // Enviar una respuesta de éxito
+ res.status(200).json({ message: "Partida guardada exitosamente" });
+ } catch (error) {
+ // Manejar errores y enviar una respuesta de error con el mensaje de error
+ console.error("Error al guardar el juego:", error);
+ res.status(400).json({ error: error.message });
+ }
+});
+
+
+
+app.get('/getgamehistory/:username', async (req, res) => {
+ try {
+ const username = req.params.username;
+ console.log("Se está intentando encontrar el historial del usuario " + username);
+ // Buscar las partidas asociadas al nombre de usuario proporcionado
+ const games = await Game.find({ username }).populate('questions');
+ console.log("Se encontraron los juegos para " + username + ": ", games);
+ res.json(games);
+ } catch (error) {
+ res.status(400).json({
+ error: error.message
+ });
+ }
+});
const server = app.listen(port, () => {
- console.log(`User Service listening at http://localhost:${port}`);
+ console.log(`User 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
\ No newline at end of file
diff --git a/webapp/README.md b/webapp/README.md
index 9568101e..33dd89a5 100644
--- a/webapp/README.md
+++ b/webapp/README.md
@@ -108,14 +108,21 @@ E2E tests are maybe the most difficult part to integrate in our system. We have
In this project, the E2E testing user stories are defined using Cucumber. Cucumber uses a language called Gherkin to define the user stories. You can find the example in the `features` directory. Then, the actual tests are in the folder `steps`. We are going to configure jest to execute only the tests of this directory (check the `jest.config.ts` file in the `e2e` directory).
-The E2E tests have two extra difficulties. The first one, we need a browser to perform the tests as if the user was using the application. For this matter, we use `jest-puppeteer` that will launch a Chromium instance for running the tests. The browser is started in the `beforeAll` function. Note that the browser is launched in a headless mode. This is necessary for the tests to run in the CI environment. If you want to debug the tests you can always turn this feature off. The second problem is that we need all our services at the same time to be able to run the tests. For achieving this, we are going to use the package `start-server-and-test`. This package, allows us to launch multiple servers and then run the tests. No need for any configuration. We can configure it straight in the `package.json` file:
+The E2E tests have two extra difficulties. The first one, we need a browser to perform the tests as if the user was using the application.
+For this matter, we use `jest-puppeteer` that will launch a Chromium instance for running the tests.
+The browser is started in the `beforeAll` function. Note that the browser is launched in a headless mode.
+This is necessary for the tests to run in the CI environment. If you want to debug the tests you can always turn this feature off.
+The second problem is that we need all our services at the same time to be able to run the tests.
+For achieving this, we are going to use the package `start-server-and-test`.
+This package, allows us to launch multiple servers and then run the tests.
+No need for any configuration. We can configure it straight in the `package.json` file:
```json
"test:e2e": "start-server-and-test 'node e2e/test-environment-setup.js' http://localhost:8000/health prod 3000 \"cd e2e && jest\"",
```
-The package accepts pairs of parameters (launch a server and an URL to check if it is running. It also accepts npm commands (for instance prod, for the webapp, that will run `npm run prod`). The last parameter of the task will be launching Jest to run the E2E tests.
+The package accepts pairs of parameters (launch a server and an URL to check if it is running. It also accepts npm commands (for instance prod, for the webapp, that will run `npm run prod`). The last parameter of the task will be launching Jest to run the e2e tests.
Note that we are handling all the setup for the auth and user microservices using the file `test-environment-setup.js`. This file has the code needed to run everything, including an in-memory Mongo database to be able to execute the tests.
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
index 7b2f7861..0256e663 100644
--- a/webapp/package-lock.json
+++ b/webapp/package-lock.json
@@ -22,6 +22,8 @@
"web-vitals": "^3.5.1"
},
"devDependencies": {
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"axios-mock-adapter": "^1.22.0",
"expect-puppeteer": "^9.0.2",
"jest": "^29.3.1",
@@ -659,9 +661,17 @@
}
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
- "version": "7.21.0-placeholder-for-preset-env.2",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
- "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "version": "7.21.11",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz",
+ "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.18.6",
+ "@babel/helper-create-class-features-plugin": "^7.21.0",
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
+ },
"engines": {
"node": ">=6.9.0"
},
@@ -1905,6 +1915,17 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
diff --git a/webapp/package.json b/webapp/package.json
index af6ba0d7..18a15f71 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -43,6 +43,8 @@
]
},
"devDependencies": {
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"axios-mock-adapter": "^1.22.0",
"expect-puppeteer": "^9.0.2",
"jest": "^29.3.1",
diff --git a/webapp/src/App.js b/webapp/src/App.js
index 6e130102..50a60ea3 100644
--- a/webapp/src/App.js
+++ b/webapp/src/App.js
@@ -16,6 +16,7 @@ function App() {
return (
+
Fecha | +Tiempo de partida (s) | +Porcentaje de Aciertos | +Número de Preguntas | +Número de Aciertos | +Número de Fallos | +
---|---|---|---|---|---|
{game.date} | +{game.duration} | +{game.percentage}% | +{game.totalQuestions} | +{game.correctAnswers} | +{game.incorrectAnswers} | +
+ Pregunta {index + 1}: {question.question} +Respuesta Correcta: {question.correctAnswer} +Respuesta del Usuario: {question.userAnswer} +La respuesta fue: {question.correctAnswer === question.userAnswer ? 'Correcta' : 'Incorrecta'} + |
+