diff --git a/README.md b/README.md index 1b3ae97b..25b02d26 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ The deploy action is the following: 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-questiongenerationservice, docker-push-questionservice] steps: - name: Deploy over SSH uses: fifsky/ssh-action@master diff --git a/docker-compose.yml b/docker-compose.yml index 2f821cb0..6acbff77 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,20 @@ services: - "8003:8003" networks: - mynetwork + + questionservice: + container_name: questionservice-${teamname:-defaultASW} + image: ghcr.io/arquisoft/wiq_es3b/questionservice:latest + profiles: ["dev", "prod"] + build: ./questionservice + depends_on: + - mongodb + ports: + - "8004:8004" + networks: + - mynetwork + environment: + MONGODB_URI: mongodb://mongodb:27017/questionsdb gatewayservice: container_name: gatewayservice-${teamname:-defaultASW} @@ -59,6 +73,7 @@ services: - userservice - authservice - questiongenerationservice + - questionservice ports: - "8000:8000" networks: @@ -67,6 +82,7 @@ services: AUTH_SERVICE_URL: http://authservice:8002 USER_SERVICE_URL: http://userservice:8001 QUESTION_GENERATION_SERVICE_URL: http://questiongenerationservice:8003 + QUESTIONS_SERVICE_URL: http://questionservice:8004 webapp: container_name: webapp-${teamname:-defaultASW} @@ -76,7 +92,10 @@ services: depends_on: - gatewayservice ports: - - "3000:3000" + - "3000:3000" + #volumes: + #- ./webapp:/app + prometheus: image: prom/prometheus diff --git a/docs/images/GameView.png b/docs/images/GameView.png new file mode 100644 index 00000000..6e9cf2c8 Binary files /dev/null and b/docs/images/GameView.png differ diff --git a/docs/images/StartView.png b/docs/images/StartView.png new file mode 100644 index 00000000..4787dd22 Binary files /dev/null and b/docs/images/StartView.png differ diff --git a/docs/images/UserView.png b/docs/images/UserView.png new file mode 100644 index 00000000..e3811f7b Binary files /dev/null and b/docs/images/UserView.png differ diff --git a/docs/src/03_system_scope_and_context.adoc b/docs/src/03_system_scope_and_context.adoc index c528e907..ec9a5198 100644 --- a/docs/src/03_system_scope_and_context.adoc +++ b/docs/src/03_system_scope_and_context.adoc @@ -1,75 +1,90 @@ ifndef::imagesdir[:imagesdir: ../images] [[section-system-scope-and-context]] -== System Scope and Context +# System Scope and Context [role="arc42help"] -**** -.Contents -System scope and context - as the name suggests - delimits your system (i.e. your scope) from all its communication partners -(neighboring systems and users, i.e. the context of your system). It thereby specifies the external interfaces. +--- -If necessary, differentiate the business context (domain specific inputs and outputs) from the technical context (channels, protocols, hardware). +## Contents +### Scope and context -.Motivation -The domain interfaces and technical interfaces to communication partners are among your system's most critical aspects. Make sure that you completely understand them. +This project aims to develop a quiz game. +The main constraints are developing the game as a web app and using Wikidata to obtain the questions and answers. -.Form -Various options: +--- -* Context diagrams -* Lists of communication partners and their interfaces. +## Business Context +[role="arc42help"] -.Further Information +### Contents -See https://docs.arc42.org/section-3/[Context and Scope] in the arc42 documentation. +* *Users:* They interact directly with the application through the user interface provided by the frontend using React, HTML, CSS, and JavaScript. +* *Wikidata API:* The application communicates with the Wikidata service to obtain questions and answers related to different topics. Requests are made using the HTTP protocol, and response data is received in JSON format. -**** +--- +### Motivation -=== Business Context +Regarding the information exchanged with the application, it will require having a registered account to play, and it will also exchange information with a MongoDB database and Wikidata itself to obtain the questions. -[role="arc42help"] -**** -.Contents -Specification of *all* communication partners (users, IT-systems, ...) with explanations of domain specific inputs and outputs or interfaces. -Optionally you can add domain specific formats or communication protocols. +--- -.Motivation -All stakeholders should understand which data are exchanged with the environment of the system. +### Form -.Form -All kinds of diagrams that show the system as a black box and specify the domain interfaces to communication partners. +|=== -Alternatively (or additionally) you can use a table. -The title of the table is the name of your system, the three columns contain the name of the communication partner, the inputs, and the outputs. +| *Quiz Game* | *Comunication Partners* | *Inputs* | *Outputs* +| Users | User Interface | User answers | Game questions +| Wikidata API | API Service | Question requests | JSON Responses -**** +|=== -**** +--- -**** +### Context diagram +[plantuml, "context", png] +---- +component "App" as app -=== Technical Context +:User: -> [app]: Answer question +[app] -> User: Return result -[role="arc42help"] -**** -.Contents -Technical interfaces (channels and transmission media) linking your system to its environment. In addition a mapping of domain specific input/output to the channels, i.e. an explanation which I/O uses which channel. +database DB +[app] -> DB: Ask for question +DB -> [app]: Return question + +component "WikiData" as wd +[app] --> wd: Ask for keyword +wd --> [app]: Return keyword +---- -.Motivation -Many stakeholders make architectural decision based on the technical interfaces between the system and its context. Especially infrastructure or hardware designers decide these technical interfaces. +--- + +## Technical Context + +[role="arc42help"] -.Form -E.g. UML deployment diagram describing channels to neighboring systems, -together with a mapping table showing the relationships between channels and input/output. +### Contents -**** +* *HTTP Channel:* The application uses the HTTP protocol to communicate with the Wikidata API service. +* *MongoDB Data Channel:* Interactions with the MongoDB database allow for storing and retrieving questions and answers. -**** +--- -**** +### Deployment diagram +[plantuml, "deployment", png] +---- +node "Aplication Server" as app +node "DB Server" as db { +artifact "MongoDB Server" +} +node Wikidata as w +node Interface as i -**** +app - db +app -- w +app -- i +---- diff --git a/docs/src/04_solution_strategy.adoc b/docs/src/04_solution_strategy.adoc index 7bf03f7a..97a4dd76 100644 --- a/docs/src/04_solution_strategy.adoc +++ b/docs/src/04_solution_strategy.adoc @@ -1,32 +1,85 @@ ifndef::imagesdir[:imagesdir: ../images] [[section-solution-strategy]] -== Solution Strategy - +# Solution Strategy [role="arc42help"] -**** -.Contents -A short summary and explanation of the fundamental decisions and solution strategies, that shape system architecture. It includes +## Contents +This page contains a short summary and explanation of the fundamental decisions and solution strategies, that shape system architecture. + +--- + +### Technology Decisions +Below, all the technologies to be used in the development of the application are listed: + +* *JavaScript / React:* A JavaScript library designed to facilitate the development of web applications. +* *JavaScript / NodeJS:* An asynchronous runtime environment based on JavaScript. +* *MongoDB:* A document-oriented NoSQL database system. +* *Docker:* Used for deploying applications locally. +* *Azure:* Used for deploying applications on a server. + +--- + +### Top-Level Decisions +In this section, a summarized list of all decisions regarding the architecture of the application is included. + +|=== + +| *Architectural Pattern* | In this project, the pattern based on the Microservices model is used. +| *Frontend (Client)* | React: Building the user interface +| *Backend (Server)* | NodeJS: Building the server on the backend +| | Mongoose: Interaction with the database +| *Database* | MongoDB: NoSQL database storing data in BSON format +| *Deployment* | Azure cloud services + +|=== + +--- + +### Quality Goals +Next, several quality goals are established, along with the decisions made to achieve them. + +|=== +| *Quality Goal* | *Decisions* + +| Usability +| Creation of a responsive interface adaptable to all types of screens. + +| Accessibility +| Compliance with accessibility standards to ensure a usable application. + +| Maintainability +| A structured project that facilitates the maintainability of the application. + +| Testing +| Assurance of a fully functional and error-free application. + +|=== + +--- + +### Relevant Organizational +Regarding the organization of the team, we have decided to use agile methodologies for better and faster group work. In this project, we will follow the best practices of the SCRUM framework. -* technology decisions -* decisions about the top-level decomposition of the system, e.g. usage of an architectural pattern or design pattern -* decisions on how to achieve key quality goals -* relevant organizational decisions, e.g. selecting a development process or delegating certain tasks to third parties. +Practices to be followed: -.Motivation -These decisions form the cornerstones for your architecture. They are the foundation for many other detailed decisions or implementation rules. +* Division of the project into *tasks*. +* Equitable *assignment* of tasks. +* Frequent *meetings* on the progress of the application. +* Construction of *"Alpha"* versions before the final release. -.Form -Keep the explanations of such key decisions short. +Additionally, "Issues" and the GitHub-provided Kanban (ToDo - In Progress - Done) are used for communication. -Motivate what was decided and why it was decided that way, -based upon problem statement, quality goals and key constraints. -Refer to details in the following sections. +--- +### Motivation +This section addresses the fundamental decisions for the project's architecture. -.Further Information +|=== -See https://docs.arc42.org/section-4/[Solution Strategy] in the arc42 documentation. +| *Data Management* | +| *Deployment* | +| *Security* | +| *Monitoring* | -**** +|=== diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 402821b4..e75c44a8 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -9,6 +9,7 @@ const port = 8000; const authServiceUrl = process.env.AUTH_SERVICE_URL || 'http://localhost:8002'; const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001'; const questionGenerationServiceUrl = process.env.QUESTION_GENERATION_SERVICE_URL || 'http://localhost:8003'; +const questionServiceUrl = process.env.QUESTIONS_SERVICE_URL || 'http://localhost:8004'; app.use(cors()); app.use(express.json()); @@ -52,6 +53,17 @@ app.get('/api/questions/create', async (req, res) => { } }); +// Ruta para agregar una nueva pregunta +app.post('/addquestion', async (req, res) => { + try { + // Forward the add question request to the questions service + const questionResponse = await axios.post(questionServiceUrl + '/addquestion', req.body); + res.json(questionResponse.data); + } catch (error) { + res.status(error.response.status).json({ error: error.response.data.error }); + } +}); + // Start the gateway service const server = app.listen(port, () => { console.log(`Gateway Service listening at http://localhost:${port}`); diff --git a/package-lock.json b/package-lock.json index 9c6bb280..2951e9ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,989 @@ { "name": "wiq_es3b", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "devDependencies": { + "serve": "^14.2.1" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", + "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "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/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "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 + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "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" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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 + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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==", + "dev": true + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "dev": true, + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "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==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "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" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "dev": true + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/serve": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.1.tgz", + "integrity": "sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==", + "dev": true, + "dependencies": { + "@zeit/schemas": "2.29.0", + "ajv": "8.11.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "dev": true, + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.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==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + } + } } diff --git a/package.json b/package.json index 0967ef42..66d13a98 100644 --- a/package.json +++ b/package.json @@ -1 +1,5 @@ -{} +{ + "devDependencies": { + "serve": "^14.2.1" + } +} \ No newline at end of file diff --git a/question_generator/Dockerfile b/question_generator/Dockerfile index 81221b5c..d7c06392 100644 --- a/question_generator/Dockerfile +++ b/question_generator/Dockerfile @@ -2,7 +2,7 @@ FROM node:20 # Set the working directory in the container -WORKDIR /usr/src/questionservice +WORKDIR /usr/src/questiongenerationservice # Copy package.json and package-lock.json to the working directory COPY package*.json ./ @@ -17,4 +17,4 @@ COPY . . EXPOSE 8003 # Define the command to run your app -CMD ["node", "questionService.js"] +CMD ["node", "questionGenerationService.js"] diff --git a/question_generator/questionService.js b/question_generator/questionGenerationService.js similarity index 100% rename from question_generator/questionService.js rename to question_generator/questionGenerationService.js diff --git a/questionservice/.dockerignore b/questionservice/.dockerignore new file mode 100644 index 00000000..3091757a --- /dev/null +++ b/questionservice/.dockerignore @@ -0,0 +1,2 @@ +node_modules +coverage \ No newline at end of file diff --git a/questionservice/Dockerfile b/questionservice/Dockerfile new file mode 100644 index 00000000..8b14159b --- /dev/null +++ b/questionservice/Dockerfile @@ -0,0 +1,20 @@ +# Use an official Node.js runtime as a parent image +FROM node:20 + +# Set the working directory in the container +WORKDIR /usr/src/questionservice + +# 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 8004 + +# Define the command to run your app +CMD ["node", "question-service.js"] diff --git a/questionservice/package.json b/questionservice/package.json new file mode 100644 index 00000000..82096e9c --- /dev/null +++ b/questionservice/package.json @@ -0,0 +1,20 @@ +{ + "name": "questionservice", + "version": "1.0.0", + "description": "Question service, in charge of adding the questions to the application", + "main": "service.js", + "scripts": { + "start": "node question-service.js", + "test": "jest" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/arquisoft/wiq_es3b.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/arquisoft/wiq_es3b/issues" + }, + "homepage": "https://github.com/arquisoft/wiq_es3b#readme" +} diff --git a/questionservice/question-model.js b/questionservice/question-model.js new file mode 100644 index 00000000..89bf65ed --- /dev/null +++ b/questionservice/question-model.js @@ -0,0 +1,32 @@ +const mongoose = require('mongoose'); + +const questionSchema = new mongoose.Schema({ + question: { + type: String, + required: true, + }, + options: { + type: [String], + required: true, + validate: { + validator: function (options) { + return options.length >= 2 && options.length <= 4; + }, + message: 'Options must have between 2 and 4 elements.', + }, + }, + correctOptionIndex: { + type: Number, + required: true, + validate: { + validator: function (index) { + return index >= 0 && index < 4; // Max of 4 options + }, + message: 'Correct option index must be between 0 and 3.', + }, + }, +}); + +const Question = mongoose.model('Question', questionSchema); + +module.exports = Question; diff --git a/questionservice/question-service.js b/questionservice/question-service.js new file mode 100644 index 00000000..97873347 --- /dev/null +++ b/questionservice/question-service.js @@ -0,0 +1,43 @@ +const express = require('express'); +const mongoose = require('mongoose'); +const Question = require('./question-model'); // Importa el modelo de preguntas + +const app = express(); +const port = 8004; // Puerto para el servicio de preguntas + +app.use(express.json()); + +// Ruta para agregar una nueva pregunta +app.post('/addquestion', async (req, res) => { + try { + validateRequiredFields(req, ['question', 'options', 'correctOptionIndex']); + + const { question, options, correctOptionIndex } = req.body; + + // Crea una nueva instancia del modelo de preguntas + const newQuestion = new Question({ + question, + options, + correctOptionIndex, + }); + + // Guarda la nueva pregunta en la base de datos + const savedQuestion = await newQuestion.save(); + + res.status(201).json(savedQuestion); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// logica para preguntas?? + +// Conecta a la base de datos de preguntas +const mongoUri = process.env.MONGODB_QUESTIONS_URI || 'mongodb://localhost:27017/questionsdb'; +mongoose.connect(mongoUri); + +const server = app.listen(port, () => { + console.log(`Questions Service listening at http://localhost:${port}`); +}); + +module.exports = server; \ No newline at end of file diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 3cbad8b7..6422ea29 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -3,9 +3,15 @@ FROM node:20 COPY . /app WORKDIR /app +# Copy package.json and package-lock.json to the working directory +#COPY package*.json ./ + #Install the dependencies RUN npm install +# Copy the app source code to the working directory +#COPY . . + ARG API_URI="http://localhost:8000" ENV REACT_APP_API_ENDPOINT=$API_URI diff --git a/webapp/src/App.css b/webapp/src/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/webapp/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/webapp/src/App.js b/webapp/src/App.js index d932005b..f8cc21da 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,38 +1,25 @@ -import React, { useState } from 'react'; -import AddUser from './components/AddUser'; -import Login from './components/Login'; -import CssBaseline from '@mui/material/CssBaseline'; -import Container from '@mui/material/Container'; -import Typography from '@mui/material/Typography'; -import Link from '@mui/material/Link'; +import { useState } from 'react' + +import { Start } from './components/Start' +import { Game } from './components/Game' +import { Participation } from './components/Participation' +import User from './components/User' function App() { - const [showLogin, setShowLogin] = useState(true); + const [menuState, setMenuState] = useState(0) - const handleToggleView = () => { - setShowLogin(!showLogin); - }; + const goTo = (parameter) => { + setMenuState(parameter) + } return ( - - - - Welcome to the 2024 edition of the Software Architecture course - - {showLogin ? : } - - {showLogin ? ( - - Don't have an account? Register here. - - ) : ( - - Already have an account? Login here. - - )} - - - ); + <> + {menuState === 0 && goTo(x)}/>} + {menuState === 1 && goTo(x)}/>} + {menuState === 2 && goTo(x)}/>} + {menuState === 3 && goTo(x)}/>} + + ) } -export default App; +export default App diff --git a/webapp/src/assets/react.svg b/webapp/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/webapp/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webapp/src/components/Game.jsx b/webapp/src/components/Game.jsx new file mode 100644 index 00000000..4cc1bd7a --- /dev/null +++ b/webapp/src/components/Game.jsx @@ -0,0 +1,67 @@ +import { Card, List, ListItem, ListItemButton, ListItemText, Typography } from '@mui/material' +import reactLogo from '../assets/react.svg' + +const Question = () => { + return( + + + + + ¿Cuál es la capital de España? + + + + + + + Londres + + + + + + + Madrid + + + + + + + Paris + + + + + + + Roma + + + + + + + ) +} + +export const Game = ({ goTo }) => { + + return ( + + <> + + React logo + + + React Quiz + + + goTo(1) }> + + Volver al menú + + + + ) +} diff --git a/webapp/src/components/Login.js b/webapp/src/components/Login.js index 0ad6268e..5810344a 100644 --- a/webapp/src/components/Login.js +++ b/webapp/src/components/Login.js @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import axios from 'axios'; import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; -const Login = () => { +const Login = ({ goTo }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); @@ -22,7 +22,7 @@ const Login = () => { setCreatedAt(userCreatedAt); setLoginSuccess(true); - + setOpenSnackbar(true); } catch (error) { setError(error.response.data.error); @@ -32,10 +32,13 @@ const Login = () => { const handleCloseSnackbar = () => { setOpenSnackbar(false); }; + + if (loginSuccess) { goTo(1); } return ( {loginSuccess ? ( +
Hello {username}! @@ -47,7 +50,7 @@ const Login = () => { ) : (
- Login + Login :D question, correct, incorrects[] +// Esquema de Juegos -> usuario, preguntas[], respuestas[], tiempo + +export const Participation = ({ goTo }) => { + return ( +
+

Participation

+ +
+ ); + }; diff --git a/webapp/src/components/Start.jsx b/webapp/src/components/Start.jsx new file mode 100644 index 00000000..a597b763 --- /dev/null +++ b/webapp/src/components/Start.jsx @@ -0,0 +1,22 @@ +import reactLogo from '../assets/react.svg' +import "../css/Start.css" + +export const Start = ({ goTo }) => { + + return ( + + <> +
+ + React logo + +
+

Quiz ASW

+
+ +
+ + ) +} \ No newline at end of file diff --git a/webapp/src/components/User.jsx b/webapp/src/components/User.jsx new file mode 100644 index 00000000..ad84a3f0 --- /dev/null +++ b/webapp/src/components/User.jsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; +import AddUser from './AddUser'; +import Login from './Login'; +import CssBaseline from '@mui/material/CssBaseline'; +import Container from '@mui/material/Container'; +import Typography from '@mui/material/Typography'; +import Link from '@mui/material/Link'; + +function User({ goTo }) { + const [showLogin, setShowLogin] = useState(true); + + const handleToggleView = () => { + setShowLogin(!showLogin); + }; + + return ( + + + + Welcome to the 2024 edition of the Software Architecture course + + {showLogin ? goTo(x)} /> : } + + {showLogin ? ( + + Don't have an account? Register here. + + ) : ( + + Already have an account? Login here. + + )} + + + ); +} + +export default User; \ No newline at end of file diff --git a/webapp/src/css/Start.css b/webapp/src/css/Start.css new file mode 100644 index 00000000..7918328c --- /dev/null +++ b/webapp/src/css/Start.css @@ -0,0 +1,3 @@ +.card { + display: flex; +} \ No newline at end of file diff --git a/webapp/src/css/User.css b/webapp/src/css/User.css new file mode 100644 index 00000000..a571b3ae --- /dev/null +++ b/webapp/src/css/User.css @@ -0,0 +1,43 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; + } + + .logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; + } + .logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); + } + .logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); + } + + @keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + @media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } + } + + .card { + padding: 2em; + } + + .read-the-docs { + color: #888; + } + \ No newline at end of file diff --git a/webapp/src/css/index.css b/webapp/src/css/index.css new file mode 100644 index 00000000..f19c08a0 --- /dev/null +++ b/webapp/src/css/index.css @@ -0,0 +1,74 @@ +@import url('https://fonts.cdnfonts.com/css/roboto-slab-2'); + +:root { + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + font-family: 'Roboto Slab', Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + margin: 0 auto; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/webapp/src/index.css b/webapp/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/webapp/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/webapp/src/index.js b/webapp/src/index.js index d563c0fb..d980f8db 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -1,13 +1,32 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; +import './css/index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; + +const darkTheme = createTheme({ + palette: { + mode: 'dark', + }, + typography: { + fontFamily: 'Arial, sans-serif', + h1: { + fontSize: '2.5rem', + fontWeight: 700, + } + } +}); + const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - + + + + ); diff --git a/webapp/src/logo.svg b/webapp/src/logo.svg deleted file mode 100644 index 9dfc1c05..00000000 --- a/webapp/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webapp/src/components/AddUser.test.js b/webapp/src/test/AddUser.test.js similarity index 97% rename from webapp/src/components/AddUser.test.js rename to webapp/src/test/AddUser.test.js index 87334886..e585cf92 100644 --- a/webapp/src/components/AddUser.test.js +++ b/webapp/src/test/AddUser.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import AddUser from './AddUser'; +import AddUser from '../components/AddUser'; const mockAxios = new MockAdapter(axios); diff --git a/webapp/src/App.test.js b/webapp/src/test/App.test.js similarity index 91% rename from webapp/src/App.test.js rename to webapp/src/test/App.test.js index 5e3b7314..7faf84d5 100644 --- a/webapp/src/App.test.js +++ b/webapp/src/test/App.test.js @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react'; -import App from './App'; +import App from '../App'; test('renders learn react link', () => { render(); diff --git a/webapp/src/test/Game.test.js b/webapp/src/test/Game.test.js new file mode 100644 index 00000000..6b7c05dd --- /dev/null +++ b/webapp/src/test/Game.test.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { Game } from '../components/Game'; + +describe('Game component', () => { + test('renders question correctly', () => { + render( {}} />); // Renderizar el componente Game + + // Verificar que las opciones de respuesta se muestran correctamente + const options = screen.getAllByRole('button'); + expect(options).toHaveLength(5); // Verificar que hay cuatro opciones + }); + + test('clicking "Volver al menú" calls goTo function with correct argument', () => { + const goToMock = jest.fn(); // Crear una función simulada + render(); // Renderizar el componente Game con la función simulada + + // Simular clic en el botón "Volver al menú" + fireEvent.click(screen.getByText('Volver al menú')); + + // Verificar que la función goTo se llamó con el argumento correcto (en este caso, 1) + expect(goToMock).toHaveBeenCalledWith(1); + }); +}); diff --git a/webapp/src/components/Login.test.js b/webapp/src/test/Login.test.js similarity index 94% rename from webapp/src/components/Login.test.js rename to webapp/src/test/Login.test.js index af102dcf..d43d4baa 100644 --- a/webapp/src/components/Login.test.js +++ b/webapp/src/test/Login.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { render, fireEvent, screen, waitFor, act } from '@testing-library/react'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import Login from './Login'; +import Login from '../components/Login'; const mockAxios = new MockAdapter(axios); @@ -12,7 +12,8 @@ describe('Login component', () => { }); it('should log in successfully', async () => { - render(); + + render( {}} />); const usernameInput = screen.getByLabelText(/Username/i); const passwordInput = screen.getByLabelText(/Password/i); @@ -34,7 +35,8 @@ describe('Login component', () => { }); it('should handle error when logging in', async () => { - render(); + + render( {}} />); const usernameInput = screen.getByLabelText(/Username/i); const passwordInput = screen.getByLabelText(/Password/i); diff --git a/webapp/src/test/Participation.test.js b/webapp/src/test/Participation.test.js new file mode 100644 index 00000000..97cb6954 --- /dev/null +++ b/webapp/src/test/Participation.test.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { Participation } from '../components/Participation'; + +describe('Participation component', () => { + test('renders correctly', () => { + render( {}} />); + + // Verificar que el texto "Participation" se muestra correctamente + const titleText = screen.getByText(/Participation/i); + expect(titleText).toBeInTheDocument(); + + // Verificar que el botón "Menu" se muestra correctamente + const menuButton = screen.getByRole('button', { name: /Menu/i }); + expect(menuButton).toBeInTheDocument(); + }); + + test('clicking "Menu" button calls goTo function with correct argument', () => { + const goToMock = jest.fn(); + render(); + + // Simular clic en el botón "Menu" + fireEvent.click(screen.getByText('Menu')); + + // Verificar que la función goTo se llamó con el argumento correcto (en este caso, 0) + expect(goToMock).toHaveBeenCalledWith(0); + }); +}); diff --git a/webapp/src/test/Start.test.js b/webapp/src/test/Start.test.js new file mode 100644 index 00000000..3760c01c --- /dev/null +++ b/webapp/src/test/Start.test.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { Start } from '../components/Start'; + +describe('Start component', () => { + test('renders correctly', () => { + render( {}} />); // Renderizar el componente Start + + // Verificar que el texto "Quiz ASW" se muestra correctamente + const titleText = screen.getByText(/Quiz ASW/i); + expect(titleText).toBeInTheDocument(); + + // Verificar que el botón "Jugar" se muestra correctamente + const playButton = screen.getByRole('button', { name: /Jugar/i }); + expect(playButton).toBeInTheDocument(); + }); + + test('clicking "Jugar" button calls goTo function with correct argument', () => { + const goToMock = jest.fn(); // Crear una función simulada + render(); // Renderizar el componente Start con la función simulada + + // Simular clic en el botón "Jugar" + fireEvent.click(screen.getByText('Jugar')); + + // Verificar que la función goTo se llamó con el argumento correcto (en este caso, 2) + expect(goToMock).toHaveBeenCalledWith(2); + }); +}); diff --git a/webapp/src/test/User.test.js b/webapp/src/test/User.test.js new file mode 100644 index 00000000..657f4e55 --- /dev/null +++ b/webapp/src/test/User.test.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import User from '../components/User'; + +describe('User component', () => { + test('renders login form by default', () => { + render( {}} />); + + // Verificar que el texto de bienvenida se muestra correctamente + const welcomeText = screen.getByText(/Welcome to the 2024 edition of the Software Architecture course/i); + expect(welcomeText).toBeInTheDocument(); + + // Verificar que el formulario de inicio de sesión se muestra por defecto + const loginForm = screen.getByRole('textbox', { name: /username/i }); + expect(loginForm).toBeInTheDocument(); + + // Verificar que el enlace para registrar se muestra correctamente + const registerLink = screen.getByRole('button', { name: /register here/i }); + expect(registerLink).toBeInTheDocument(); + }); + + test('toggling between login and registration form works correctly', () => { + render( {}} />); + + // Verificar que el enlace para registrar está presente + const registerLink = screen.getByRole('button', { name: /register here/i }); + expect(registerLink).toBeInTheDocument(); + + // Simular clic en el enlace para registrar + fireEvent.click(registerLink); + + // Verificar que el enlace para iniciar sesión se muestra después de hacer clic en el enlace de registro + const loginLink = screen.getByRole('button', { name: /login here/i }); + expect(loginLink).toBeInTheDocument(); + + // Simular clic en el enlace para iniciar sesión + fireEvent.click(loginLink); + + // Verificar que el formulario de inicio de sesión se muestra nuevamente después de hacer clic en el enlace de inicio de sesión + const loginForm = screen.getByRole('textbox', { name: /username/i }); + expect(loginForm).toBeInTheDocument(); + }); +});