diff --git a/.eslintrc.js b/.eslintrc.js index 8ee479f..cb00900 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,9 @@ module.exports = { 'max-lines-per-function': ['error', { max: 75, skipComments: true }], 'no-underscore-dangle': 0, 'react/jsx-props-no-spreading': 0, - 'react/prop-types': 0 + 'react/prop-types': 0, + 'no-unused-vars': 'off', + //'@typescipt-eslint/no-unused-vars': ['error'], + 'prettier/prettier': ['error', { endOfLine: 'auto' }] } }; diff --git a/src/models/AuthCode.ts b/src/models/AuthCode.ts index 3c566a9..a646cd0 100644 --- a/src/models/AuthCode.ts +++ b/src/models/AuthCode.ts @@ -39,7 +39,8 @@ const authCodeSchema: Schema = new Schema( */ { // Here's an example of how to add a field to the schema. - exampleField: { required: true, type: String, unique: false } + phoneNumber: { required: true, type: String, unique: true }, + value: { default: AuthUtils.generateOTP, required: true, type: Number } }, { timestamps: true } ); diff --git a/src/models/User.ts b/src/models/User.ts index a06e0c2..7110fdf 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -5,7 +5,6 @@ import AuthUtils from '../utils/AuthUtils'; import { Model } from '../utils/constants'; import { AuthTokens, BaseModel } from '../utils/types'; - interface IUser extends BaseModel { email?: string; @@ -64,20 +63,18 @@ export type UserDocument = Document<{}, {}, IUser> & const userSchema: Schema = new Schema( { - - email: { required: false, sparse: true, type: String, unique: true}, - firstName: { required: false, type: String}, - instagramUrl: { required: false, type: String}, - lastName: { required: false, type: String}, - linkedInUrl: { required: false, type: String}, - profilePictureKey: { required: false, type: String}, - phoneNumber : { required: true, type: String, unique: true}, - twitterUrl: { required: false, type: String}, - - + email: { required: false, sparse: true, type: String, unique: true }, + firstName: { required: false, type: String }, + instagramUrl: { required: false, type: String }, + lastName: { required: false, type: String }, + linkedInUrl: { required: false, type: String }, + phoneNumber: { required: true, type: String, unique: true }, + profilePictureKey: { required: false, type: String }, // We shouldn't be returning the refreshToken when fetching a user from // the database, since that is sensitive information. - refreshToken: { required: false, select: false, type: String} + refreshToken: { required: false, select: false, type: String }, + + twitterUrl: { required: false, type: String } }, { timestamps: true, @@ -90,6 +87,7 @@ type TokenArgs = { date: number; id: string; }; + /* * Creates and returns a new access and refresh token for the user. It also * persists the refresh token to the database, so we can associate the token diff --git a/src/routes/LoginRoute.test.ts b/src/routes/LoginRoute.test.ts index 353aaf3..9f9a2cb 100644 --- a/src/routes/LoginRoute.test.ts +++ b/src/routes/LoginRoute.test.ts @@ -10,7 +10,7 @@ import TestUtils from '../utils/TestUtils'; * npm run test LoginRoute * - Delete this comment. */ -describe.skip('POST /login', () => { +describe('POST /login', () => { test('If the phone number is not valid, should return a 400.', async () => { await TestUtils.agent .post('/login') diff --git a/src/routes/LoginRoute.ts b/src/routes/LoginRoute.ts index 4d37ccb..b1d85ea 100644 --- a/src/routes/LoginRoute.ts +++ b/src/routes/LoginRoute.ts @@ -13,29 +13,22 @@ type LoginBody = Pick; export default class LoginRoute extends BaseRoute { constructor() { super({ - /** - * TODO: (7.01) - * - Replace null with the correct route type from the RouteMethod enum - * in the constants.ts file. - * - Fill in the path string with the appropriate path to this endpoint. - * - Delete this comment. - */ - method: null, - path: '/' + method: RouteMethod.POST, + path: '/login' }); } - /** - * Validate the following inputs: - * - body.phoneNumber - */ middleware() { /** * TODO: (7.02) * - Add a validation the returned array ensureing that the phoneNumber * field in the body is a valid US phone number. */ - return []; + return [ + body('phoneNumber') + .isMobilePhone('en-US') + .withMessage('This is not a valid phone number') + ]; } /** @@ -56,16 +49,30 @@ export default class LoginRoute extends BaseRoute { * - Send a text to the user with the code. */ // TODO: (7.03) Get the phone number from the request body. + const { phoneNumber } = req.body; // TODO: (7.03) We should delete all codes that previously existed for the // user. + await AuthCode.deleteMany({ phoneNumber }); // TODO: (7.03) Create a new AuthCode document in the database. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const authCode: AuthCodeDocument = await AuthCode.create({ phoneNumber }); // TODO: (7.03) Send a text to the user. + const wasTextSent: boolean = await TextService.sendText({ + message: 'Your OTP code: $(authCode.value)', + to: phoneNumber + }); // TODO: (7.03) If the text was not sent, throw a new RouteError with status // code 500. + if (!wasTextSent) { + throw new RouteError({ + message: 'Failed to send OTP code text, please try again', + statusCode: 500 + }); + } return true; } diff --git a/src/routes/UpdateMeRoute.test.ts b/src/routes/UpdateMeRoute.test.ts index dfb149c..1f05a30 100644 --- a/src/routes/UpdateMeRoute.test.ts +++ b/src/routes/UpdateMeRoute.test.ts @@ -9,7 +9,7 @@ import TestUtils from '../utils/TestUtils'; * npm run test UpdateMe * - Delete this comment. */ -describe.skip('PATCH /me', () => { +describe('PATCH /me', () => { test('If the user is not authenticated, should return a 401.', async () => { await TestUtils.agent.patch('/me').expect(401); }); diff --git a/src/routes/UpdateMeRoute.ts b/src/routes/UpdateMeRoute.ts index b51dd0f..560d15e 100644 --- a/src/routes/UpdateMeRoute.ts +++ b/src/routes/UpdateMeRoute.ts @@ -25,9 +25,9 @@ export default class UpdateMeRoute extends BaseRoute { * - Fill in the path string with the appropriate path to this endpoint. * - Delete this comment. */ - authenticated: false, - method: null, - path: '/' + authenticated: true, + method: RouteMethod.PATCH, + path: '/me' }); } @@ -54,6 +54,21 @@ export default class UpdateMeRoute extends BaseRoute { .isURL() .withMessage('The Instagram URL must be a valid URL.'), + body('linkedInUrl') + .if((value: string) => Utils.isDefined(value) && !!value.length) + .isURL() + .withMessage('The LinkedIn URL must be a valid URL.'), + + body('twitterUrl') + .if((value: string) => Utils.isDefined(value) && !!value.length) + .isURL() + .withMessage('The Twitter URL must be a valid URL.'), + + body('lastName') + .if((value: string) => Utils.isDefined(value)) + .isLength({ min: 1 }) + .withMessage('Last name cannot be empty.'), + multer().single('profilePicture') ]; } diff --git a/tsconfig.json b/tsconfig.json index d84c6a5..11796c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,17 @@ -{ - "compilerOptions": { - "esModuleInterop": true, - "module": "commonjs", - "outDir": "./dist", - "target": "es2018" - }, - "include": ["src/**/*.ts"], - "exclude": [ - "node_modules", - "src/**/*.test.ts", - "./*.config.ts", - "./jest.*.ts" - ], - "typeRoots": ["./node_modules/@types"] -} +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "commonjs", + "outDir": "./dist", + "target": "es2018" + + }, + "include": ["src/**/*.ts"], + "exclude": [ + "node_modules", + "src/**/*.test.ts", + "./*.config.ts", + "./jest.*.ts" + ], + "typeRoots": ["./node_modules/@types"] +}