Skip to content

Commit

Permalink
Ft send verification email 187584913 (#30)
Browse files Browse the repository at this point in the history
* [finishes #187584913] finished sending verification email features

* [finishes #187584913] finished sending verification email features
  • Loading branch information
AimePazzo authored May 28, 2024
1 parent 15533e4 commit 80e32f4
Show file tree
Hide file tree
Showing 19 changed files with 622 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
- image: cimg/node:18.17.0
steps:
- setup_remote_docker:
version: 20.10.7
version: docker24
- checkout
- run:
name: update-npm
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
push:
branches:
- develop

env:
PORT: ${{ secrets.PORT }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
Expand All @@ -17,6 +16,12 @@ env:
API_SECRET: ${{ secrets.API_SECRET }}
CLOUD_NAME: ${{ secrets.CLOUD_NAME }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
SERVER_URL_PRO : ${{ secrets.SERVER_URL_PRO }}
SMTP_HOST_PORT: ${{ secrets.SMTP_HOST_PORT }}
MP : ${{ secrets.MP }}
SMTP_HOST: ${{ secrets.SMTP_HOST }}
MAIL_ID: ${{ secrets.MAIL_ID }}
DB_HOST_TYPE: ${{ secrets.DB_HOST_TYPE }}

jobs:
build:
Expand Down Expand Up @@ -47,6 +52,7 @@ jobs:

- run: npm run coverage --if-present


- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
Expand Down
7 changes: 7 additions & 0 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"check-coverage": true,
"lines": 80,
"functions": 80,
"branches": 80,
"statements": 80
}
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ This is the backend for E-Commerce-Ninjas, written in Node.js with TypeScript.

- Welcome Endpoint
- Register Endpoint
- Verification Email Endpoint
- Resend verification Endpoint

## TABLE OF API ENDPOINTS SPECIFICATION AND DESCRIPTION


| No | VERBS | ENDPOINTS | STATUS | ACCESS | DESCRIPTION |
|----|-------|-------------------|--------|--------|-------------------- |
| 1 | GET | / | 200 OK | public | Show welcome message|
| 2 | POST | /api/auth/register| 200 OK | public | create user account |
| No | VERBS | ENDPOINTS | STATUS | ACCESS | DESCRIPTION |
|----|-------|------------------------------|-------------|--------|---------------------------|
| 1 | GET | / | 200 OK | public | Show welcome message |
| 2 | GET | /api/auth/verify-email/:token| 200 OK | public | Verifying email |
| 3 | POST | /api/auth/register | 201 CREATED | public | create user account |
| 4 | POST | /api/auth/send-verify-email | 200 OK | public | Resend verification email |



Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
},
"nyc": {
"extends": "@istanbuljs/nyc-config-typescript",
"check-coverage": true,
"all": true,
"include": [
"src/**/!(*.test.*).[tj]s?(x)"
],
Expand All @@ -45,7 +43,8 @@
"text-summary"
],
"report-dir": "coverage",
"lines": 40
"check-coverage": true,
"all": true
},
"keywords": [],
"author": "",
Expand Down
2 changes: 1 addition & 1 deletion src/databases/migrations/20240520180022-create-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default {
defaultValue: false
},
status: {
type: new DataTypes.BOOLEAN,
type: new DataTypes.STRING(128),
allowNull: false,
defaultValue: true
},
Expand Down
44 changes: 44 additions & 0 deletions src/databases/migrations/20240523180022-create-sessions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { QueryInterface, DataTypes } from "sequelize";
export default {
up: async (queryInterface: QueryInterface) => {
await queryInterface.createTable("sessions", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
userId: {
type: new DataTypes.INTEGER,
allowNull: false
},
device: {
type: new DataTypes.STRING(280),
allowNull: true
},
token: {
type: new DataTypes.STRING(280),
allowNull: true
},
otp: {
type: new DataTypes.STRING(280),
allowNull: true
},
createdAt: {
field: "createdAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
field: "updatedAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
});
},

down: async (queryInterface: QueryInterface) => {
await queryInterface.dropTable("sessions");
}
};
73 changes: 73 additions & 0 deletions src/databases/models/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable require-jsdoc */
import { Model, DataTypes } from "sequelize";
import sequelizeConnection from "../config/db.config";
export interface SessionAttributes {
id: number;
userId: number;
device: string;
token: string;
otp: string;
createdAt: Date;
updatedAt: Date;
}

class Session extends Model<SessionAttributes> implements SessionAttributes {
declare id: number;
declare userId: number;
declare device: string;
declare token: string;
declare otp:string;
declare createdAt: Date;
declare updatedAt: Date;

static associate(models: any) {
Session.belongsTo(models.Users, { foreignKey: "userId",as: "user" });
}
}

Session.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
userId: {
type: new DataTypes.INTEGER,
allowNull: false
},
device: {
type: new DataTypes.STRING(280),
allowNull: true
},
token: {
type: new DataTypes.STRING(280),
allowNull: true
},
otp: {
type: new DataTypes.STRING(280),
allowNull: true
},
createdAt: {
field: "createdAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
field: "updatedAt",
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
},
{
sequelize: sequelizeConnection,
tableName: "sessions",
timestamps: true,
modelName:"Sessions"
}
);

export default Session;
5 changes: 4 additions & 1 deletion src/databases/models/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class Users extends Model<UsersAttributes, UsersCreationAttributes> implements U
declare updatedAt?: Date;

// Define any static methods or associations here
static associate(models: any) {
Users.hasOne(models.Tokens, { foreignKey: "userId",as: "token" });
}
}

Users.init(
Expand Down Expand Up @@ -117,7 +120,7 @@ Users.init(
defaultValue: false
},
status: {
type: new DataTypes.BOOLEAN,
type: new DataTypes.STRING(128),
allowNull: true,
defaultValue: true
},
Expand Down
10 changes: 8 additions & 2 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import jwt from "jsonwebtoken"
import jwt,{JwtPayload} from "jsonwebtoken"
import dotenv from "dotenv"

dotenv.config

const generateToken = (id: number) => {
return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: "1h" });
};
export { generateToken}

const decodeToken = (token: string) => {
return jwt.verify(token, process.env.JWT_SECRET) as JwtPayload
;
};

export { generateToken, decodeToken}
52 changes: 44 additions & 8 deletions src/middlewares/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import authRepositories from "../modules/auth/repository/authRepositories";
import { UsersAttributes } from "../databases/models/users";
import Joi from "joi";
import httpStatus from "http-status";
import { decodeToken } from "../helpers";

const validation = (schema: Joi.ObjectSchema | Joi.ArraySchema) => async (req: Request, res: Response, next: NextFunction) => {
try {
Expand All @@ -22,16 +23,51 @@ const validation = (schema: Joi.ObjectSchema | Joi.ArraySchema) => async (req: R

const isUserExist = async (req: Request, res: Response, next: NextFunction) => {
try {
const email:string = req.body.email
const userExists:UsersAttributes = await authRepositories.findUserByEmail(email);
if (userExists) {
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "User already exists." });
const userExists: UsersAttributes = await authRepositories.findUserByAttributes("email", req.body.email);
if (userExists && userExists.isVerified === true) {
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Account already exists." });
}
if (userExists && userExists.isVerified === false) {
return res.status(httpStatus.BAD_REQUEST).json({ status: httpStatus.BAD_REQUEST, message: "Account already exists. Please verify your account" });
}
return next();
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message })
}
return next();

}

const isAccountVerified = async (req: any, res: Response, next: NextFunction) => {
try {
let user: any = null;
if (req?.params?.token) {
const decodedToken = await decodeToken(req.params.token);
user = await authRepositories.findUserByAttributes("id", decodedToken.id);
}
if (req?.body?.email) {
user = await authRepositories.findUserByAttributes("email", req.body.email);
}

if (!user) {
return res.status(httpStatus.NOT_FOUND).json({ message: "Account not found." });
}

if (user.isVerified) {
return res.status(httpStatus.BAD_REQUEST).json({ message: "Account already verified." });
}

const session = await authRepositories.findSessionByUserId(user.id);
if (!session) {
return res.status(httpStatus.BAD_REQUEST).json({ message: "Invalid token." });
}

req.session = session;
req.user = user;
next();
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR , message: error.message})
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}

}

export {validation,isUserExist};

export { validation, isUserExist, isAccountVerified };
37 changes: 29 additions & 8 deletions src/modules/auth/controller/authControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,40 @@ import userRepositories from "../repository/authRepositories";
import { generateToken } from "../../../helpers";
import httpStatus from "http-status";
import { UsersAttributes } from "../../../databases/models/users";

import authRepositories from "../repository/authRepositories";
import { sendVerificationEmail } from "../../../services/sendEmail";

const registerUser = async (req: Request, res: Response): Promise<void> => {
try {
const register:UsersAttributes = await userRepositories.registerUser(req.body);
const register: UsersAttributes = await userRepositories.createUser(req.body);
const token: string = generateToken(register.id);
const data = {register, token};

res.status(httpStatus.OK).json({message:"Account created successfully. Please check email to verify account.", data})
}catch(error) {
const session = {userId: register.id, device: req.headers["user-device"] , token: token, otp: null };
await authRepositories.createSession(session);
await sendVerificationEmail(register.email, "Verification Email", `${process.env.SERVER_URL_PRO}/api/auth/verify-email/${token}`);
res.status(httpStatus.CREATED).json({ message: "Account created successfully. Please check email to verify account.", data: register })
} catch (error) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}


}

const sendVerifyEmail = async(req:any, res:Response) =>{
try {
await sendVerificationEmail(req.user.email, "Verification Email", `${process.env.SERVER_URL_PRO}/api/auth/verify-email/${req.session.token}`);
res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Verification email sent successfully." });
} catch (error) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}
}

const verifyEmail = async (req: any, res: Response) => {
try {
await authRepositories.destroySession(req.user.id, req.session.token)
await authRepositories.UpdateUserByAttributes("isVerified", true, "id", req.user.id);
res.status(httpStatus.OK).json({ status: httpStatus.OK, message: "Account verified successfully, now login." });
} catch (error) {
return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ status: httpStatus.INTERNAL_SERVER_ERROR, message: error.message });
}
}

export default { registerUser }
export default { registerUser, sendVerifyEmail, verifyEmail }
Loading

0 comments on commit 80e32f4

Please sign in to comment.