diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index c08d0e37..62b3dc83 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -33,7 +33,7 @@ app.post('/login', async (req, res) => { const authResponse = await axios.post(authServiceUrl+'/login', req.body); res.json(authResponse.data); } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -43,7 +43,7 @@ app.post('/adduser', async (req, res) => { const userResponse = await axios.post(userServiceUrl+'/adduser', req.body); res.json(userResponse.data); } catch (error) { - manageError(error); + manageError(res, error); } }); @@ -55,7 +55,7 @@ app.get('/questions', verifyToken, async (req, res) => { const questionResponse = await axios.get(questionServiceUrl+'/questions'); res.json(questionResponse.data); } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -78,7 +78,7 @@ app.get('/questions/:lang/:amount/:type', verifyToken, async (req, res) => { } } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -97,7 +97,7 @@ app.get('/questions/:lang/:amount', verifyToken, async (req, res) => { res.json(questionResponse.data); } } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -115,7 +115,7 @@ app.get('/questions/:lang', verifyToken, async (req, res) => { } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -126,7 +126,7 @@ app.post('/record', verifyToken, async(req, res) => { const recordResponse = await axios.post(recordServiceUrl+'/record', req.body); res.json(recordResponse.data); } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -136,7 +136,7 @@ app.get('/record/ranking/top10', verifyToken, async(req, res)=>{ const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/top10'); res.json(recordResponse.data); } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -151,7 +151,7 @@ app.get('/record/ranking/:user', verifyToken, async(req, res)=>{ res.json(recordResponse.data); } } catch (error) { - manageError(error) + manageError(res, error) } }); @@ -166,20 +166,24 @@ app.get('/record/:user', verifyToken, async(req, res)=>{ res.json(recordResponse.data); } } catch (error) { - manageError(error) + manageError(res, error) } }); // Read the OpenAPI YAML file synchronously -const file = fs.readFileSync('./openapi.yaml', 'utf8'); +const openapiPath = './openapi.yaml' +if(fs.existsSync(openapiPath)){ + const file = fs.readFileSync(openapiPath, 'utf8'); -// Parse the YAML content into a JavaScript object representing the Swagger document -const swaggerDocument = YAML.parse(file); + // Parse the YAML content into a JavaScript object representing the Swagger document + const swaggerDocument = YAML.parse(file); + + // Serve the Swagger UI documentation at the '/api-doc' endpoint + // This middleware serves the Swagger UI files and sets up the Swagger UI page + // It takes the parsed Swagger document as input + app.use('/api-doc', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +} -// Serve the Swagger UI documentation at the '/api-doc' endpoint -// This middleware serves the Swagger UI files and sets up the Swagger UI page -// It takes the parsed Swagger document as input -app.use('/api-doc', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // Start the gateway service const server = app.listen(port, () => { @@ -206,7 +210,7 @@ function verifyToken(req, res, next) { } function validateLang(lang){ - return ['en', 'es', 'tk'].includes(lang); + return ['en', 'es', 'tr'].includes(lang); } function validateAmount(amount) { @@ -223,7 +227,7 @@ function validateUser(user){ return !(/\s/.test(user)) //True if there are no spaces } -function manageError(error){ +function manageError(res, 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 diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml index 47615d2a..6755daa9 100644 --- a/gatewayservice/openapi.yaml +++ b/gatewayservice/openapi.yaml @@ -1,17 +1,33 @@ openapi: 3.0.0 info: - title: wiq_en1b API - description: Our project's OpenAPI specification. - version: 0.1.2 + title: WIQ_en1b API + description: WIQ_en1b OpenAPI specification. + version: "0.2" contact: name: Project github url: https://github.com/Arquisoft/wiq_en1b servers: - - url: http://localhost:8000 - description: Development server - url: http://wiqen1b.serveminecraft.net:8000 description: Production server + - url: http://localhost:8000 + description: Development server paths: + /health: + get: + summary: Check the health status of the service. + operationId: checkHealth + responses: + '200': + description: Service is healthy. + content: + application/json: + schema: + type: object + properties: + status: + type: string + description: Health status. + example: OK /adduser: post: summary: Add a new user to the database. @@ -23,25 +39,29 @@ paths: schema: type: object properties: + email: + type: string + description: User email + example: user@example.com username: type: string - description: User ID. + description: User name example: student password: type: string - description: User password. - example: pass + description: User password + example: password123 + repeatPassword: + type: string + description: Userpassword + example: password123 responses: '200': description: User added successfully. content: application/json: schema: - type: object - properties: - username: - type: string - description: User ID + $ref: '#/components/schemas/LoginResponse' '400': description: Failed to add user content: @@ -64,22 +84,6 @@ paths: type: string description: Error information. example: Internal Server Error - /health: - get: - summary: Check the health status of the service. - operationId: checkHealth - responses: - '200': - description: Service is healthy. - content: - application/json: - schema: - type: object - properties: - status: - type: string - description: Health status. - example: OK /login: post: summary: Log in to the system. @@ -93,70 +97,158 @@ paths: properties: username: type: string - description: User ID. + description: UserID. example: student password: type: string - description: User password. + description: Userpassword. example: pass responses: '200': - description: Login successful. Returns user token, and username. + description: Login successful. Returns user token, username and email. content: application/json: schema: - type: object - properties: - token: - type: string - description: User token. - example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWY3NTZkYjNmYTIyZDIyN2E0YjdjN2QiLCJpYXQiOjE3MTA3MDg3NDUsImV4cCI6MTcxMDcxMjM0NX0.VMG_5DOyQ4GYlJQRcu1I6ICG1IGzuo2Xuei093ONHxw - username: - type: string - description: Username. - example: student + $ref: '#/components/schemas/LoginResponse' '400': description: Invalid credentials. content: application/json: schema: - type: object - properties: - error: - type: string - description: Shows the error info.. - example: Invalid credentials + $ref: '#/components/schemas/Error' '500': description: Internal server error. content: application/json: schema: - type: object - properties: - error: - type: string - description: Error information. - example: Internal Server Error + $ref: '#/components/schemas/Error' /questions: get: summary: Get 5 random questions from the system in any language + parameters: + - name: token + in: header + schema: + type: string responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/QuestionResponse' + $ref: '#/components/schemas/QuestionResponse' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /questions/{lang}: get: summary: Get 5 random questions from the system in the lang specified parameters: + - name: token + in: header + schema: + type: string + - name: lang + in: path + required: true + schema: + type: string + enum: [es, en, tk] + description: Language code for filtering questions + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionResponse' + '400': + description: Wrong values given. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /questions/{lang}/{amount}: + get: + summary: Get an amount of random questions from the system in the lang specified + parameters: + - name: token + in: header + schema: + type: string + - name: lang + in: path + required: true + schema: + type: string + enum: [es, en, tk] + description: Language code for filtering questions + - name: amount + in: path + required: true + schema: + type: integer + minimum: 1 + maximum: 20 + description: Amount of questions retrieved must be between 1 and 20 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QuestionResponse' + '400': + description: Wrong values given. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /questions/{lang}/{amount}/{type}: + get: + summary: Get an amount of questions of the given type from the system in the lang specified + parameters: + - name: token + in: header + schema: + type: string - name: lang in: path required: true schema: type: string - description: Language code for filtering questions must be es or en + enum: ["es", "en", "tk"] + description: Language code for filtering questions + - name: amount + in: path + required: true + schema: + type: integer + minimum: 1 + maximum: 20 + description: Amount of questions retrieved must be between 1 and 20 + - name: type + in: path + required: true + schema: + type: string + enum: ["POPULATION", "CAPITAL", "LANGUAGE", "SIZE"] + description: Type for filtering questions responses: '200': description: OK @@ -164,9 +256,26 @@ paths: application/json: schema: $ref: '#/components/schemas/QuestionResponse' + '400': + description: Wrong values given. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /record: post: summary: Add a new record to the database + parameters: + - name: token + in: header + schema: + type: string requestBody: required: true content: @@ -183,10 +292,20 @@ paths: properties: user: type: string + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /record/{user}: get: summary: Gets the records from a given user parameters: + - name: token + in: header + schema: + type: string - name: user in: path required: true @@ -200,38 +319,83 @@ paths: application/json: schema: $ref: '#/components/schemas/RecordResponse' + '400': + description: Wrong values given. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /record/ranking/top10: + get: + summary: Gets the top 10 users on the system + parameters: + - name: token + in: header + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Top10Ranking' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /record/ranking/{user}: + get: + summary: Gets the ranking information of the given user + parameters: + - name: token + in: header + schema: + type: string + - name: user + in: path + required: true + schema: + type: string + description: username of the user + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UserRanking' + '400': + description: Wrong values given. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: schemas: - LoginRequest: - type: object - properties: - username: - type: string - password: - type: string - required: - - username - - password LoginResponse: type: object properties: token: type: string - User: - type: object - properties: username: type: string email: type: string - required: - - username - - email - UserResponse: - type: object - properties: - message: - type: string QuestionResponse: type: array items: @@ -270,7 +434,7 @@ components: correctAnswer: type: string points: - type: number + type: integer date: type: string required: @@ -303,11 +467,34 @@ components: correctAnswer: type: string points: - type: number + type: integer date: type: string Error: type: object properties: error: - type: string \ No newline at end of file + type: string + Ranking: + type: object + properties: + _id: + type: string + totalPoints: + type: integer + totalCompetitiveGames: + type: integer + position: + type: integer + UserRanking: + type: object + properties: + userCompetitiveStats: + $ref: '#/components/schemas/Ranking' + Top10Ranking: + type: object + properties: + usersCompetitiveStats: + type: array + items: + $ref: '#/components/schemas/Ranking' \ No newline at end of file diff --git a/users/authservice/auth-service.js b/users/authservice/auth-service.js index 924dc512..a233e849 100644 --- a/users/authservice/auth-service.js +++ b/users/authservice/auth-service.js @@ -2,7 +2,7 @@ const express = require('express'); const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); -const User = require('./auth-model') +const User = require('./user-model') const app = express(); const port = 8002; diff --git a/users/authservice/auth-service.test.js b/users/authservice/auth-service.test.js index 7f258f5c..e1518db7 100644 --- a/users/authservice/auth-service.test.js +++ b/users/authservice/auth-service.test.js @@ -1,13 +1,14 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); const bcrypt = require('bcrypt'); -const User = require('./auth-model'); +const User = require('./user-model'); let mongoServer; let app; //test user let user = { + email: "user@gmail.com", username: 'testuser', password: 'testpassword', }; @@ -15,7 +16,7 @@ let user = { async function addUser(user){ const hashedPassword = await bcrypt.hash(user.password, 10); const newUser = new User({ - email: "user@gmail.com", + email: user.email, username: user.username, password: hashedPassword, createdAt: new Date() diff --git a/users/authservice/auth-model.js b/users/authservice/user-model.js similarity index 100% rename from users/authservice/auth-model.js rename to users/authservice/user-model.js diff --git a/users/userservice/user-model.js b/users/userservice/user-model.js index e6643ff2..5cdbaf69 100644 --- a/users/userservice/user-model.js +++ b/users/userservice/user-model.js @@ -1,22 +1,10 @@ const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ - email: { - type: String, - required: true, - }, - username: { - type: String, - required: true, - }, - password: { - type: String, - required: true, - }, - createdAt: { - type: Date, - default: Date.now, - }, + email: String, + username: String, + password: String, + createdAt: Date, }); const User = mongoose.model('User', userSchema); diff --git a/webapp/e2e/features/competitiveGame.feature b/webapp/e2e/features/competitiveGame.feature new file mode 100644 index 00000000..073860be --- /dev/null +++ b/webapp/e2e/features/competitiveGame.feature @@ -0,0 +1,5 @@ +Feature: Game Configurator and Competitive Game functionality + Scenario: Create Competitive Game should go to /questions + Given I am on the game configurator + When I click on new competitive game + Then I am in /questions \ No newline at end of file diff --git a/webapp/e2e/steps/competitiveGame.steps.js b/webapp/e2e/steps/competitiveGame.steps.js new file mode 100644 index 00000000..95f41dad --- /dev/null +++ b/webapp/e2e/steps/competitiveGame.steps.js @@ -0,0 +1,50 @@ +const puppeteer = require('puppeteer'); +const { defineFeature, loadFeature } = require('jest-cucumber'); +const setDefaultOptions = require('expect-puppeteer').setDefaultOptions; + +const feature = loadFeature('./features/competitiveGame.feature'); + +const { register, login, logout } = require("../utils"); + +let page; +let browser; + +const email = "testUser@example.com"; +const username = "testUser" +const password = "testUserPassword" + +defineFeature(feature, test => { + + beforeAll(async () => { + browser = await puppeteer.launch({ + headless: "new", + slowMo: 40, + defaultViewport: { width: 1920, height: 1080 }, + args: ['--window-size=1920,1080'] + }); + + page = await browser.newPage(); + setDefaultOptions({ timeout: 30000 }); + + await register(page, email, username, password); + }); + + beforeEach(async () => { + await logout(page); + await login(page, username, password); + }) + + test('Create Competitive Game should go to /questions', ({ given,when, then }) => { + given('I am on the game configurator', async () => { + await page.goto('http://localhost:3000/configurator'); + await page.waitForSelector('.GameConfiguratorDiv'); + }); + when('I click on new competitive game', async () => { + await page.click('#competitive'); + }); + then('I am in /questions', async () => { + await expect(page).toMatchElement('.questionContainer'); + }); + }); + +}); diff --git a/webapp/e2e/test-environment-setup.js b/webapp/e2e/test-environment-setup.js index efd12b6e..44e73ddb 100644 --- a/webapp/e2e/test-environment-setup.js +++ b/webapp/e2e/test-environment-setup.js @@ -294,7 +294,14 @@ async function loadQuestions() { }] //No need of loading questions for these tests - //await Question.bulkSave(questions); + await questions.forEach(async question =>{ + let dbQuestion = new Question(); + dbQuestion.question=question.question; + dbQuestion.answers=question.answers; + dbQuestion.language=question.language; + dbQuestion.type=question.type; + await dbQuestion.save(); + }); } startServer(); diff --git a/webapp/openapi.yaml b/webapp/openapi.yaml deleted file mode 100644 index 98968bbf..00000000 --- a/webapp/openapi.yaml +++ /dev/null @@ -1,313 +0,0 @@ -openapi: 3.0.0 -info: - title: wiq_en1b API - description: Our project's OpenAPI specification. - version: 0.1.2 - contact: - name: Project github - url: https://github.com/Arquisoft/wiq_en1b -servers: - - url: http://localhost:8000 - description: Development server - - url: http://SOMEIP:8000 - description: Production server -paths: - /adduser: - post: - summary: Add a new user to the database. - operationId: addUser - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - username: - type: string - description: User ID. - example: student - password: - type: string - description: User password. - example: pass - responses: - '200': - description: User added successfully. - content: - application/json: - schema: - type: object - properties: - username: - type: string - description: User ID - '400': - description: Failed to add user - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Error information. - example: Username already in use - '500': - description: Internal server error. - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Error information. - example: Internal Server Error - /health: - get: - summary: Check the health status of the service. - operationId: checkHealth - responses: - '200': - description: Service is healthy. - content: - application/json: - schema: - type: object - properties: - status: - type: string - description: Health status. - example: OK - /login: - post: - summary: Log in to the system. - operationId: loginUser - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - username: - type: string - description: User ID. - example: student - password: - type: string - description: User password. - example: pass - responses: - '200': - description: Login successful. Returns user token, and username. - content: - application/json: - schema: - type: object - properties: - token: - type: string - description: User token. - example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NWY3NTZkYjNmYTIyZDIyN2E0YjdjN2QiLCJpYXQiOjE3MTA3MDg3NDUsImV4cCI6MTcxMDcxMjM0NX0.VMG_5DOyQ4GYlJQRcu1I6ICG1IGzuo2Xuei093ONHxw - username: - type: string - description: Username. - example: student - '400': - description: Invalid credentials. - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Shows the error info.. - example: Invalid credentials - '500': - description: Internal server error. - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: Error information. - example: Internal Server Error - /questions: - get: - summary: Get 5 random questions from the system in any language - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/QuestionResponse' - /questions/{lang}: - get: - summary: Get 5 random questions from the system in the lang specified - parameters: - - name: lang - in: path - required: true - schema: - type: string - description: Language code for filtering questions must be es or en - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/QuestionResponse' - /record: - post: - summary: Add a new record to the database - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Record' - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - user: - type: string - /record/{user}: - get: - summary: Gets the records from a given user - parameters: - - name: user - in: path - required: true - schema: - type: string - description: Username for filtering records - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/RecordResponse' -components: - schemas: - LoginRequest: - type: object - properties: - username: - type: string - password: - type: string - required: - - username - - password - LoginResponse: - type: object - properties: - token: - type: string - User: - type: object - properties: - username: - type: string - email: - type: string - required: - - username - - email - UserResponse: - type: object - properties: - message: - type: string - QuestionResponse: - type: array - items: - type: object - properties: - question: - type: string - answers: - type: array - items: - type: string - correct_answer: - type: string - Record: - type: object - properties: - user: - type: string - game: - type: object - properties: - questions: - type: array - items: - type: object - properties: - question: - type: string - example: What is the capital of Oviedo? - answers: - type: array - items: - type: string - answerGiven: - type: string - correctAnswer: - type: string - points: - type: number - date: - type: string - required: - - user - - game - RecordResponse: - type: object - properties: - user: - type: string - games: - type: array - items: - type: object - properties: - questions: - type: array - items: - type: object - properties: - question: - type: string - example: What is the capital of Oviedo? - answers: - type: array - items: - type: string - answerGiven: - type: string - correctAnswer: - type: string - points: - type: number - date: - type: string - Error: - type: object - properties: - error: - type: string \ No newline at end of file diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.js b/webapp/src/components/GameConfigurator/GameConfigurator.js index 4a536729..5ab12b27 100644 --- a/webapp/src/components/GameConfigurator/GameConfigurator.js +++ b/webapp/src/components/GameConfigurator/GameConfigurator.js @@ -79,7 +79,7 @@ function ButtonCustomized({t,handleClick}) { function ButtonCompetitive({t}){ return ( - +

{t("gameConfigurator.play_competi")}

); diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index ec7a1b6d..5ede8c08 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -41,7 +41,10 @@ class QuestionGenerator{ let response; if(type==="COMPETITIVE"){ response = await axios.get(this.apiUrl + '/' + lang, {headers : {'token':token}}); - }else{ + }else if(type==="ALL"){ + response = await axios.get(this.apiUrl + '/' + lang + '/' +amount, {headers : {'token':token}}); + } + else{ response = await axios.get(this.apiUrl + '/' + lang + '/' +amount + '/' + type, {headers : {'token':token}}); } console.log(response)