From 9e3adddd9bce969ee68b36494dc629c1aee13147 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 10:10:36 +0200 Subject: [PATCH 01/16] Forget password done in the backend --- gatewayservice/gateway-service.js | 91 ++++++++++++++++++++++++++++++- gatewayservice/package-lock.json | 9 +++ gatewayservice/package.json | 3 +- users/userservice/user-service.js | 79 +++++++++++++++++++++++++-- 4 files changed, 172 insertions(+), 10 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 49b59c0f..5e84ef08 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -8,13 +8,26 @@ const fs = require("fs") const YAML = require('yaml') const jwt = require('jsonwebtoken'); const app = express(); -const port = 8000; +const port = 8010; +//Setting up the email +const nodemailer = require('nodemailer'); + +const transporter = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: "wiqen1b@gmail.com", + pass: "akskfqgakjvcswyg ", + }, +}); const authServiceUrl = process.env.AUTH_SERVICE_URL || 'http://localhost:8002'; -const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001'; +const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8011'; const questionServiceUrl = process.env.QUESTION_SERVICE_URL || 'http://localhost:8003'; const recordServiceUrl = process.env.RECORD_SERVICE_URL || 'http://localhost:8004'; + +var forgetPasswords = new Map() + app.use(cors()); app.use(express.json()); @@ -41,7 +54,50 @@ app.post('/adduser', async (req, res) => { try { // Forward the add user request to the user service const userResponse = await axios.post(userServiceUrl+'/adduser', req.body); - console.log(userResponse) + res.json(userResponse.data); + } catch (error) { + manageError(res, error); + + } +}); + +app.post('/forgetPassword', async (req, res) => { + try { + // Forward the forget password request to the user service + const userResponse = await axios.post(userServiceUrl+'/forgetPassword', req.body); + + let sixNumbers = getRandomSixDigitNumber(); + while(forgetPasswords.has(sixNumbers)) + sixNumbers = getRandomSixDigitNumber(); + + forgetPasswords.set(sixNumbers, userResponse.data.token) + await sendEmail(res, userResponse.data.email, userResponse.data.username, sixNumbers) + } catch (error) { + manageError(res, error); + + } +}); + +app.get('/tokenFromCode/:code', async (req, res) => { + try { + var code = parseInt(req.params.code); + if(forgetPasswords.has(code)){ + var token = forgetPasswords.get(code) + forgetPasswords.delete(code) + res.json({token: token}); + } + else + res.status(400).json({ error : "Invalid code" }); + } catch (error) { + manageError(res, error); + + } +}); + +app.post('/changePassword', verifyToken, async (req, res) => { + try { + // Forward the forget password request to the user service + const userResponse = await axios.post(userServiceUrl+'/changePassword', req.body); res.json(userResponse.data); } catch (error) { manageError(res, error); @@ -229,10 +285,39 @@ function validateUser(user){ } function manageError(res, error){ + console.log(error) if(error.response) //Some microservice responded with an error res.status(error.response.status).json({ error: error.response.data.error }); else //Some other error res.status(500).json({error : "Internal server error"}) } +function getRandomSixDigitNumber() { + const now = Date.now(); // Gets the current timestamp + const lastSixDigits = now.toString().slice(-6); // Gets the last 6 digits as a string + return parseInt(lastSixDigits, 10); // Converts it back to an integer +} + +async function sendEmail(res, email, username, numbers) { + console.log(numbers) + // Configuración del correo + const mailOptions = { + from: process.env.EMAIL_USER, // Remitente + to: email, // Destinatario + subject: 'Hello ' + username + ' this is the wiqen1b team', // Asunto + text: 'We see that you have requested a password change.\n' + + 'Please introduce the code: ' + numbers + '. You have around 10 minutes to change your password \n' + + 'In case you have not requested a password change forget this email existance', + }; + + try { + // Envía el correo + await transporter.sendMail(mailOptions); + res.send('Email sent successfully'); + } catch (error) { + console.error('Error sending email:', error); + res.status(500).send('Error sending email'); + } +} + module.exports = server diff --git a/gatewayservice/package-lock.json b/gatewayservice/package-lock.json index 48729129..8748d7db 100644 --- a/gatewayservice/package-lock.json +++ b/gatewayservice/package-lock.json @@ -15,6 +15,7 @@ "express-openapi": "^12.1.3", "express-prom-bundle": "^7.0.0", "jsonwebtoken": "^9.0.2", + "nodemailer": "^6.9.13", "swagger-ui-express": "^5.0.0", "yaml": "^2.4.1" }, @@ -3790,6 +3791,14 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/gatewayservice/package.json b/gatewayservice/package.json index 0757ca8f..756bc677 100644 --- a/gatewayservice/package.json +++ b/gatewayservice/package.json @@ -22,8 +22,9 @@ "cors": "^2.8.5", "express": "^4.18.2", "express-openapi": "^12.1.3", - "jsonwebtoken": "^9.0.2", "express-prom-bundle": "^7.0.0", + "jsonwebtoken": "^9.0.2", + "nodemailer": "^6.9.13", "swagger-ui-express": "^5.0.0", "yaml": "^2.4.1" }, diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index a6ea8a09..7bc09273 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -7,7 +7,7 @@ const jwt = require('jsonwebtoken'); const User = require('./user-model') const app = express(); -const port = 8001; +const port = 8011; // Middleware to parse JSON in request body app.use(bodyParser.json()); @@ -31,10 +31,11 @@ function validateRequiredFields(req, requiredFields) { } } - let email = req.body.email.toString(); - let username = req.body.username.toString(); - let password = req.body.password.toString(); - let repeatPassword = req.body.repeatPassword.toString(); + //If there are not here is because they dont need to be, it has being check before which need to be here + let email = req.body.email ? req.body.email.toString() : "example@example.com"; + let username = req.body.username ? req.body.username.toString() : "example"; + let password = req.body.password ? req.body.password.toString() : "123456789"; + let repeatPassword = req.body.repeatPassword ? req.body.repeatPassword.toString() : "123456789"; if(!validateEmail(email)){ //User put a wrong format email @@ -106,7 +107,73 @@ app.post('/adduser', async (req, res) => { res.json({ token: token, username: savedUser.username, email: savedUser.email}); } catch (error) { res.status(400).json({ error: error.message }); - }}); + }} +); + +app.post('/forgetPassword', async (req, res) => { + try { + // Check if required fields are present in the request body + try{ + validateRequiredFields(req, ['email', 'username']); + } + catch(error){ + res.status(400).json({ error : error.message }); + return + } + //Check there is a user with that name + const userUsername = await User.findOne({username: req.body.username.toString()}); + + if(!userUsername || userUsername.email !== req.body.email) + return res.status(400).json({error : "No user found, review credentials"}) + + + const token = jwt.sign({ userId: userUsername._id }, (process.env.JWT_KEY??'my-key'), { expiresIn: '15m' }); + + res.json({ token: token, username: userUsername.username, email: userUsername.email}); + } catch (error) { + res.status(400).json({ error: error.message }); + }} +); + +app.post('/changePassword', async (req, res) => { + try { + // Check if required fields are present in the request body + try{ + validateRequiredFields(req, ['email', 'username', 'password', 'repeatPassword']); + } + catch(error){ + res.status(400).json({ error : error.message }); + console.log(res) + return + } + + + //Check there is a user with that name + const userUsername = await User.findOne({username: req.body.username.toString()}); + + if(!userUsername || userUsername.email !== req.body.email) + return res.status(400).json({error : "No user found, review credentials"}) + + // Encrypt the password before saving it + const hashedPassword = await bcrypt.hash(req.body.password, 10); + + const result = await User.updateOne( + { _id: userUsername._id }, + { $set: { password: hashedPassword } } + ); + + if (result.nModified === 0) { + res.status(404).send('User not found or no change'); + } else { + const token = jwt.sign({ userId: userUsername._id }, (process.env.JWT_KEY??'my-key'), { expiresIn: '1h' }); + res.json({ token: token, username: userUsername.username, email: userUsername.email}); + } + + + } catch (error) { + res.status(400).json({ error: error.message }); + }} +); const server = app.listen(port, () => { console.log(`User Service listening at http://localhost:${port}`); From f7d2c5f0c671b8e8de4cb4e5722b3b4923b5bcfc Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 10:26:28 +0200 Subject: [PATCH 02/16] Updated the backend doc --- gatewayservice/gateway-service.js | 1 - gatewayservice/openapi.yaml | 147 ++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 5e84ef08..cf72ba03 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -299,7 +299,6 @@ function getRandomSixDigitNumber() { } async function sendEmail(res, email, username, numbers) { - console.log(numbers) // Configuración del correo const mailOptions = { from: process.env.EMAIL_USER, // Remitente diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml index 6755daa9..65cfed8f 100644 --- a/gatewayservice/openapi.yaml +++ b/gatewayservice/openapi.yaml @@ -84,6 +84,153 @@ paths: type: string description: Error information. example: Internal Server Error + /forgetPassword: + post: + summary: Sends a forget password alert to the server + operationId: forgetPassword + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: + type: string + description: User email + example: user@example.com + username: + type: string + description: User name + example: student + responses: + '200': + description: Email sent successfully + content: + application/json: + schema: + type: string + example: "Email sent successfully" + '400': + description: Failed to find user + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: No user found, review credentials + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error + /tokenFromCode{code}: + get: + summary: Get a token from a 6 digit code + parameters: + - name: code + in: path + required: true + schema: + type: string + responses: + '200': + description: Code found returns token + content: + application/json: + schema: + type: object + properties: + token: + type: string + '400': + description: Invalid code + content: + application/json: + schema: + type: string + example: "Invalid code" + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error + /changePassword: + post: + summary: Changes the password of the authorized user + parameters: + - name: token + in: header + schema: + type: string + operationId: changePassword + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: + type: string + description: User email + example: user@example.com + username: + type: string + description: User name + example: student + password: + type: string + description: User password + example: password123 + repeatPassword: + type: string + description: Userpassword + example: password123 + responses: + '200': + description: Email sent successfully + content: + application/json: + schema: + $ref: '#/components/schemas/LoginResponse' + '400': + description: Failed to find user + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: No user found, review credentials + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error /login: post: summary: Log in to the system. From 44a5448f7787a5e915eb62fb25d7e93f15c169b6 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 11:42:05 +0200 Subject: [PATCH 03/16] Added tests to the gateway service --- gatewayservice/gateway-service.js | 9 +++-- gatewayservice/gateway-service.test.js | 51 +++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index cf72ba03..232c8666 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -16,7 +16,7 @@ const transporter = nodemailer.createTransport({ service: 'Gmail', auth: { user: "wiqen1b@gmail.com", - pass: "akskfqgakjvcswyg ", + pass: "akskfqgakjvcswyg", }, }); @@ -80,6 +80,7 @@ app.post('/forgetPassword', async (req, res) => { app.get('/tokenFromCode/:code', async (req, res) => { try { + console.log(forgetPasswords) var code = parseInt(req.params.code); if(forgetPasswords.has(code)){ var token = forgetPasswords.get(code) @@ -301,9 +302,9 @@ function getRandomSixDigitNumber() { async function sendEmail(res, email, username, numbers) { // Configuración del correo const mailOptions = { - from: process.env.EMAIL_USER, // Remitente - to: email, // Destinatario - subject: 'Hello ' + username + ' this is the wiqen1b team', // Asunto + from: process.env.EMAIL_USER, + to: email, + subject: 'Hello ' + username + ' this is the wiqen1b team', text: 'We see that you have requested a password change.\n' + 'Please introduce the code: ' + numbers + '. You have around 10 minutes to change your password \n' + 'In case you have not requested a password change forget this email existance', diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index a51f5f18..0517d123 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -2,6 +2,7 @@ const request = require('supertest'); const axios = require('axios'); const jwt = require('jsonwebtoken'); const app = require('./gateway-service'); +const nodemailer = require('nodemailer'); afterAll(async () => { app.close(); @@ -12,8 +13,6 @@ jest.mock('jsonwebtoken'); jest.mock('axios'); - - describe('Gateway Service with mocked micro services', () => { // Mock responses from external services @@ -24,6 +23,10 @@ describe('Gateway Service with mocked micro services', () => { return Promise.resolve({ data: { username: 'newuser' } }); } else if(url.endsWith('/record')){ return Promise.resolve({data : {user:'testuser'}}) + } else if(url.endsWith('/forgetPassword')){ + return Promise.resolve({data : { token: 'mockedToken', username : 'testuser', email:"example@example.com"}}) + } else if(url.endsWith('/changePassword')){ + return Promise.resolve({data : {token: 'mockedToken', username : 'testuser', email:"example@example.com"}}) } }); @@ -60,6 +63,14 @@ describe('Gateway Service with mocked micro services', () => { callback(null, "decoded"); }); + + //Mock nodemailer + jest.mock('nodemailer', () => ({ + createTransport: jest.fn().mockReturnValue({ + sendMail: jest.fn(), + }), + })); + // Test /login endpoint it('should forward login request to auth service', async () => { const response = await request(app) @@ -146,6 +157,42 @@ describe('Gateway Service with mocked micro services', () => { }); + //Test /forgetPassword + it('should forward the request and send an email', async () => { + const response = await request(app) + .post('/forgetPassword') + .send({ email: 'example@example.com', username: 'testuser'}); + expect(response.statusCode).toBe(200); + expect(response.text).toBe('Email sent successfully'); + }) + + //Test tokenFromCode/:code + it('should find a token', async () => { + //First generate the code:token + + const fixedTimestamp = 1683078000000; + jest.spyOn(Date, 'now').mockReturnValue(fixedTimestamp); + + await request(app) + .post('/forgetPassword') + .send({ email: 'example@example.com', username: 'testuser'}); + const response = await request(app).get('/tokenFromCode/000000'); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('token', "mockedToken"); + }) + + //Test /changePassword + it('should forward the request', async () => { + const response = await request(app) + .post('/changePassword') + .send({ username: 'testuser', password: 'newpassword' }) + .set('token', 'valorDelToken'); + + expect(response.statusCode).toBe(200); + expect(response.body.username).toBe('testuser'); + }) + }); function checkRecord(response){ From 68379fa6542221c0ad961a4e03860813e28d16fc Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 12:24:29 +0200 Subject: [PATCH 04/16] Added tests to user endpoints --- users/userservice/user-service.js | 2 - users/userservice/user-service.test.js | 65 +++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index 7bc09273..b3aaa957 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -75,7 +75,6 @@ app.post('/adduser', async (req, res) => { } catch(error){ res.status(400).json({ error : error.message }); - console.log(res) return } @@ -143,7 +142,6 @@ app.post('/changePassword', async (req, res) => { } catch(error){ res.status(400).json({ error : error.message }); - console.log(res) return } diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index ab9fa21e..19cff1da 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -1,5 +1,13 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); +const jwt = require('jsonwebtoken'); + +// Mock the `jsonwebtoken` module +jest.mock('jsonwebtoken', () => ({ + sign: jest.fn(), +})); +const expectedToken = "mockedToken" +jwt.sign.mockReturnValue(expectedToken); let mongoServer; let app; @@ -32,7 +40,7 @@ afterEach(async () => { }; }) -describe('User Service', () => { +describe('User Service /adduser', () => { it('should add a new user on POST /adduser', async () => { const response = await request(app).post('/adduser').send(newUser); expect(response.status).toBe(200); @@ -46,11 +54,11 @@ describe('User Service', () => { }); it('Should not register user /adduser', async () => { - newUser.email = 'example2@example.com'; + newUser.email = 'example2@example.com'; - const response = await request(app).post('/adduser').send(newUser); - expect(response.status).toBe(400); - expect(response.body).toHaveProperty('error', 'Username already in use'); + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Username already in use'); }); it('Should not register user /adduser', async () => { @@ -59,7 +67,7 @@ describe('User Service', () => { const response = await request(app).post('/adduser').send(newUser); expect(response.status).toBe(400); expect(response.body).toHaveProperty('error', 'Email already in use'); - }); + }); }); @@ -110,4 +118,47 @@ describe('User service validations', () => { function setPassword(newPassword){ newUser.password = newPassword; newUser.repeatPassword = newPassword; -} \ No newline at end of file +} + + +describe('User service /forgetPassword', () => { + it('should return a token when given good credentials', async () => { + const response = await request(app).post('/forgetPassword').send({email:newUser.email, username:newUser.username}); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('token', expectedToken); + }) + + it('should show missing field email', async () => { + const response = await request(app).post('/forgetPassword').send({username:newUser.username}); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Missing required field: email'); + }) + + it('should show no user found', async () => { + const response = await request(app).post('/forgetPassword').send({email: "add" + newUser.email, username:newUser.username}); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'No user found, review credentials'); + }) +}) + +describe('User service /changePassword', () => { + it('should return a token when given good credentials', async () => { + setPassword('123456789') + const response = await request(app).post('/changePassword').send(newUser); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('token', expectedToken); + }) + + it('should show missing field email', async () => { + const response = await request(app).post('/changePassword').send({username:newUser.username}); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Missing required field: email'); + }) + + it('should show no user found', async () => { + newUser.email = "add" + newUser.email; + const response = await request(app).post('/changePassword').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'No user found, review credentials'); + }) +}) \ No newline at end of file From 1fc147aa885a4978bafc035b361d6dd6a65d7ce7 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 27 Apr 2024 12:55:37 +0200 Subject: [PATCH 05/16] Added forgot password front-end and calls to endpoint --- webapp/src/App.js | 1 + .../ForgetPassword/ForgetPassword.js | 164 ++++++++++++++++++ .../ForgetPassword/ForgetPasswordFunctions.js | 54 ++++++ .../components/loginAndRegistration/Login.js | 11 +- webapp/src/custom.css | 18 ++ webapp/src/translations/en/global.json | 8 + webapp/src/translations/es/global.json | 8 + 7 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 webapp/src/components/ForgetPassword/ForgetPassword.js create mode 100644 webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 5f2c4fd7..81b04b66 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -40,6 +40,7 @@ function App() { : } /> : }/> : } /> + } /> } /> diff --git a/webapp/src/components/ForgetPassword/ForgetPassword.js b/webapp/src/components/ForgetPassword/ForgetPassword.js new file mode 100644 index 00000000..f7db429d --- /dev/null +++ b/webapp/src/components/ForgetPassword/ForgetPassword.js @@ -0,0 +1,164 @@ +import "../../custom.css"; +import {useTranslation} from "react-i18next"; +import { useState } from 'react'; +import ForgetPasswordFunctions from "./ForgetPasswordFunctions"; + +const ForgetPasswordFunctions = new ForgetPasswordFunctions(); +export default function ForgotPassword() { + const[t] = useTranslation("global"); + const[emailStatus, setEmailStatus] = useState("ASK_EMAIL"); + const [email, setEmail] = useState(''); + const [username, setUsername] = useState(''); + const [code, setCode]=useState(0); + const[newPassword, setNewPassword]=useState(''); + const[repeatPassword, setRepeatPassword]=useState(''); + let componentToRender ; + const handleSubmitStateToPending =()=>{ + setEmailStatus("PENDING"); + } + const sendEmail =()=>{ + const received = ForgetPasswordFunctions.sendEmail(email, username); + if(received){ + setEmailStatus("RECEIVED"); + }else{ + console.log("not received") + } + } + + function obtenerCodigo() { + // Obtener todos los inputs con la clase "input" + const inputs = document.querySelectorAll('.password .input'); + let codigo = ''; + // Recorrer cada input y concatenar su valor al código si no es "X" + inputs.forEach(input => { + if (input.value !== "X") { + codigo += input.value; + } + }); + // Convertir el código a un número entero + const codigoNumerico = parseInt(codigo, 10); + console.log("codigo obtenido: "+codigoNumerico) + setCode(codigoNumerico); + ForgetPasswordFunctions.tokenFromCode(code); + setEmailStatus("VERIFIED"); + } + + function restorePassword(){ + let oneHourAfter = new Date().getTime() + (1 * 60 * 60 * 1000) + Cookies.set('user', JSON.stringify({username : response.data.username, token : response.data.token}) + , {expires:oneHourAfter}); + navigate('/menu'); + window.location.reload(); + } + + + if(emailStatus==="ASK_EMAIL"){ + componentToRender= + } + else if(emailStatus==="PENDING"){ + componentToRender= + sendEmail(); + }else if(emailStatus==="RECEIVED"){ + componentToRender= + }else{ + componentToRender= + } + + + return ( +
+ {componentToRender} +
+ ); + } + + export default function AskEmailUsername({t, handleSubmit}){ +
+
+

{t("forgotPassword.enter_email")}

+
+

{t("addUser.email_placeholder")}:

+ setEmail(e.target.value)} + /> +
+
+

{t("addUser.username_placeholder")}:

+ setUsername(e.target.value)} + /> +
+ +
+
+ } + + export default function PendingComponent({t}){ + return ( +
+

{t("forgotPassword.sending_title")}

+

{t("forgotPasswor.sending_paragraph")}

+
+ ); + } + + + export default function EnterCode({t,obtainCode}){ + return ( +
+

{t("forgotPassword.enter_code")}

+ + + + + + + {obtainCode()} +
+ + ); + } + + export default function RestorePassword({t}){ + return( +
+
+

{t("addUser.password_placeholder")}:

+ setNewPassword(e.target.value)} + /> +
+
+

{t("addUser.repeat_password_placeholder")}:

+ setRepeatPassword(e.target.value)} + /> +
+
+ ); + } + + + \ No newline at end of file diff --git a/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js b/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js new file mode 100644 index 00000000..6b4513ae --- /dev/null +++ b/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js @@ -0,0 +1,54 @@ +import axios from 'axios'; + +class ForgetPasswordFunctions{ + + constructor(){ + this.apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'); + this.token; + } + + async sendEmail(email, username){ + try { + const response = await axios.post(this.apiUrl + '/forgetPassword', { email, username}); + return !!response.text; + } catch (error) { + console.log(error) + return false; + } + } + + async tokenFromCode(code){ + try{ + const response = await axios.get(this.apiUrl+'/tokenFromCode/'+code) + this.token=response.data.token; + return response.data.token; + }catch (error) { + console.log(error) + return "Invalid Token"; + } + } + async changePassword(email, username, password, repeatPassword){ + try{ + const response = await axios.post(this.apiUrl+'/changePassword', + { + email, + username, + password, + repeatPassword + }, + { + headers: { + 'token': this.token + } + } + ) + return response; + }catch (error) { + console.log(error) + return "Invalid Token"; + } + } + +} + +export default ForgetPasswordFunctions; diff --git a/webapp/src/components/loginAndRegistration/Login.js b/webapp/src/components/loginAndRegistration/Login.js index bc7fd00c..c2a4b301 100644 --- a/webapp/src/components/loginAndRegistration/Login.js +++ b/webapp/src/components/loginAndRegistration/Login.js @@ -59,7 +59,7 @@ const Login = () => { - + @@ -79,6 +79,15 @@ function LinkRegister() { ); } +function LinkForgetPassword(){ + const { t } = useTranslation("global"); + return ( + + {t("login.forget_pass")} + + ); +} + export default Login; diff --git a/webapp/src/custom.css b/webapp/src/custom.css index f6660f91..69d887e9 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1678,3 +1678,21 @@ input[type="text"] { } +/*Forget-Password*/ +.password { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; +} +.password input { + width: 30px; + height: 40px; + text-align: center; + background-color: transparent; + border: none; + border-bottom: solid 2px rgb(20, 181, 230); + font-size: 20px; + color: white; + outline: none; +} \ No newline at end of file diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index ac6e6bf3..c565f5eb 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -44,6 +44,7 @@ "remember_me": "Remember me", "forgot_password": "Forgot password?", "login_button": "Login", + "forget_pass":"Have you forgotten your password? Let's restore it.", "register_link": "Don't you have an account? Register here." }, "addUser": { @@ -115,6 +116,13 @@ "error":{ "error":"Error", "sorry":"We're sorry, this page does not exist. Don't be angry, I'm just a little cat." + }, + "forgotPassword":{ + "sending_title":"Check your email", + "sending_paragraph":"In order to restore your password we are sending an email with a code of 6 digits", + "enter_code":"Enter code of 6 digits that was sent via email", + "enter_email":"Enter email and password" + } } diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 8906341c..fe7e4a41 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -47,6 +47,7 @@ "remember_me": "Recordarme", "forgot_password": "¿Olvidaste tu contraseña?", "login_button": "Iniciar sesión", + "forget_pass":"¿Has olvidado tu contraseña? Restaurémosla.", "register_link": "¿No tienes una cuenta? Regístrate aquí." }, @@ -117,6 +118,13 @@ "num_games":"Juegos Competitivos", "search":"Buscar", "enter_username":"Inserta usuario..." + } , + "forgotPassword":{ + "sending_title":"Revisa tu email", + "sending_paragraph":"Para poder reestablecer tu email hemos enviado al email con el que te registraste un código de 6 cifras", + "enter_code":"Introduce el código de 6 cifras enviado a tu email", + "enter_email":"Introduce Email y password" + } } From c54317487aa2c14dcf12b9f1e5814dfc58c8b05a Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 15:57:01 +0200 Subject: [PATCH 06/16] Remade the front end component --- webapp/src/App.js | 1 + .../ForgetPassword/ForgetPassword.js | 469 ++++++++++++------ .../ForgetPassword/ForgetPasswordFunctions.js | 11 +- .../loginAndRegistration/AddUser.js | 40 +- webapp/src/custom.css | 11 +- webapp/src/translations/en/global.json | 6 +- webapp/src/utils/manageError.js | 17 - webapp/src/utils/utils.js | 56 +++ 8 files changed, 408 insertions(+), 203 deletions(-) delete mode 100644 webapp/src/utils/manageError.js create mode 100644 webapp/src/utils/utils.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 81b04b66..49989923 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -14,6 +14,7 @@ import HistoricalView from './components/HistoricalData/HistoricalView'; import Cookies from 'js-cookie'; import GameConfigurator from './components/GameConfigurator/GameConfigurator'; import RankingView from './components/ranking/RankingView'; +import ForgetPassword from './components/ForgetPassword/ForgetPassword' function App() { diff --git a/webapp/src/components/ForgetPassword/ForgetPassword.js b/webapp/src/components/ForgetPassword/ForgetPassword.js index f7db429d..b4fd3b7e 100644 --- a/webapp/src/components/ForgetPassword/ForgetPassword.js +++ b/webapp/src/components/ForgetPassword/ForgetPassword.js @@ -1,164 +1,353 @@ import "../../custom.css"; -import {useTranslation} from "react-i18next"; -import { useState } from 'react'; +import { useTranslation } from "react-i18next"; +import { useState } from "react"; import ForgetPasswordFunctions from "./ForgetPasswordFunctions"; +import Cookies from "js-cookie"; +import zxcvbn from "zxcvbn"; +import { useNavigate } from "react-router-dom"; -const ForgetPasswordFunctions = new ForgetPasswordFunctions(); +import { validateEmail, validatePasswords, validateUsername } from "../../utils/utils"; + +const forgetPasswordFunctions = new ForgetPasswordFunctions(); export default function ForgotPassword() { - const[t] = useTranslation("global"); - const[emailStatus, setEmailStatus] = useState("ASK_EMAIL"); - const [email, setEmail] = useState(''); - const [username, setUsername] = useState(''); - const [code, setCode]=useState(0); - const[newPassword, setNewPassword]=useState(''); - const[repeatPassword, setRepeatPassword]=useState(''); - let componentToRender ; - const handleSubmitStateToPending =()=>{ - setEmailStatus("PENDING"); - } - const sendEmail =()=>{ - const received = ForgetPasswordFunctions.sendEmail(email, username); - if(received){ + const navigate = useNavigate(); + const [t] = useTranslation("global"); + + // Component state variables + const [emailStatus, setEmailStatus] = useState("RECEIVED"); + const [email, setEmail] = useState("dasf"); + const [username, setUsername] = useState("fdafds"); + const [newPassword, setNewPassword] = useState(""); + const [repeatPassword, setRepeatPassword] = useState(""); + const [passwordStrength, setPasswordStrength] = useState({}); + const [passwordStrengthText, setPasswordStrengthText] = useState(""); + const [submitErrors, setSubmitErrors] = useState([]); + const [errors, setErrors] = useState([]); + + // Function to handle state transition and send email + const handleSubmitStateToPending = async (event) => { + event.preventDefault(); + + const newSubmitErrors = []; + + validateEmail(newSubmitErrors, email); + validateUsername(newSubmitErrors, username); + + setSubmitErrors(newSubmitErrors); + + if (newSubmitErrors.length === 0) { + + try { + setEmailStatus("PENDING"); + await sendEmail(); + setSubmitErrors([]) + } catch (error) { + setErrors([error.message]) + } + + } + }; + + // Function to send the email + const sendEmail = async () => { + try{ + const received = await forgetPasswordFunctions.sendEmail(email, username); + if (received) { setEmailStatus("RECEIVED"); - }else{ - console.log("not received") + } else { + console.log("Email not received"); + } } - } + catch(error){ + setErrors([error.message]) + } + }; - function obtenerCodigo() { - // Obtener todos los inputs con la clase "input" - const inputs = document.querySelectorAll('.password .input'); - let codigo = ''; - // Recorrer cada input y concatenar su valor al código si no es "X" - inputs.forEach(input => { - if (input.value !== "X") { - codigo += input.value; - } + // Function to obtain the code from input fields + async function obtenerCodigo(event) { + event.preventDefault(); + let inputs = document.querySelectorAll(".codeGrid input"); + let codigo = ""; + let inputsNotEmpty = false; + inputs.forEach((input) => { + codigo += input.value; + inputsNotEmpty = inputsNotEmpty || input.value === "" }); - // Convertir el código a un número entero - const codigoNumerico = parseInt(codigo, 10); - console.log("codigo obtenido: "+codigoNumerico) - setCode(codigoNumerico); - ForgetPasswordFunctions.tokenFromCode(code); - setEmailStatus("VERIFIED"); - } - function restorePassword(){ - let oneHourAfter = new Date().getTime() + (1 * 60 * 60 * 1000) - Cookies.set('user', JSON.stringify({username : response.data.username, token : response.data.token}) - , {expires:oneHourAfter}); - navigate('/menu'); - window.location.reload(); + if(!inputsNotEmpty){ + try { + const codigoNumerico = parseInt(codigo, 10); + await forgetPasswordFunctions.tokenFromCode(codigoNumerico); + setEmailStatus("RESTORE_PASSWORD"); + } catch (error) { + setErrors([error.message]) + } + } + } + // Function to restore the password + async function restorePassword(event) { + event.preventDefault(); - if(emailStatus==="ASK_EMAIL"){ - componentToRender= - } - else if(emailStatus==="PENDING"){ - componentToRender= - sendEmail(); - }else if(emailStatus==="RECEIVED"){ - componentToRender= - }else{ - componentToRender= + const newSubmitErrors = []; + + validatePasswords(newSubmitErrors, newPassword, repeatPassword); + + setSubmitErrors(newSubmitErrors); + + if (newSubmitErrors.length === 0) { + + const response = await forgetPasswordFunctions.changePassword( + email, + username, + newPassword, + repeatPassword + ); + + const oneHourAfter = new Date().getTime() + 1 * 60 * 60 * 1000; + Cookies.set( + "user", + JSON.stringify({ + username: response.data.username, + token: response.data.token, + }), + { expires: oneHourAfter } + ); + + navigate("/menu"); + window.location.reload(); + } } - + // Function to show errors + const showErrors = () => { + if (submitErrors.length > 0) { + return submitErrors.map((error, index) => ( +

+ {t(error)} +

+ )); + } + else{ + if(errors.length > 0){ + return errors.map((error, index) => ( +

+ {t(error)} +

+ )); + } else + return null; + } + }; + + // Function to handle password change and determine strength + const handlePasswordChange = (e) => { + const newPassword = e.target.value; + setNewPassword(newPassword); + + const newStrength = zxcvbn(newPassword); + + switch (newStrength.score) { + case 0: + case 1: + setPasswordStrengthText("addUser.very_weak_password"); + break; + case 2: + setPasswordStrengthText("addUser.weak_password"); + break; + case 3: + setPasswordStrengthText("addUser.good_password"); + break; + case 4: + setPasswordStrengthText("addUser.strong_password"); + break; + default: + setPasswordStrengthText("addUser.very_weak_password"); + break; + } + setPasswordStrength(newStrength); + }; + + // Main component return block with conditional rendering return (
- {componentToRender} +
+
+
+ {(() => { + switch (emailStatus) { + case "ASK_EMAIL": + return ( + + ); + case "PENDING": + return (); + case "RECEIVED": + return (); + case "RESTORE_PASSWORD": + return ( + + ); + default: + return

There has been an error

; + } + })()} +
+
+
); - } - - export default function AskEmailUsername({t, handleSubmit}){ -
-
-

{t("forgotPassword.enter_email")}

-
-

{t("addUser.email_placeholder")}:

- setEmail(e.target.value)} - /> -
-
-

{t("addUser.username_placeholder")}:

- setUsername(e.target.value)} - /> -
- -
-
- } +} - export default function PendingComponent({t}){ - return ( -
-

{t("forgotPassword.sending_title")}

-

{t("forgotPasswor.sending_paragraph")}

-
- ); - } +// Sub-components used in the main component +function AskEmailUsername({ email, setEmail, username, setUsername, t, handleSubmit, showErrors }) { + return ( +
+

{t("forgotPassword.enter_email")}

+ {showErrors()} +
+

{t("addUser.email_placeholder")}:

+ setEmail(e.target.value)} + /> +
+
+

{t("addUser.username_placeholder")}:

+ setUsername(e.target.value)} + /> +
+ +
+
+ ); +} +function PendingComponent({ t }) { + return ( +
+

{t("forgotPassword.sending_title")}

+

{t("forgotPassword.sending_paragraph")}

+
+ ); +} - export default function EnterCode({t,obtainCode}){ - return ( -
-

{t("forgotPassword.enter_code")}

- - - - - - - {obtainCode()} +function EnterCode({ t, obtainCode, showErrors }) { + return ( +
+
+

{t("forgotPassword.enter_code")}

+ {showErrors()} +
+ + + + + +
- - ); - } + +
+
+
+ ); +} - export default function RestorePassword({t}){ - return( -
-
-

{t("addUser.password_placeholder")}:

- setNewPassword(e.target.value)} - /> -
-
-

{t("addUser.repeat_password_placeholder")}:

- setRepeatPassword(e.target.value)} - /> -
+function RestorePassword({ + email, + username, + passwordStrength, + passwordStrengthText, + newPassword, + handlePasswordChange, + repeatPassword, + setRepeatPassword, + handleSubmit, + t, + showErrors +}) { + return ( +
+

{t("forgotPassword.enter_password")}

+ {showErrors()} +
+

{t("addUser.email_placeholder")}:

+
- ); - } - - - \ No newline at end of file +
+

{t("addUser.username_placeholder")}:

+ +
+
+

{t("addUser.password_placeholder")}:

+ +
+
+ {t(passwordStrengthText)} + +
+
+

{t("addUser.repeat_password_placeholder")}:

+ setRepeatPassword(e.target.value)} + /> +
+
+ ); +} diff --git a/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js b/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js index 6b4513ae..782fa4dc 100644 --- a/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js +++ b/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js @@ -4,7 +4,7 @@ class ForgetPasswordFunctions{ constructor(){ this.apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'); - this.token; + this.token = ""; } async sendEmail(email, username){ @@ -12,8 +12,7 @@ class ForgetPasswordFunctions{ const response = await axios.post(this.apiUrl + '/forgetPassword', { email, username}); return !!response.text; } catch (error) { - console.log(error) - return false; + throw new Error(error.message) } } @@ -23,8 +22,7 @@ class ForgetPasswordFunctions{ this.token=response.data.token; return response.data.token; }catch (error) { - console.log(error) - return "Invalid Token"; + throw new Error(error.message) } } async changePassword(email, username, password, repeatPassword){ @@ -44,8 +42,7 @@ class ForgetPasswordFunctions{ ) return response; }catch (error) { - console.log(error) - return "Invalid Token"; + throw new Error(error.message) } } diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 66683cc6..cabc827a 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom'; import zxcvbn from "zxcvbn"; import Cookies from 'js-cookie'; -import { manageError } from "../../utils/manageError"; +import { manageError, validateEmail, validateUsername, validatePasswords } from "../../utils/utils"; const AddUser = () => { const navigate = useNavigate(); @@ -23,46 +23,16 @@ const AddUser = () => { const [passwordStrengthText, setPasswordStrengthText] = useState(''); const [submitErrors, setSubmitErrors] = useState([]); - const validateEmail = (email) => { - return String(email) - .toLowerCase() - .match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); - }; - const handleSubmit = async (event) => { event.preventDefault(); const newSubmitErrors = []; //Validations - - if(!validateEmail(email)){ - //User put a wrong format email - newSubmitErrors.push("addUser.error_wrong_email_format") - } - - if(password !== repeatPassword){ - //User put the same password - newSubmitErrors.push("addUser.error_passwords_no_match"); - } - if(/\s/.test(password)){ - //User put spaces in password - newSubmitErrors.push("addUser.error_password_spaces"); - } - if(password.length < 8){ - //Password too short - newSubmitErrors.push("addUser.error_password_minimum_length"); - } - - if(password.length > 64){ - //Password too long - newSubmitErrors.push("addUser.error_password_maximum_length"); - } - - if(/\s/.test(username)){ - //Spaces in username - newSubmitErrors.push("addUser.error_username_spaces"); - } + validateEmail(newSubmitErrors, email); + validateUsername(newSubmitErrors, username); + validatePasswords(newSubmitErrors, password, repeatPassword); + setSubmitErrors(newSubmitErrors); diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 69d887e9..a1944f52 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1679,13 +1679,13 @@ input[type="text"] { /*Forget-Password*/ -.password { +.code { display: flex; align-items: center; justify-content: center; gap: 10px; } -.password input { +.code input { width: 30px; height: 40px; text-align: center; @@ -1695,4 +1695,11 @@ input[type="text"] { font-size: 20px; color: white; outline: none; +} + +.codeGrid { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; } \ No newline at end of file diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index c565f5eb..467f87e7 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -121,8 +121,10 @@ "sending_title":"Check your email", "sending_paragraph":"In order to restore your password we are sending an email with a code of 6 digits", "enter_code":"Enter code of 6 digits that was sent via email", - "enter_email":"Enter email and password" - + "enter_email":"Enter email and username", + "enter_password":"Enter new password", + "enter_email_button": "Send email", + "send_code": "Send code" } } diff --git a/webapp/src/utils/manageError.js b/webapp/src/utils/manageError.js deleted file mode 100644 index ea56f89c..00000000 --- a/webapp/src/utils/manageError.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Receives an error found in a catch after doing some axios petition in the try - * returns {status: "4xx", error: "error message"} - * or {status: "500", error: "Internal server error"} - * @param {*} error - */ - -function manageError(error){ - if(error.response) - return {status : error.response.status, error : error.response.data.error}; - else //Some other error - return {status : 500, error : "Interanl server error"}; -} - -module.exports = { - manageError -}; \ No newline at end of file diff --git a/webapp/src/utils/utils.js b/webapp/src/utils/utils.js new file mode 100644 index 00000000..3135a649 --- /dev/null +++ b/webapp/src/utils/utils.js @@ -0,0 +1,56 @@ +/** + * Receives an error found in a catch after doing some axios petition in the try + * returns {status: "4xx", error: "error message"} + * or {status: "500", error: "Internal server error"} + * @param {*} error + */ + +function manageError(error){ + if(error.response) + return {status : error.response.status, error : error.response.data.error}; + else //Some other error + return {status : 500, error : "Interanl server error"}; +} + +function validateEmail(errors, email){ + var isValid = String(email) + .toLowerCase() + .match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); + + if(!isValid) + errors.push("addUser.error_wrong_email_format") +} + +function validateUsername(errors, username){ + if(/\s/.test(username)){ + //Spaces in username + errors.push("addUser.error_username_spaces"); + } +} + +function validatePasswords(errors, password, repeatPassword){ + if(password !== repeatPassword){ + //User put the same password + errors.push("addUser.error_passwords_no_match"); + } + if(/\s/.test(password)){ + //User put spaces in password + errors.push("addUser.error_password_spaces"); + } + if(password.length < 8){ + //Password too short + errors.push("addUser.error_password_minimum_length"); + } + + if(password.length > 64){ + //Password too long + errors.push("addUser.error_password_maximum_length"); + } +} + +module.exports = { + manageError, + validateEmail, + validateUsername, + validatePasswords +}; \ No newline at end of file From 3df887a5b2c6fc1f9583600b256720773fdd57d6 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 16:51:05 +0200 Subject: [PATCH 07/16] Made frontend use the backend --- docker-compose.yml | 2 +- gatewayservice/gateway-service.js | 5 +---- users/userservice/user-service.js | 2 +- webapp/Dockerfile | 1 + webapp/src/components/ForgetPassword/ForgetPassword.js | 8 +++++--- .../components/ForgetPassword/ForgetPasswordFunctions.js | 2 +- webapp/src/translations/en/global.json | 3 ++- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 89ffbe46..43d9de66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,7 +108,7 @@ depends_on: - gatewayservice ports: - - "3000:3000" + - "80:80" prometheus: image: prom/prometheus diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 232c8666..0bcefac1 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -8,7 +8,7 @@ const fs = require("fs") const YAML = require('yaml') const jwt = require('jsonwebtoken'); const app = express(); -const port = 8010; +const port = 8000; //Setting up the email const nodemailer = require('nodemailer'); @@ -80,7 +80,6 @@ app.post('/forgetPassword', async (req, res) => { app.get('/tokenFromCode/:code', async (req, res) => { try { - console.log(forgetPasswords) var code = parseInt(req.params.code); if(forgetPasswords.has(code)){ var token = forgetPasswords.get(code) @@ -286,7 +285,6 @@ function validateUser(user){ } function manageError(res, error){ - console.log(error) if(error.response) //Some microservice responded with an error res.status(error.response.status).json({ error: error.response.data.error }); else //Some other error @@ -315,7 +313,6 @@ async function sendEmail(res, email, username, numbers) { await transporter.sendMail(mailOptions); res.send('Email sent successfully'); } catch (error) { - console.error('Error sending email:', error); res.status(500).send('Error sending email'); } } diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index b3aaa957..7c230074 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -7,7 +7,7 @@ const jwt = require('jsonwebtoken'); const User = require('./user-model') const app = express(); -const port = 8011; +const port = 8001; // Middleware to parse JSON in request body app.use(bodyParser.json()); diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 68c3b59f..ce09edc7 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -8,6 +8,7 @@ RUN npm install ARG API_URI="http://localhost:8000" ENV REACT_APP_API_ENDPOINT=$API_URI +ENV PORT=80 #Create an optimized version of the webapp RUN npm run build diff --git a/webapp/src/components/ForgetPassword/ForgetPassword.js b/webapp/src/components/ForgetPassword/ForgetPassword.js index b4fd3b7e..c00bc349 100644 --- a/webapp/src/components/ForgetPassword/ForgetPassword.js +++ b/webapp/src/components/ForgetPassword/ForgetPassword.js @@ -14,9 +14,9 @@ export default function ForgotPassword() { const [t] = useTranslation("global"); // Component state variables - const [emailStatus, setEmailStatus] = useState("RECEIVED"); - const [email, setEmail] = useState("dasf"); - const [username, setUsername] = useState("fdafds"); + const [emailStatus, setEmailStatus] = useState("ASK_EMAIL"); + const [email, setEmail] = useState(""); + const [username, setUsername] = useState(""); const [newPassword, setNewPassword] = useState(""); const [repeatPassword, setRepeatPassword] = useState(""); const [passwordStrength, setPasswordStrength] = useState({}); @@ -348,6 +348,8 @@ function RestorePassword({ onChange={(e) => setRepeatPassword(e.target.value)} />
+ +
); } diff --git a/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js b/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js index 782fa4dc..3ff1478d 100644 --- a/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js +++ b/webapp/src/components/ForgetPassword/ForgetPasswordFunctions.js @@ -10,7 +10,7 @@ class ForgetPasswordFunctions{ async sendEmail(email, username){ try { const response = await axios.post(this.apiUrl + '/forgetPassword', { email, username}); - return !!response.text; + return !!response.data; } catch (error) { throw new Error(error.message) } diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 467f87e7..96626a69 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -124,7 +124,8 @@ "enter_email":"Enter email and username", "enter_password":"Enter new password", "enter_email_button": "Send email", - "send_code": "Send code" + "send_code": "Send code", + "enter_password_button":"Send password" } } From a4b6160457b61f5e9eaeccb4ca4da1dd4bc81cb8 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 19:23:12 +0200 Subject: [PATCH 08/16] Modified tests location --- .github/workflows/release.yml | 2 +- questionGenerator/src/main/java/Main.java | 11 ++++++----- .../main/java/{StaticMain.java => MainStatic.java} | 2 +- .../questionGenerator/QuestionGeneratorTests.java | 5 +++-- .../generator/CapitalGeneratorTests.java | 2 +- .../generator/DirectorGeneratorTests.java | 2 +- .../generator/HeadOfGovernmentGeneratorTest.java | 2 +- .../generator/LanguageGeneratorTests.java | 2 +- .../generator/PopulationGeneratorTests.java | 2 +- .../generator/SizeGeneratorTests.java | 2 +- .../generator/VideogameCountryGeneratorTests.java | 2 +- .../generator/VideogameDeveloperGeneratorTests.java | 2 +- .../generator/VideogameGenreGeneratorTests.java | 2 +- .../generator/VideogamePublisherGeneratorTests.java | 2 +- .../questionGenerator/question/QuestionTests.java | 2 +- .../components/GameConfigurator/GameConfigurator.js | 2 -- 16 files changed, 22 insertions(+), 22 deletions(-) rename questionGenerator/src/main/java/{StaticMain.java => MainStatic.java} (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/QuestionGeneratorTests.java (97%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/CapitalGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/DirectorGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/HeadOfGovernmentGeneratorTest.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/LanguageGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/PopulationGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/SizeGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/VideogameCountryGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/VideogameDeveloperGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/VideogameGenreGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/generator/VideogamePublisherGeneratorTests.java (98%) rename questionGenerator/src/{test => tests}/java/questionGenerator/question/QuestionTests.java (97%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cef2a143..bbf44fd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -184,4 +184,4 @@ jobs: wget https://raw.githubusercontent.com/arquisoft/wiq_en1b/master/docker-compose.yml -O docker-compose.yml wget https://raw.githubusercontent.com/arquisoft/wiq_en1b/master/.env docker compose down - docker compose --profile prod up -d + docker compose --profile prod up -d --pull always diff --git a/questionGenerator/src/main/java/Main.java b/questionGenerator/src/main/java/Main.java index e5c144aa..f62293ce 100644 --- a/questionGenerator/src/main/java/Main.java +++ b/questionGenerator/src/main/java/Main.java @@ -1,6 +1,7 @@ package main.java; import java.util.ArrayList; +import java.util.Date; import java.util.List; import main.java.questionGenerator.QuestionGenerator; @@ -44,18 +45,18 @@ private static List generate() { List questions = new ArrayList(); for(String lang : languages) { qg.setLanguageCode(lang); - for(QuestionType type: types) + for(QuestionType type: types){ + System.out.println(String.format("Starting Type: %s Language: %s at: %s", type, lang, new Date())); questions.addAll(run(qg, type, NUMBER_OF_QUESTIONS)); + System.out.println(String.format("Type: %s Language: %s Finished", type, lang)); + } + } return questions; } private static List run(QuestionGenerator qg, QuestionType type, int numberOfQuestions) { List questions = qg.generateQuestions(type, numberOfQuestions); - for(int i=0; i

-


-

{t("gameConfigurator.competi_game")}

{t("gameConfigurator.rules_competi")}

{/* Botones para jugar un juego personalizado o competitivo */} From 0d82be6ee1e607b9e95225082586be50dc047c18 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 19:52:55 +0200 Subject: [PATCH 09/16] Added debug lines --- questionGenerator/src/main/java/Main.java | 2 +- .../src/main/java/questionGenerator/QuestionGenerator.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/questionGenerator/src/main/java/Main.java b/questionGenerator/src/main/java/Main.java index f62293ce..05c7002c 100644 --- a/questionGenerator/src/main/java/Main.java +++ b/questionGenerator/src/main/java/Main.java @@ -25,7 +25,7 @@ public class Main { //, QuestionType.VIDEOGAME_DEVELOPER, QuestionType.VIDEOGAME_PUBLISHER, QuestionType.VIDEOGAME_GENRE, QuestionType.VIDEOGAME_COUNTRY}; //private static final int NUMBER_OF_QUESTIONS = 50; - private static final int NUMBER_OF_QUESTIONS = 100; + private static final int NUMBER_OF_QUESTIONS = 75; //private static final int NUMBER_OF_QUESTIONS = 3; //private static final int NUMBER_OF_QUESTIONS = 1; diff --git a/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java b/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java index 1fd27702..ec3f851c 100644 --- a/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java +++ b/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java @@ -59,6 +59,7 @@ public List generateQuestions(QuestionType type, int amount){ try { q = generator.generate(entity); questions.add(q); + System.out.println(questions); } catch(RuntimeException e) { /* * Sometimes not all the parameters for generating can be passed in the query, so this @@ -129,9 +130,9 @@ public void setLanguageCode(String languageCode) { } private long getSampleSize(int amount) { - long value = amount * 10; - if(value<1000) - return 1000; + long value = amount * 2; + if(value>500) + return 500; return value; } From e9d7540faca742211ad3cc06f5ad374427e80484 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 27 Apr 2024 20:10:54 +0200 Subject: [PATCH 10/16] Added jest test ForgetPassword --- .../ForgetPassword/ForgetPassword.test.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 webapp/src/components/ForgetPassword/ForgetPassword.test.js diff --git a/webapp/src/components/ForgetPassword/ForgetPassword.test.js b/webapp/src/components/ForgetPassword/ForgetPassword.test.js new file mode 100644 index 00000000..d3417776 --- /dev/null +++ b/webapp/src/components/ForgetPassword/ForgetPassword.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import ForgetPassword from './ForgetPassword'; +import { MemoryRouter } from 'react-router-dom'; +import { act } from 'react-dom/test-utils'; +import { initReactI18next } from 'react-i18next'; +import i18en from 'i18next'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import userEvent from '@testing-library/user-event'; + +i18en.use(initReactI18next).init({ + resources: {}, + lng: 'en', + interpolation:{ + escapeValue: false, + } +}); +global.i18en = i18en; +const mockAxios = new MockAdapter(axios); +describe('ForgetPassword Component', () => { + test('renders Ask Email component', async () => { + await act(async () => { + render(); + }); + await waitFor(() => expect(screen.getByText(i18en.t("forgotPassword.enter_email"))).toBeInTheDocument()); + expect(screen.getByText(/addUser.email_placeholder/)).toBeInTheDocument(); + expect(screen.getByText(/addUser.username_placeholder/)).toBeInTheDocument(); + }); + test('renders AskForCode component', async () => { + + var code = 111111 + var token = "mockedToken" + await act(async () => { + render(); + }); + await waitFor(() => expect(screen.getByText(i18en.t("forgotPassword.enter_email"))).toBeInTheDocument()); + const emailInput = screen.getByPlaceholderText(/addUser.email_placeholder/i); + const usernameInput = screen.getByPlaceholderText(/addUser.username_placeholder/i); + + expect(screen.getByText(/addUser.email_placeholder/)).toBeInTheDocument(); + userEvent.type(emailInput, 'test@example.com'); + userEvent.type(usernameInput, 'testuser'); + + // Hacer clic en el botón de enviar + const submitButton = screen.getByText(/forgotPassword.enter_email_button/i); // Ajusta el texto según el texto real del botón + act(async ()=>{ + userEvent.click(submitButton); + mockAxios.onPost('http://localhost:8000/forgetPassword').reply(200, "Email sent"); + await waitFor(async () => expect(screen.getByText(i18en.t("forgotPassword.enter_code")).toBeInTheDocument())); + const inputs = screen.getAllByPlaceholderText('X'); + // Introducir el mismo carácter en todos los inputs + userEvent.type(inputs[0], '1'); // Introducir el carácter '1', puedes cambiarlo al que desees + userEvent.type(inputs[1], '1'); + userEvent.type(inputs[2], '1'); + userEvent.type(inputs[3], '1'); + userEvent.type(inputs[4], '1'); + userEvent.type(inputs[5], '1'); + // Simula un clic en el botón de submit + fireEvent.click(screen.getByText(/"forgotPassword.send_code"/)); + mockAxios.onGet('http://localhost:8000/tokenFromCode/' + code).reply(200, + {token: token}); + var username = "testUsername" + + }); + }); + + +}); From ca3f21f3b33eb62e5b07e7e4e6366a88b702021e Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 21:00:53 +0200 Subject: [PATCH 11/16] Added more debug lines --- questionGenerator/src/main/java/Main.java | 7 ++++--- .../src/main/java/questionGenerator/QuestionGenerator.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/questionGenerator/src/main/java/Main.java b/questionGenerator/src/main/java/Main.java index 05c7002c..312f9930 100644 --- a/questionGenerator/src/main/java/Main.java +++ b/questionGenerator/src/main/java/Main.java @@ -24,15 +24,16 @@ public class Main { QuestionType.SIZE, QuestionType.HEAD_OF_GOVERMENT}; //, QuestionType.VIDEOGAME_DEVELOPER, QuestionType.VIDEOGAME_PUBLISHER, QuestionType.VIDEOGAME_GENRE, QuestionType.VIDEOGAME_COUNTRY}; - //private static final int NUMBER_OF_QUESTIONS = 50; - private static final int NUMBER_OF_QUESTIONS = 75; + private static final int NUMBER_OF_QUESTIONS = 50; + //private static final int NUMBER_OF_QUESTIONS = 75; //private static final int NUMBER_OF_QUESTIONS = 3; //private static final int NUMBER_OF_QUESTIONS = 1; public static void main(String[] args) { + List questions = generate().stream().map(q -> q.getJSON().toString()).toList(); while(true) { - List questions = generate().stream().map(q -> q.getJSON().toString()).toList(); QuestionRepository.getInstance().populate(questions); + questions = generate().stream().map(q -> q.getJSON().toString()).toList(); try { Thread.sleep(TIME_SKIP); } catch (InterruptedException e) { diff --git a/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java b/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java index ec3f851c..773eceeb 100644 --- a/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java +++ b/questionGenerator/src/main/java/questionGenerator/QuestionGenerator.java @@ -59,7 +59,7 @@ public List generateQuestions(QuestionType type, int amount){ try { q = generator.generate(entity); questions.add(q); - System.out.println(questions); + System.out.println(questions.size()); } catch(RuntimeException e) { /* * Sometimes not all the parameters for generating can be passed in the query, so this From d09b790c06e3d19a8b150114d6533ad76b6d2981 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 27 Apr 2024 21:22:53 +0200 Subject: [PATCH 12/16] Added more to the jest test for forgetPassword --- .../ForgetPassword/ForgetPassword.test.js | 22 ++++++++++++------- .../questionView/QuestionView.test.js | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/webapp/src/components/ForgetPassword/ForgetPassword.test.js b/webapp/src/components/ForgetPassword/ForgetPassword.test.js index d3417776..b108e9a3 100644 --- a/webapp/src/components/ForgetPassword/ForgetPassword.test.js +++ b/webapp/src/components/ForgetPassword/ForgetPassword.test.js @@ -8,6 +8,7 @@ import i18en from 'i18next'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import userEvent from '@testing-library/user-event'; +import Cookies from 'js-cookie'; i18en.use(initReactI18next).init({ resources: {}, @@ -20,7 +21,7 @@ global.i18en = i18en; const mockAxios = new MockAdapter(axios); describe('ForgetPassword Component', () => { test('renders Ask Email component', async () => { - await act(async () => { + act( () => { render(); }); await waitFor(() => expect(screen.getByText(i18en.t("forgotPassword.enter_email"))).toBeInTheDocument()); @@ -28,25 +29,24 @@ describe('ForgetPassword Component', () => { expect(screen.getByText(/addUser.username_placeholder/)).toBeInTheDocument(); }); test('renders AskForCode component', async () => { - var code = 111111 var token = "mockedToken" - await act(async () => { + act( () => { render(); }); await waitFor(() => expect(screen.getByText(i18en.t("forgotPassword.enter_email"))).toBeInTheDocument()); const emailInput = screen.getByPlaceholderText(/addUser.email_placeholder/i); const usernameInput = screen.getByPlaceholderText(/addUser.username_placeholder/i); - + //introducimos email y username expect(screen.getByText(/addUser.email_placeholder/)).toBeInTheDocument(); userEvent.type(emailInput, 'test@example.com'); userEvent.type(usernameInput, 'testuser'); // Hacer clic en el botón de enviar const submitButton = screen.getByText(/forgotPassword.enter_email_button/i); // Ajusta el texto según el texto real del botón + userEvent.click(submitButton); + mockAxios.onPost('http://localhost:8000/forgetPassword').reply(200, "Email sent"); act(async ()=>{ - userEvent.click(submitButton); - mockAxios.onPost('http://localhost:8000/forgetPassword').reply(200, "Email sent"); await waitFor(async () => expect(screen.getByText(i18en.t("forgotPassword.enter_code")).toBeInTheDocument())); const inputs = screen.getAllByPlaceholderText('X'); // Introducir el mismo carácter en todos los inputs @@ -60,8 +60,14 @@ describe('ForgetPassword Component', () => { fireEvent.click(screen.getByText(/"forgotPassword.send_code"/)); mockAxios.onGet('http://localhost:8000/tokenFromCode/' + code).reply(200, {token: token}); - var username = "testUsername" - + //llegamos al replace + await waitFor(async () => expect(screen.getByText(i18en.t("forgotPassword.enter_password")).toBeInTheDocument())); + + mockAxios.onPost('http://localhost:8000/changePassword').reply(200, { token: token, username: username}); + //me redirigen a game Menu + await waitFor(async () => expect(screen.getByText(i18en.t('gameMenu.title')).toBeInTheDocument())); + //la cookie queda bien seteada + expect(Cookies.get('user')).toHaveProperty('token', token); }); }); diff --git a/webapp/src/components/questionView/QuestionView.test.js b/webapp/src/components/questionView/QuestionView.test.js index 11c2439a..7e818b50 100644 --- a/webapp/src/components/questionView/QuestionView.test.js +++ b/webapp/src/components/questionView/QuestionView.test.js @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import {configure} from '@testing-library/dom'; -import Cookies from 'js-cookie' +import Cookies from 'js-cookie'; // Función para configurar el mock de global.Audio const setupAudioMock = () => { From 786af0207b52ffda57e8473e81751b03e798fdde Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 27 Apr 2024 21:33:55 +0200 Subject: [PATCH 13/16] Added all translations --- webapp/src/translations/es/global.json | 7 +- webapp/src/translations/tk/global.json | 250 +++++++++++++------------ 2 files changed, 135 insertions(+), 122 deletions(-) diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 922ed71c..bf32ca33 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -136,8 +136,11 @@ "sending_title":"Revisa tu email", "sending_paragraph":"Para poder reestablecer tu email hemos enviado al email con el que te registraste un código de 6 cifras", "enter_code":"Introduce el código de 6 cifras enviado a tu email", - "enter_email":"Introduce Email y password" - + "enter_email":"Introduce Email y password", + "enter_password":"Introduce la nueva contraseña", + "enter_email_button": "Enviar Email", + "send_code": "Enviar Código", + "enter_password_button":"Enviar contraseña" } } diff --git a/webapp/src/translations/tk/global.json b/webapp/src/translations/tk/global.json index 528dec3e..4ecedf8b 100644 --- a/webapp/src/translations/tk/global.json +++ b/webapp/src/translations/tk/global.json @@ -1,132 +1,142 @@ - { - "home":{ - "welcome":"WIQ'a hoş geldiniz!", - "how_to_play":"Nasıl Oynanır?", - "login":"Giriş Yap", - "register":"Hesabınız yok mu? Buradan kaydolun.", - "language":"Dil İngilizce olarak değiştirildi", - "msg1":"Bilgi yarışmasına hoş geldiniz! Zihninizin sınanmasına ve becerilerinizi sergilemenize hazır olun. Bu heyecan verici oyunda, çeşitli konulardaki bilginizi test edecek bir dizi soruyla karşılaşacaksınız.", - "msg2":"Öyleyse ileri gidin, soruların ustası olduğunuzu kanıtlayın! Meydan okumayı kabul etmeye hazır mısınız?", - "clickOpen":"Devam etmek için tıklayın", - "clickClose":"Kapatmak için tıklayın", - "game":"Misafir olarak oyna" + "home": { + "welcome": "WIQ'a hoş geldiniz!", + "how_to_play": "Nasıl Oynanır?", + "login": "Giriş Yap", + "register": "Hesabınız yok mu? Buradan kaydolun.", + "language": "Dil İngilizce olarak değiştirildi", + "msg1": "Bilgi meydan okumasına hoş geldiniz! Zihninizin sınırlarını test etmeye ve becerilerinizi göstermeye hazırlanın. Bu heyecan verici oyun, çeşitli konulardaki bilginizi test eden bir dizi soruyla karşılaşmanızı sağlayacak.", + "msg2": "Öyleyse devam edin, soruların ustası olduğunuzu kanıtlayın! Meydana mı çıkmaya hazır mısınız?", + "clickOpen": "Devam etmek için tıklayın", + "clickClose": "Kapatmak için tıklayın", + "game":"Misafir olarak oyna" }, - "navBar":{ - "title":"Bil ve Kazan!", - "en":"İngilizce", - "es":"İspanyolca", - "tk":"Türkçe", - "language":"Dil", - "logout":"Çıkış Yap" + "navBar": { + "title": "Bil ve Kazan!", + "en": "İngilizce", + "es": "İspanyolca", + "tk": "Türkçe", + "language": "Dil", + "logout":"Çıkış Yap" }, - "instructions":{ - "title":"WIQ Talimatları", - "objective":"Hedef:", - "objective_p1":"Oyunun amacı, mümkün olduğunca çok soruyu doğru cevaplamaktır.", - "how_to_play":"Nasıl Oynanır:", - "how_to_play_p1":"Oyun bir dizi sorudan oluşur.", - "how_to_play_p2":"Her soruyu dikkatlice okuyun.", - "how_to_play_p3":"Sağlanan seçeneklerden doğru cevabı seçin.", - "how_to_play_p4":"Seçtiğiniz cevabı göndermek için üzerine tıklayın veya dokunun.", - "how_to_play_p5":"Uygulamaya kayıtlıysanız, 'Rekabetçi' modunun sıralamasında ve ilk 10 oyuncusunda pozisyonunuzu görebileceksiniz.", - "guest":"Misafir olarak oyna:", - "guest_p1":"'Misafir' modunda kayıt olmadan oynayabilirsiniz.", - "guest_p2":"Sonucunuzun herhangi bir sıralamada görünmeyeceğini ve bir geçmişinizin olmayacağını unutmayın.", - "guest_p3":"Bu özelliklerden yararlanmak istiyorsanız lütfen kaydolun.", - "personalization":"Oyunu Özelleştir:", - "personalization_p1":"Karşılaşacağınız soru türünü ve cevaplayacağınız soru sayısını seçebilirsiniz.", - "personalization_p2":"10 saniyeniz olacak ve farklı türlerden 5 sorunun olduğu rekabetçi modu oynayabilirsiniz.", - "scoring":"Puanlama:", - "scoring_p1":"Her doğru cevap size 100 puan kazandırır.", - "scoring_p2":"Yanlış cevaplar 50 puan düşürür.", - "time_limit":"Zaman Sınırı:", - "time_limit_p1":"Bazı oyun modlarında her soruya cevap vermek için bir zaman sınırı olabilir. Puanınızı maksimuma çıkarmak için hızlı ve doğru olun.", - "have_fun":"Keyfini Çıkarın!:", - "have_fun_p1":"Oyunun tadını çıkarın ve bilginizi test edin. İyi şanslar!", - "voice":"Ses ve Klavye Erişilebilirliği:", - "voice_p1":"Soruyu ve cevabı dinleme seçeneğiniz vardır. Bir düğmeye basarak soruyu ve ardından her birinin önünde bir numara olan cevapları duyabilirsiniz.", - "voice_p2":"Bir düğmeye basarak soruyu ve ardından her birinin önünde bir numara olan cevapları duyabilirsiniz.", - "voice_p3":"Bu numara, seçtiğiniz cevaba karşılık gelen klavyede ilgili numarayı basarak yanıt vermek için kullanılır.", - "voice_p4":"Ek olarak, 's' harfine basarak sesi etkinleştirebilirsiniz." + "instructions": { + "title": "WIQ Talimatları", + "objective": "Amaç:", + "objective_p1": "Oyunun amacı, mümkün olduğunca çok soruyu doğru cevaplamaktır.", + "how_to_play": "Nasıl Oynanır:", + "how_to_play_p1": "Oyun, bir dizi sorudan oluşur.", + "how_to_play_p2": "Her soruyu dikkatlice okuyun.", + "how_to_play_p3": "Sağlanan seçeneklerden doğru cevabı seçin.", + "how_to_play_p4": "Seçtiğiniz cevabı göndermek için tıklayın veya dokunun.", + "how_to_play_p5": "Uygulamaya kayıtlıysanız, 'Rekabetçi' moddaki sıranızı ve en iyi 10 oyuncuyu görebilirsiniz.", + "guest": "Misafir olarak oyna:", + "guest_p1": "Misafir olarak kaydolmadan oynayabilirsiniz.", + "guest_p2": "Sonuçlarınızın hiçbir sıralamada görünmeyeceğini ve oyun geçmişinizin olmayacağını unutmayın.", + "guest_p3": "Bu özelliklerin tadını çıkarmak istiyorsanız kaydolun.", + "personalization": "Oyunu Kişiselleştirin:", + "personalization_p1": "Karşılaşacağınız soru türünü ve cevaplayacağınız soru sayısını seçebilirsiniz.", + "personalization_p2": "Rekabetçi modda, cevaplamak için 10 saniyeniz olacak ve farklı türlerden 5 soru alacaksınız.", + "scoring": "Puanlama:", + "scoring_p1": "Her doğru cevap size 100 puan kazandırır.", + "scoring_p2": "Yanlış cevaplar 50 puanı düşürür.", + "time_limit": "Zaman Sınırı:", + "time_limit_p1": "Bazı oyun modlarında her soruya cevap vermek için bir zaman sınırı olabilir. Puanınızı maksimize etmek için hızlı ve doğru olun.", + "have_fun": "Keyfini Çıkarın!", + "have_fun_p1": "Oyunun tadını çıkarın ve bilginizi test edin. İyi şanslar!", + "voice": "Ses ve Klavye Erişimi:", + "voice_p1": "Soruyu ve cevabı dinleme seçeneğiniz vardır. Bir düğmeye basarak soruyu ve ardından cevapları bir numara ile dinleyebilirsiniz.", + "voice_p2": "Bir düğmeye basarak soruyu ve ardından cevapları bir numara ile dinleyebilirsiniz.", + "voice_p3": "Bu numara, istediğiniz cevaba klavyeden yanıt vermek için ilgili numarayı basmanızı sağlar.", + "voice_p4": "Ayrıca, 's' harfine basarak sesi etkinleştirebilirsiniz." }, - "login":{ - "title":"Giriş", - "username_placeholder":"Kullanıcı Adı veya E-posta", - "password_placeholder":"Şifre", - "remember_me":"Beni hatırla", - "forgot_password":"Şifrenizi mi unuttunuz?", - "login_button":"Giriş Yap", - "register_link":"Hesabınız yok mu? Buradan kaydolun." + "login": { + "title": "Giriş Yap", + "username_placeholder": "Kullanıcı Adı veya E-posta", + "password_placeholder": "Şifre", + "remember_me": "Beni Hatırla", + "forgot_password": "Şifrenizi mi unuttunuz?", + "login_button": "Giriş Yap", + "forget_pass":"Şifrenizi mi unuttunuz? Sıfırlayalım.", + "register_link": "Hesabınız yok mu? Buradan kaydolun." }, - "addUser":{ - "title":"Kayıt Ol", - "username_placeholder":"Kullanıcı Adı", - "password_placeholder":"Şifre", - "repeat_password_placeholder":"Şifreyi Tekrar Girin", - "register_button":"Kaydol", - "login_link":"Hesabınız var mı? Buradan giriş yapın.", - "email_placeholder":"E-posta", - "very_weak_password":"Çok zayıf şifre", - "weak_password":"Zayıf şifre", - "good_password":"İyi şifre", - "strong_password":"Güçlü şifre", - "error_passwords_no_match":"Şifreler uyuşmuyor", - "error_password_spaces":"Şifre boşluk içeremez", - "error_username_spaces":"Kullanıcı adı boşluk içeremez", - "error_password_minimum_length":"Şifre en az 8 karakter uzunluğunda olmalıdır", - "error_password_maximum_length":"Şifre en fazla 64 karakter uzunluğunda olabilir", - "error_username_in_use":"Kullanıcı adı zaten kullanımda", - "error_wrong_email_format":"Yanlış e-posta formatı (user@example.com)" + "addUser": { + "title": "Kayıt Ol", + "username_placeholder": "Kullanıcı Adı", + "password_placeholder": "Şifre", + "repeat_password_placeholder": "Şifreyi Tekrar Girin", + "register_button": "Kaydol", + "login_link": "Zaten bir hesabınız mı var? Buradan giriş yapın.", + "email_placeholder" : "E-posta", + "very_weak_password": "Çok Zayıf Şifre", + "weak_password": "Zayıf Şifre", + "good_password": "İyi Şifre", + "strong_password": "Güçlü Şifre", + "error_passwords_no_match": "Şifreler eşleşmiyor", + "error_password_spaces": "Şifre boşluk içeremez", + "error_username_spaces": "Kullanıcı adı boşluk içeremez", + "error_password_minimum_length": "Şifre en az 8 karakter olmalıdır", + "error_password_maximum_length": "Şifre en fazla 64 karakter olmalıdır", + "error_username_in_use": "Kullanıcı adı zaten kullanılıyor", + "error_wrong_email_format": "Yanlış e-posta biçimi (user@example.com)" }, - "gameMenu":{ - "history_button":"Geçmiş Verileri Görüntüle", - "new_game_button":"Yeni Oyun Oluştur", - "view_ranking":"Sıralama", - "title":"Oyun Menüsü", - "back":"Geri" + "gameMenu": { + "history_button": "Geçmişi Görüntüle", + "new_game_button": "Yeni Oyun Oluştur", + "view_ranking": "Sıralama", + "title": "Oyun Menüsü", + "back": "Geri" }, - "questionView":{ - "seconds":"saniye", - "question_counter":"Soru numarası ", - "end_countdown":"Süre doldu!", - "finished_game":"Oyun bitti!", - "point":"Puan", - "no_questions_message":"Lütfen biraz bekleyin" + "questionView": { + "seconds": "saniye", + "question_counter": "Soru numarası ", + "end_countdown": "Zaman doldu!", + "finished_game": "Oyun bitti!", + "point": "Puan", + "no_questions_message": "Lütfen biraz bekleyin" }, - "historicalView":{ - "game":"Oyun", - "points":"puan", - "no_games_played":"Henüz oyun oynanmamış" + "historicalView": { + "game": "Oyun", + "points": "puan", + "no_games_played": "Henüz oyun oynamadınız" }, - "gameConfigurator":{ - "game_config":"Oyun yapılandırması", - "type_quest":"Soru Türü: ", - "num_quest":"Soru Sayısı: ", - "play_custom":"Özelleştirilmiş Oyunu Oyna", - "rules_competi":"Tüm türlerdeki sorularla ve 5 adetle oyna", - "play_competi":"Rekabetçi Oyunu Oyna", - "option_all":"Hepsi", - "option_population":"Nüfus", - "option_capital":"Başkent", - "option_language":"Dil", - "option_size":"Boyut", - "option_head_of_goverment":"Hükümet Başkanı", - "custo_game":"Özel oyun oluştur", - "competi_game":"Rekabetçi Oyna", - "randomize":"Parametreleri Rastgeleleştir" + "gameConfigurator": { + "game_config": "Oyun yapılandırması", + "type_quest": "Soru Türü: ", + "num_quest": "Soru Sayısı: ", + "play_custom": "Özel Oyun Oyna", + "rules_competi": "Tüm türlerde ve 5 adet soru ile oynayın", + "play_competi": "Rekabetçi Oyun Oyna", + "option_all": "Tümü", + "option_population": "Nüfus", + "option_capital": "Başkent", + "option_language": "Dil", + "option_size": "Boyut", + "option_head_of_goverment":"Hükümet Başkanı", + "custo_game": "Özel oyun oluştur", + "competi_game": "Rekabetçi oyun oyna", + "randomize": "Parametreleri Rastgele Yap" }, - "ranking":{ - "ranking":"Sıralama", - "position":"Pozisyon", - "username":"Kullanıcı Adı", - "points":"Puanlar", - "num_games":"Rekabetçi oyunlar", - "search":"Ara", - "enter_username":"Kullanıcı Adını Girin..." + "ranking": { + "ranking": "Sıralama", + "position": "Pozisyon", + "username": "Kullanıcı Adı", + "points": "Puanlar", + "num_games": "Rekabetçi oyunlar", + "search": "Ara", + "enter_username": "Kullanıcı adını girin..." }, - "error":{ - "error":"Hata", - "sorry":"Üzgünüz, bu sayfa mevcut değil. Kızma, ben sadece küçük bir kediğim." + "error": { + "error": "Hata", + "sorry": "Üzgünüz, bu sayfa mevcut değil. Kızma, sadece küçük bir kedicik olduğumu unutma." + }, + "forgotPassword": { + "sending_title": "E-postanızı kontrol edin", + "sending_paragraph": "Şifrenizi yeniden ayarlamak için kaydolduğunuz e-postaya 6 haneli bir kod gönderdik", + "enter_code": "E-postanıza gönderilen 6 haneli kodu girin", + "enter_email": "E-posta ve kullanıcı adını girin", + "enter_password":"Yeni şifreyi girin", + "enter_email_button": "E-posta Gönder", + "send_code": "Kodu Gönder", + "enter_password_button":"Şifre Gönder" } - } \ No newline at end of file +} From 96a5af0c9de73cd9988b56ce63fc3a0511718418 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 27 Apr 2024 21:56:42 +0200 Subject: [PATCH 14/16] Erroneous global.json --- webapp/src/translations/es/global.json | 2 +- webapp/src/translations/tk/global.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 6ed87ab4..ade02ce4 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -155,7 +155,7 @@ "name2": "Lucía Ruiz Núñez", "name3": "Daniel Sinne Argüelles", "name4": "Jorge Cano Martínez", - "name5": "Ahmet Erdem Yabacı", + "name5": "Ahmet Erdem Yabaci", "name6": "Laura Gómez Menéndez" } diff --git a/webapp/src/translations/tk/global.json b/webapp/src/translations/tk/global.json index 66657d3a..1cc1db56 100644 --- a/webapp/src/translations/tk/global.json +++ b/webapp/src/translations/tk/global.json @@ -138,9 +138,8 @@ "enter_email_button": "E-posta Gönder", "send_code": "Kodu Gönder", "enter_password_button":"Şifre Gönder" - } -} -, + +}, "footer":{ "about": "Hakkımızda", "API": "API Belgeleri", @@ -156,4 +155,5 @@ "name5": "Ahmet Erdem Yabacı", "name6": "Laura Gómez Menéndez" } - } \ No newline at end of file +} + \ No newline at end of file From 32254dea4f51e75dfef4d612334782e53c55d721 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 22:34:40 +0200 Subject: [PATCH 15/16] Small tweaks --- gatewayservice/gateway-service.js | 2 +- questionGenerator/src/main/java/Main.java | 2 +- questionGenerator/src/main/java/MainStatic.java | 1 - .../src/main/java/questionGenerator/QuestionGenerator.java | 1 - .../answersAreEntites/AbstractAnswersAreEntites.java | 2 +- .../src/main/java/questionGenerator/question/Question.java | 6 ------ webapp/e2e/steps/competitiveGame.steps.js | 2 +- 7 files changed, 4 insertions(+), 12 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 0bcefac1..bab68145 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -21,7 +21,7 @@ const transporter = nodemailer.createTransport({ }); const authServiceUrl = process.env.AUTH_SERVICE_URL || 'http://localhost:8002'; -const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8011'; +const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001'; const questionServiceUrl = process.env.QUESTION_SERVICE_URL || 'http://localhost:8003'; const recordServiceUrl = process.env.RECORD_SERVICE_URL || 'http://localhost:8004'; diff --git a/questionGenerator/src/main/java/Main.java b/questionGenerator/src/main/java/Main.java index 312f9930..cd0480f8 100644 --- a/questionGenerator/src/main/java/Main.java +++ b/questionGenerator/src/main/java/Main.java @@ -24,7 +24,7 @@ public class Main { QuestionType.SIZE, QuestionType.HEAD_OF_GOVERMENT}; //, QuestionType.VIDEOGAME_DEVELOPER, QuestionType.VIDEOGAME_PUBLISHER, QuestionType.VIDEOGAME_GENRE, QuestionType.VIDEOGAME_COUNTRY}; - private static final int NUMBER_OF_QUESTIONS = 50; + private static final int NUMBER_OF_QUESTIONS = 20; //private static final int NUMBER_OF_QUESTIONS = 75; //private static final int NUMBER_OF_QUESTIONS = 3; //private static final int NUMBER_OF_QUESTIONS = 1; diff --git a/questionGenerator/src/main/java/MainStatic.java b/questionGenerator/src/main/java/MainStatic.java index 42cfba1e..f3e37431 100644 --- a/questionGenerator/src/main/java/MainStatic.java +++ b/questionGenerator/src/main/java/MainStatic.java @@ -29,7 +29,6 @@ private static void run(QuestionGenerator qg, QuestionType type, int numberOfQue List questions = qg.generateQuestions(type, numberOfQuestions); for(int i=0; i generateQuestions(QuestionType type, int amount){ try { q = generator.generate(entity); questions.add(q); - System.out.println(questions.size()); } catch(RuntimeException e) { /* * Sometimes not all the parameters for generating can be passed in the query, so this diff --git a/questionGenerator/src/main/java/questionGenerator/generator/answersAreEntites/AbstractAnswersAreEntites.java b/questionGenerator/src/main/java/questionGenerator/generator/answersAreEntites/AbstractAnswersAreEntites.java index 963104e7..49cf2ea0 100644 --- a/questionGenerator/src/main/java/questionGenerator/generator/answersAreEntites/AbstractAnswersAreEntites.java +++ b/questionGenerator/src/main/java/questionGenerator/generator/answersAreEntites/AbstractAnswersAreEntites.java @@ -47,7 +47,7 @@ private String getAnswer(String id) throws Exception{ public List getWrongAnswers(String rightAnswer) throws Exception { List entites = new ArrayList<>(); try { - entites = EntityGenerator.getEntities(type, getSampleSize(), getPropertyId()); + entites = EntityGenerator.getEntities(type, 20, getPropertyId()); } catch (IOException e) { e.printStackTrace(); } diff --git a/questionGenerator/src/main/java/questionGenerator/question/Question.java b/questionGenerator/src/main/java/questionGenerator/question/Question.java index 7068cbd6..7e9a33f3 100644 --- a/questionGenerator/src/main/java/questionGenerator/question/Question.java +++ b/questionGenerator/src/main/java/questionGenerator/question/Question.java @@ -9,7 +9,6 @@ public class Question { private String question; private List answers; - private int number = -1; private String language; private QuestionType type; @@ -43,17 +42,12 @@ public void setAnswers(List answers) { public void addRightAnswer(String answer) { answers.add(0, answer); } - - public void setNumber(int number){ - this.number = number; - } public JSONObject getJSON() { JSONObject json = new JSONObject(); json.accumulate("question", question); for(String s : answers) json.accumulate("answers", s); - if(number != -1) json.accumulate("number", number); //Para que los tests pasen json.accumulate("language", language); json.accumulate("type", type); return json; diff --git a/webapp/e2e/steps/competitiveGame.steps.js b/webapp/e2e/steps/competitiveGame.steps.js index ab5ad9e2..6e65e983 100644 --- a/webapp/e2e/steps/competitiveGame.steps.js +++ b/webapp/e2e/steps/competitiveGame.steps.js @@ -17,7 +17,7 @@ defineFeature(feature, test => { beforeAll(async () => { browser = await puppeteer.launch({ - headless: "new", + headless: false, slowMo: 40, defaultViewport: { width: 1920, height: 1080 }, args: ['--window-size=1920,1080'] From 5eaf7e6dc81a1447a7412e0dfbbb038a9946c905 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 27 Apr 2024 22:51:00 +0200 Subject: [PATCH 16/16] Small tweaks --- questionGenerator/src/main/java/MainStatic.java | 5 ----- webapp/e2e/steps/competitiveGame.steps.js | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/questionGenerator/src/main/java/MainStatic.java b/questionGenerator/src/main/java/MainStatic.java index f3e37431..2d45c8d8 100644 --- a/questionGenerator/src/main/java/MainStatic.java +++ b/questionGenerator/src/main/java/MainStatic.java @@ -27,11 +27,6 @@ public static void main(String[] args) { private static void run(QuestionGenerator qg, QuestionType type, int numberOfQuestions){ List questions = qg.generateQuestions(type, numberOfQuestions); - for(int i=0; i q.getJSON().toString()).toList()); } } diff --git a/webapp/e2e/steps/competitiveGame.steps.js b/webapp/e2e/steps/competitiveGame.steps.js index 6e65e983..ab5ad9e2 100644 --- a/webapp/e2e/steps/competitiveGame.steps.js +++ b/webapp/e2e/steps/competitiveGame.steps.js @@ -17,7 +17,7 @@ defineFeature(feature, test => { beforeAll(async () => { browser = await puppeteer.launch({ - headless: false, + headless: "new", slowMo: 40, defaultViewport: { width: 1920, height: 1080 }, args: ['--window-size=1920,1080']