diff --git a/docs/api/api_data.js b/docs/api/api_data.js index 25e0c5a6..3bfaa726 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -60,10 +60,10 @@ define({ "api": [ }, { "group": "body", - "type": "String", + "type": "Number", "optional": false, - "field": "birthDate", - "description": "
a Date parsable string.
" + "field": "age", + "description": "The user's age.
" }, { "group": "body", @@ -86,7 +86,7 @@ define({ "api": [ "examples": [ { "title": "Request-Example:", - "content": "{ \n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"password\":\"hunter2\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"birthDate\":\"10/30/1997\"\n}", + "content": "{ \n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"password\":\"hunter2\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"age\":23,\n}", "type": "json" } ] @@ -113,7 +113,7 @@ define({ "api": [ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account creation successful\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account creation successful\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] @@ -195,7 +195,7 @@ define({ "api": [ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account found by user id\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account found by user id\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] @@ -379,7 +379,7 @@ define({ "api": [ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account found by user email\", \n \"data\": {\n \t\"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account found by user email\", \n \"data\": {\n \t\"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] @@ -466,10 +466,10 @@ define({ "api": [ }, { "group": "body", - "type": "String", + "type": "Number", "optional": true, - "field": "birthDate", - "description": "A Date parsable string.
" + "field": "age", + "description": "The user's age.
" }, { "group": "body", @@ -517,7 +517,7 @@ define({ "api": [ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account update successful.\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\": \"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account update successful.\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\": \"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] diff --git a/docs/api/api_data.json b/docs/api/api_data.json index cfc8a3ab..685b3dec 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -60,10 +60,10 @@ }, { "group": "body", - "type": "String", + "type": "Number", "optional": false, - "field": "birthDate", - "description": "a Date parsable string.
" + "field": "age", + "description": "The user's age.
" }, { "group": "body", @@ -86,7 +86,7 @@ "examples": [ { "title": "Request-Example:", - "content": "{ \n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"password\":\"hunter2\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"birthDate\":\"10/30/1997\"\n}", + "content": "{ \n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"password\":\"hunter2\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"age\":23,\n}", "type": "json" } ] @@ -113,7 +113,7 @@ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account creation successful\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account creation successful\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] @@ -195,7 +195,7 @@ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account found by user id\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account found by user id\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \"gender\":\"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] @@ -379,7 +379,7 @@ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account found by user email\", \n \"data\": {\n \t\"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account found by user email\", \n \"data\": {\n \t\"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\":\"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] @@ -466,10 +466,10 @@ }, { "group": "body", - "type": "String", + "type": "Number", "optional": true, - "field": "birthDate", - "description": "A Date parsable string.
" + "field": "age", + "description": "The user's age.
" }, { "group": "body", @@ -517,7 +517,7 @@ "examples": [ { "title": "Success-Response: ", - "content": "{\n \"message\": \"Account update successful.\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\": \"Male\",\n \"birthDate\":Date(\"10/30/1997\")\n }\n }", + "content": "{\n \"message\": \"Account update successful.\", \n \"data\": {\n \"id\": ObjectId(\"5bff8b9f3274cf001bc71048\"),\n \t\"firstName\": \"Theo\",\n \"lastName\":\"Klein\",\n \"pronoun\":\"he/him\",\n \"email\":\"theo@klein.com\",\n \"phoneNumber\":1234567890,\n \t\"gender\": \"Male\",\n \"age\":23,\n }\n }", "type": "object" } ] diff --git a/docs/standards.md b/docs/standards.md index 08be5dc4..ea7c5bf4 100644 --- a/docs/standards.md +++ b/docs/standards.md @@ -352,7 +352,7 @@ module.exports = { VALIDATOR.regexValidator("body", "email", true, Constants.EMAIL_REGEX), VALIDATOR.alphaArrayValidator("body", "dietaryRestrictions", true), VALIDATOR.enumValidator("body", "shirtSize", Constants.SHIRT_SIZES, true), - VALIDATOR.dateValidator("body", "birthDate", true), + VALIDATOR.ageValidator("body", "age", true), VALIDATOR.phoneNumberValidator("body", "phoneNumber", true) ], }; diff --git a/middlewares/account.middleware.js b/middlewares/account.middleware.js index c57f9dfb..95763e02 100644 --- a/middlewares/account.middleware.js +++ b/middlewares/account.middleware.js @@ -55,7 +55,7 @@ function parseAccount(req, res, next) { password: Services.Account.hashPassword(req.body.password), dietaryRestrictions: req.body.dietaryRestrictions, gender: req.body.gender, - birthDate: req.body.birthDate, + age: req.body.age, phoneNumber: req.body.phoneNumber }; @@ -67,7 +67,7 @@ function parseAccount(req, res, next) { delete req.body.password; delete req.body.dietaryRestrictions; delete req.body.gender; - delete req.body.birthDate; + delete req.body.age; delete req.body.phoneNumber; req.body.accountDetails = accountDetails; diff --git a/middlewares/validators/account.validator.js b/middlewares/validators/account.validator.js index 51ada35b..b20a8221 100644 --- a/middlewares/validators/account.validator.js +++ b/middlewares/validators/account.validator.js @@ -18,7 +18,7 @@ module.exports = { process.env.JWT_CONFIRM_ACC_SECRET, true ), - VALIDATOR.dateValidator("body", "birthDate", false), + VALIDATOR.ageValidator("body", "age", false), VALIDATOR.phoneNumberValidator("body", "phoneNumber", true) ], updateAccountValidator: [ @@ -29,7 +29,7 @@ module.exports = { VALIDATOR.regexValidator("body", "email", true, Constants.EMAIL_REGEX), VALIDATOR.alphaArrayValidator("body", "dietaryRestrictions", true), VALIDATOR.stringValidator("body", "gender", true), - VALIDATOR.dateValidator("body", "birthDate", true), + VALIDATOR.ageValidator("body", "age", true), VALIDATOR.phoneNumberValidator("body", "phoneNumber", true) ], inviteAccountValidator: [ diff --git a/middlewares/validators/validator.helper.js b/middlewares/validators/validator.helper.js index 5e293648..d4da9bb9 100644 --- a/middlewares/validators/validator.helper.js +++ b/middlewares/validators/validator.helper.js @@ -839,6 +839,50 @@ function dateValidator(fieldLocation, fieldname, optional = true) { isValid: date }); } +} + +/** + * Validates that field must be a valid age between 0 and 100. + * @param {"query" | "body" | "header" | "param"} fieldLocation the location where the field should be found + * @param {string} fieldname name of the field that needs to be validated. + * @param {boolean} optional whether the field is optional or not. + */ +function ageValidator(fieldLocation, fieldname, optional = true) { + const age = setProperValidationChainBuilder( + fieldLocation, + fieldname, + "Invalid age" + ); + if (optional) { + return age + .optional({ + checkFalsy: true + }) + .custom((value) => { + // Check if the value is empty or undefined + if (!value) return true; // Allow empty values if optional + + // Validate age: must be a number between 0 and 100 + const ageNumber = Number(value); + return !isNaN(ageNumber) && ageNumber >= 0 && ageNumber <= 100; + }) + .withMessage({ + message: "Age is not valid. It must be between 0 and 100.", + isValid: age + }); + } else { + return age + .exists() + .withMessage("Age field must be specified") // Ensure field exists + .custom((value) => { + const ageNumber = Number(value); + return !isNaN(ageNumber) && ageNumber >= 0 && ageNumber <= 100; + }) + .withMessage({ + message: "Age is not valid. It must be between 0 and 100.", + isValid: age + }); + } } /** @@ -1027,6 +1071,7 @@ module.exports = { searchSortValidator: searchSortValidator, phoneNumberValidator: phoneNumberValidator, dateValidator: dateValidator, + ageValidator: ageValidator, enumValidator: enumValidator, routesValidator: routesValidator, stringValidator: stringValidator diff --git a/models/account.model.js b/models/account.model.js index 047c8bc0..fe83490f 100644 --- a/models/account.model.js +++ b/models/account.model.js @@ -46,8 +46,8 @@ const AccountSchema = new mongoose.Schema({ enum: Constants.EXTENDED_USER_TYPES, default: Constants.HACKER }, - birthDate: { - type: Date, + age: { + type: Number, required: true }, phoneNumber: { @@ -85,15 +85,16 @@ AccountSchema.methods.isSponsor = function() { this.accountType == Constants.SPONSOR ); }; + /** - * Calculates the user's age + * CHANGED BIRTHDATE TO AGE - Calculates the user's age */ -AccountSchema.methods.getAge = function() { - // birthday is a date - var ageDifMs = Date.now() - this.birthDate.getTime(); - var ageDate = new Date(ageDifMs); // miliseconds from epoch - return Math.abs(ageDate.getUTCFullYear() - 1970); -}; +// AccountSchema.methods.getAge = function() { +// // birthday is a date +// var ageDifMs = Date.now() - this.birthDate.getTime(); +// var ageDate = new Date(ageDifMs); // miliseconds from epoch +// return Math.abs(ageDate.getUTCFullYear() - 1970); +// }; //export the model module.exports = mongoose.model("Account", AccountSchema); diff --git a/package-lock.json b/package-lock.json index e7587c64..b9331981 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "eslint": "8.56.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-prettier": "5.1.3", - "mocha": "^10.0.0", + "mocha": "^10.2.0", "nodemon": "^3.0.2", "prettier": "3.1.1" } diff --git a/package.json b/package.json index cbc503c5..bad02641 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,13 @@ "private": true, "scripts": { "start": "DEBUG=hackboard:* NODE_ENV=development nodemon --ignore gcp_creds.json ./bin/www.js", - "start-windows": "set DEBUG=hackboard:* && set NODE_ENV=development && nodemon --ignore gcp_creds.json ./bin/www.js", + "start-windows": "set DEBUG=hackboard:* && set NODE_ENV=test && nodemon --ignore gcp_creds.json ./bin/www.js", "deploy": "NODE_ENV=deployment node ./bin/www.js", "debug": "DEBUG=hackboard:* NODE_ENV=test nodemon --ignore gcp_creds.json ./bin/www.js", "test": "DEBUG=hackboard:* NODE_ENV=test mocha -r dotenv/config --reporter spec tests/**.js --exit", + "test-windows": "DEBUG=hackboard:* SET NODE_ENV=test & mocha -r dotenv/config --reporter spec tests/**.js --exit", "seed": "NODE_ENV=test node ./seed/index.js", + "seed-windows": "SET NODE_ENV=test & node ./seed/index.js", "docs": "apidoc -i ./routes -o ./docs/api/", "format": "prettier --write '**/*.js'", "lint": "eslint --fix '**/*.js'" diff --git a/routes/api/account.js b/routes/api/account.js index 7a41dbea..9481051b 100644 --- a/routes/api/account.js +++ b/routes/api/account.js @@ -43,7 +43,7 @@ module.exports = { "email":"theo@klein.com", "phoneNumber":1234567890, "gender":"Male", - "birthDate":Date("10/30/1997") + "age":23") } } * @apiError {string} message Error message @@ -72,7 +72,7 @@ module.exports = { * @apiParam (body) {String} gender Gender of the account creator. * @apiParam (body) {String[]} dietaryRestrictions Any dietary restrictions for the user. 'None' if there are no restrictions * @apiParam (body) {String} password The password of the account. - * @apiParam (body) {String} birthDate a Date parsable string. + * @apiParam (body) {Number} age The user's age. * @apiParam (body) {Number} [phoneNumber] the user's phone number, represented as a string. * @apiParam (header) {JWT} [token] the user's invite token. * @@ -85,7 +85,7 @@ module.exports = { "password":"hunter2", "phoneNumber":1234567890, "gender":"Male", - "birthDate":"10/30/1997" + "age:23" * } * * @apiSuccess {string} message Success message @@ -101,7 +101,7 @@ module.exports = { "email":"theo@klein.com", "phoneNumber":1234567890, "gender":"Male", - "birthDate":Date("10/30/1997") + "age:23") } } @@ -212,9 +212,8 @@ module.exports = { * @apiParam (body) {String} [pronoun] The pronoun of the account creator. * @apiParam (body) {String} [email] Email of the account. * @apiParam (body) {String} [gender] Gender of the account creator. - * @apiParam (body) {String} [birthDate] A Date parsable string. + * @apiParam (body) {String} [age] Age of the account creator. * @apiParam (body) {Number} [phoneNumber] The user's phone number, represented as a string. - * @apiParam (body) {String} [birthDate] a Date parsable string. * @apiParam (body) {String[]} [dietaryRestrictions] Any dietary restrictions for the user. 'None' if there are no restrictions * @apiParamExample {json} Request-Example: * { "gender": "Male" } @@ -233,7 +232,7 @@ module.exports = { "email":"theo@klein.com", "phoneNumber":1234567890, "gender": "Male", - "birthDate":Date("10/30/1997") + "age:23") } } @@ -280,7 +279,7 @@ module.exports = { "email":"theo@klein.com", "phoneNumber":1234567890, "gender":"Male", - "birthDate":Date("10/30/1997") + "age:23") } } diff --git a/services/hacker.service.js b/services/hacker.service.js index f47b43da..bd86f7e8 100644 --- a/services/hacker.service.js +++ b/services/hacker.service.js @@ -201,8 +201,12 @@ function getStats(hackers) { .shirtSize[hacker.application.accommodation.shirtSize] ? stats.shirtSize[hacker.application.accommodation.shirtSize] + 1 : 1; - const age = hacker.accountId.getAge(); - stats.age[age] = stats.age[age] ? stats.age[age] + 1 : 1; + + // CHANGED BIRTHDATE TO AGE + // const age = hacker.accountId.getAge(); + // stats.age[age] = stats.age[age] ? stats.age[age] + 1 : 1; + + stats.age[hacker.accountId.age] = stats.age[hacker.accountId.age] ? stats.age[age] + 1 : 1; const applicationDate = hacker._id .getTimestamp() // diff --git a/tests/util/account.test.util.js b/tests/util/account.test.util.js index ec521dee..4e8655f5 100644 --- a/tests/util/account.test.util.js +++ b/tests/util/account.test.util.js @@ -83,8 +83,9 @@ function generateRandomValue(atr) { return Constants.EXTENDED_USER_TYPES[ Math.floor(Math.random() * Constants.EXTENDED_USER_TYPES.length) ]; - case "birthDate": - return new Date(); + case "age": + // Generate a random age between 0 and 100 + return Math.floor(Math.random() * 101); // 0 to 100 case "phoneNumber": return Math.floor(Math.random() * 10000000000); } @@ -264,7 +265,7 @@ const waitlistedHacker0 = { gender: "Male", confirmed: true, accountType: Constants.HACKER, - birthDate: "1990-01-04", + age: 23, phoneNumber: 1000000004 }; @@ -280,7 +281,7 @@ const NonConfirmedAccount1 = { dietaryRestrictions: ["something1", "something2"], gender: "Male", confirmed: false, - birthDate: "1980-07-30", + age: 23, phoneNumber: 1001230236, accountType: Constants.HACKER }; @@ -315,7 +316,7 @@ const NoPhoneHackerAccount0 = { dietaryRestrictions: ["something1", "something2"], gender: "Male", confirmed: false, - birthDate: "1980-07-30", + age: "23", accountType: Constants.HACKER };