Skip to content

Commit

Permalink
Implemented user registration feature with input validation, password…
Browse files Browse the repository at this point in the history
… hashing, and database integration

fix(user registration): resolve registration bug

- ensure that a user provide neccessary inputs
- restructure user entity
- refactor other codes depending on user registration logic

[Fixes #39]

rebasing from develop , adding verifie route and service and send email to the user

rebasing from develop , adding verifie route and service and send email to the user
  • Loading branch information
maxCastro1 committed May 5, 2024
1 parent 618cb5c commit 87ccd0c
Show file tree
Hide file tree
Showing 23 changed files with 520 additions and 74 deletions.
13 changes: 10 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
PORT= ********************************
APP_ENV= ********************************
PDN_DB_NAME= *****************************

TEST_DB_HOST= ********************************
TEST_DB_PORT= ********************************
TEST_DB_USER= ********************************
TEST_DB_PASS= ********************************
TEST_DB_NAME= ********************************

DEV_DB_HOST= ********************************
DEV_DB_PORT= ********************************
DEV_DB_USER= ********************************
DEV_DB_PASS= *****************************
DEV_DB_TYPE= *******************************
DEV_DB_NAME= *******************************

PDN_DB_HOST= ********************************
PDN_DB_PORT= ********************************
PDN_DB_USER= ********************************
PDN_DB_PASS= ********************************
PDN_DB_PASS= ********************************
PDN_DB_NAME= *****************************
15 changes: 9 additions & 6 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
Expand All @@ -28,8 +25,14 @@ module.exports = {
'space-before-function-paren': ['warn', 'always'],
'func-style': ['warn', 'declaration', { allowArrowFunctions: true }],
'camelcase': 'warn',
'@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }],
'@typescript-eslint/explicit-member-accessibility': ['off', { accessibility: 'explicit' }],
'@typescript-eslint/explicit-function-return-type': [
'warn',
{ allowExpressions: true },
],
'@typescript-eslint/explicit-member-accessibility': [
'off',
{ accessibility: 'explicit' },
],
'no-unused-vars': 'warn',
'no-extra-semi': 'warn',
},
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ name: knights-ecomm-be CI
on: [push, pull_request]

env:
DEV_DB_HOST: ${{secrets.DEV_DB_HOST}}
DEV_DB_PORT: ${{secrets.DEV_DB_PORT}}
DEV_DB_USER: ${{secrets.DEV_DB_USER}}
DEV_DB_PASS: ${{secrets.DEV_DB_PASS}}
DEV_DB_NAME: ${{secrets.DEV_DB_NAME}}
TEST_DB_HOST: ${{secrets.TEST_DB_HOST}}
TEST_DB_PORT: ${{secrets.TEST_DB_PORT}}
TEST_DB_USER: ${{secrets.TEST_DB_USER}}
TEST_DB_PASS: ${{secrets.TEST_DB_PASS}}
TEST_DB_NAME: ${{secrets.TEST_DB_NAME}}

jobs:
build-lint-test-coverage:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ package-lock.json
coverage/
dist
/src/logs
.DS_Stor
.DS_Store
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# E-commerse Backend API
[![knights-ecomm-be CI](https://github.com/atlp-rwanda/knights-ecomm-be/actions/workflows/ci.yml/badge.svg)](https://github.com/atlp-rwanda/knights-ecomm-be/actions/workflows/ci.yml)    [![Coverage Status](https://coveralls.io/repos/github/atlp-rwanda/knights-ecomm-be/badge.svg?branch=ch-ci-setup)](https://coveralls.io/github/atlp-rwanda/knights-ecomm-be?branch=ch-ci-setup)    [![Version](https://img.shields.io/badge/version-1.0.0-blue)](https://github.com/your-username/your-repo-name/releases/tag/v1.0.0)

[![Coverage Status](https://coveralls.io/repos/github/atlp-rwanda/knights-ecomm-be/badge.svg?branch=develop)](https://coveralls.io/github/atlp-rwanda/knights-ecomm-be?branch=develop)
  
[![Coverage Status](https://coveralls.io/repos/github/atlp-rwanda/knights-ecomm-be/badge.svg?branch=ch-ci-setup)](https://coveralls.io/github/atlp-rwanda/knights-ecomm-be?branch=ch-ci-setup)
  
[![Version](https://img.shields.io/badge/version-1.0.0-blue)](https://github.com/your-username/your-repo-name/releases/tag/v1.0.0)

## Description

Expand Down
29 changes: 29 additions & 0 deletions migrations/1714595134552-UserMigration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUserMigration1614495123940 implements MigrationInterface {
public async up (queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "user" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"firstName" character varying NOT NULL,
"lastName" character varying NOT NULL,
"email" character varying NOT NULL,
"password" character varying NOT NULL,
"gender" character varying NOT NULL,
"phoneNumber" character varying NOT NULL,
"photoUrl" character varying,
"verified" boolean NOT NULL,
"status" character varying NOT NULL CHECK (status IN ('active', 'suspended')),
"userType" character varying NOT NULL DEFAULT 'Buyer' CHECK (userType IN ('Admin', 'Buyer', 'Vendor')),
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"),
CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id")
)
`);
}

public async down (queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}
63 changes: 39 additions & 24 deletions ormconfig.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
module.exports = {
"type": "postgres",
"host": `${process.env.DEV_DB_HOST}`,
"port": `${process.env.DEV_DB_PORT}`,
"username": `${process.env.DEV_DB_USER}`,
"password": `${process.env.DEV_DB_PASS}`,
"database": `${process.env.DEV_DB_NAME}`,
"synchronize": true,
"logging": false,
"entities": [
"src/entities/**/*.ts"
],
"migrations": [
"src/migrations/**/*.ts"
],
"subscribers": [
"src/subscribers/**/*.ts"
],
"cli": {
"entitiesDir": "src/entities",
"migrationsDir": "src/migrations",
"subscribersDir": "src/subscribers"
}
};
const devConfig = {
type: 'postgres',
host: process.env.DEV_DB_HOST,
port: process.env.DEV_DB_PORT,
username: process.env.DEV_DB_USER,
password: process.env.DEV_DB_PASS,
database: process.env.DEV_DB_NAME,
synchronize: true,
logging: false,
entities: ['src/entities/**/*.ts'],
migrations: ['src/migrations/**/*.ts'],
subscribers: ['src/subscribers/**/*.ts'],
cli: {
entitiesDir: 'src/entities',
migrationsDir: 'src/migrations',
subscribersDir: 'src/subscribers',
},
};

const testConfig = {
type: 'postgres',
host: process.env.TEST_DB_HOST,
port: process.env.TEST_DB_PORT,
username: process.env.TEST_DB_USER,
password: process.env.TEST_DB_PASS,
database: process.env.TEST_DB_NAME,
synchronize: true,
logging: false,
entities: ['src/entities/**/*.ts'],
migrations: ['src/migrations/**/*.ts'],
subscribers: ['src/subscribers/**/*.ts'],
cli: {
entitiesDir: 'src/entities',
migrationsDir: 'src/migrations',
subscribersDir: 'src/subscribers',
},
};

module.exports = process.env.NODE_ENV === 'test' ? testConfig : devConfig;
20 changes: 15 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"description": "E-commerce backend",
"main": "index.js",
"scripts": {
"test": "jest --coverage --detectOpenHandles --verbose --runInBand",
"dev": "nodemon src/index.ts",
"test": "cross-env APP_ENV=test jest --coverage --detectOpenHandles --verbose --runInBand ",
"dev": "cross-env APP_ENV=dev nodemon src/index.ts",
"build": "tsc -p .",
"start": "node dist/index.js",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write ."
"format": "prettier --write .",
"typeorm": "typeorm-ts-node-commonjs",
"migration": " npm run typeorm migration:run -- -d ./ormconfig.js"
},
"keywords": [],
"author": "Scrum master",
Expand All @@ -19,13 +21,17 @@
"@types/express-winston": "^4.0.0",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"bcrypt": "^5.1.1",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"highlight.js": "^11.9.0",
"express-winston": "^4.2.0",
"highlight.js": "^11.9.0",
"jsend": "^1.1.0",
"morgan": "^1.10.0",
"nodemailer": "^6.9.13",
"nodemon": "^3.1.0",
"pg": "^8.11.5",
"reflect-metadata": "^0.2.2",
Expand All @@ -42,6 +48,7 @@
},
"devDependencies": {
"@eslint/js": "^9.1.1",
"@types/bcrypt": "^5.0.2",
"@types/body-parser": "^1.19.5",
"@types/cors": "^2.8.17",
"@types/dotenv": "^8.2.0",
Expand All @@ -50,8 +57,10 @@
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/jsend": "^1.0.32",
"@types/node": "^20.12.7",
"@types/morgan": "^1.9.9",
"@types/node": "^20.12.7",
"@types/nodemailer": "^6.4.15",
"@types/reflect-metadata": "^0.1.0",
"@types/supertest": "^6.0.2",
"@types/winston": "^2.4.4",
"@typescript-eslint/eslint-plugin": "^7.7.1",
Expand All @@ -62,6 +71,7 @@
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"jest-mock-extended": "^3.0.6",
"prettier": "^3.2.5",
"supertest": "^7.0.0",
"ts-jest": "^29.1.2",
Expand Down
112 changes: 103 additions & 9 deletions src/__test__/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,118 @@
import request from 'supertest';
import { app, server } from '../index'; // update this with the path to your app file

import { createConnection, getConnection, getConnectionOptions } from 'typeorm';
import { app, server } from '../index';
import { createConnection, getConnection, getConnectionOptions, getRepository } from 'typeorm';
import { User } from '../entities/User';

beforeAll(async () => {
// Connect to the test database
const connectionOptions = await getConnectionOptions();

await createConnection({ ...connectionOptions, name: 'testConnection' });
});

afterAll(async () => {
await getConnection('testConnection').close();
const connection = getConnection('testConnection');
const userRepository = connection.getRepository(User);

// Delete all records from the User
await userRepository.clear();

// Close the connection to the test database
await connection.close();

server.close();
});

describe('GET /', () => {
// afterAll(done => {
// server.close(done);
// });
it('This is a testing route that returns', done => {
request(app)
.get('/api/v1/status')
.expect(200)
.expect('Content-Type', /json/)
.expect(
{
status: 'success',
data: {
code: 200,
message: 'This is a testing route.',
},
},
done
);
});
});
describe('POST /user/register', () => {
it('should register a new user', async () => {
// Arrange
const newUser = {
firstName: 'John',
lastName: 'Doe',
email: 'johndoe06@example.com',
password: 'password',
gender: 'Male',
phoneNumber: '123678116',
userType: 'Buyer',
photoUrl: 'https://example.com/photo.jpg',
};

it('responds with "Knights Ecommerce API"', done => {
request(app).get('/').expect(200, 'Knights Ecommerce API', done);
// Act
const res = await request(app).post('/user/register').send(newUser);

// Assert
expect(res.status).toBe(201);
expect(res.body).toEqual({
status: 'success',
data: {
code: 201,
message: 'User registered successfully',
},
});

// Clean up: delete the test user
const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: newUser.email } });
if (user) {
await userRepository.remove(user);
}
});
});
describe('POST /user/verify/:id', () => {
it('should verify a user', async () => {
// Arrange
const newUser = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe1@example.com',
password: 'password',
gender: 'Male',
phoneNumber: '123456789',
userType: 'Buyer',
photoUrl: 'https://example.com/photo.jpg',
};

// Create a new user
const res = await request(app).post('/user/register').send(newUser);

Check warning on line 94 in src/__test__/route.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'res' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 94 in src/__test__/route.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'res' is assigned a value but never used

const userRepository = getRepository(User);
const user = await userRepository.findOne({ where: { email: newUser.email } });

if(user){
const verifyRes = await request(app).get(`/user/verify/${user.id}`);

// Assert
expect(verifyRes.status).toBe(200);
expect(verifyRes.text).toEqual('<p>User verified successfully</p>');

Check warning on line 105 in src/__test__/route.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
// Check that the user's verified field is now true
const verifiedUser = await userRepository.findOne({ where: { email: newUser.email } });
if (verifiedUser){
expect(verifiedUser.verified).toBe(true);
}

}

Check warning on line 113 in src/__test__/route.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

Trailing spaces not allowed
if (user) {
await userRepository.remove(user);
}
});
});
15 changes: 15 additions & 0 deletions src/controllers/authController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Request, Response } from 'express';
import { User } from '../entities/User';

Check warning on line 2 in src/controllers/authController.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'User' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 2 in src/controllers/authController.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'User' is defined but never used
import bcrypt from 'bcrypt';

Check warning on line 3 in src/controllers/authController.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'bcrypt' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 3 in src/controllers/authController.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'bcrypt' is defined but never used
import { getRepository } from 'typeorm';

Check warning on line 4 in src/controllers/authController.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'getRepository' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 4 in src/controllers/authController.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'getRepository' is defined but never used
import { responseError, responseServerError, responseSuccess } from '../utils/response.utils';
import { validate } from 'class-validator';
import { userVerificationService, userRegistrationService } from '../services';

export const userRegistration = async (req: Request, res: Response) => {
await userRegistrationService(req, res);
}
export const userVerification = async (req: Request, res: Response) => {
await userVerificationService(req, res);
}

Loading

0 comments on commit 87ccd0c

Please sign in to comment.