Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tman #18

Merged
merged 2 commits into from
Feb 1, 2024
Merged

Tman #18

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,30 @@
"name": "yonode-api-template",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
"type": "module",
"scripts": {
"start": "node ./src/app.js",
"dev": "nodemon ./src/app.js"
},
"license": "MIT",
"dependencies": {
"bcrypt": "^5.1.1",
"chalk": "5.3.0",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-rate-limit": "^6.10.0",
"helmet": "^7.0.0",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.3.0",
"morgan": "^1.10.0",
"reflect-metadata": "^0.2.1",
"typeorm": "^0.3.20"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// import the packages
import express from 'express';
import chalk from 'chalk';
import cookieParser from 'cookie-parser';
import { port } from './config/initial.config.js';
// import connectDB from './config/db.js';
// import usersRouter from './routes/users.js';

import cors from 'cors';
import { rateLimit } from 'express-rate-limit';
import helmet from 'helmet';
import morgan from 'morgan';
import connectDB from './config/db.config.js';
import userRouter from './routes/user.router.js';

const PORT = port;

const app = express();

app.use(morgan("dev"));

var whitelist = ['http://localhost:8000'];

var corsOptionsDelegate = function (req, callback) {
var corsOptions;
if (whitelist.indexOf(req.header('Origin')) !== -1) {
corsOptions = { origin: true };
} else {
corsOptions = { origin: false };
}
callback(null, corsOptions);
};

app.use(cors(corsOptionsDelegate));
app.use(express.json());
app.use(helmet());
app.use(cookieParser());

const apiRateLimit = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: 'Rate limit exceeded'
});

app.use(apiRateLimit);


// route management
app.use('/api/v1/users', userRouter);


// database connection
connectDB();

app.listen(PORT, () => {
console.log(process.env.PORT);
console.log(`${chalk.green.bold('Server')} is listening on port ${chalk.green.bold(PORT)} 🚀`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "reflect-metadata";
import { DataSource } from "typeorm";
import { dbURL } from "./initial.config.js";
import chalk from "chalk";

const AppDataSource = new DataSource({
type: "mongodb",
url: dbURL,
database: "yonode",
entities: ["./src/entity/**/*.js"],
synchronize: true,
useUnifiedTopology: true,
useNewUrlParser: true,
});

AppDataSource.initialize()
.then(() => {
console.log(`${chalk.green.bold("Connected")} to the database ✅`);
})
.catch((err) => {
console.log(`${chalk.red.bold("Error")} connecting to database`, err);
});

export default AppDataSource;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import dotenv from 'dotenv';

dotenv.config();

export const port = process.env.PORT || 8000;
export const dbURL = process.env.DATABASE_URL;
export const JWT_Secret = process.env.JWT_SECRET_KEY;
export const Web_Url = process.env.WEB_URL

// email
export const emailService = process.env.SERVICE
export const emailUser = process.env.USER
export const emailPass = process.env.PASS
export const emailFrom = process.env.FROM
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import jwt from 'jsonwebtoken';
import { JWT_SECRET_KEY, Web_Url } from "../config/initial.config.js";
import User from "../models/User.model.js";
import sendVerificationEmail from "../utils/emails/sendVerificationToken.js";
import { generateVerificationToken } from "../util/generations.js";

export const registerUser = async (req, res) => {

try {
const { name, username, password, email } = req.body;

const isUserExists = await User.findOne({
$or: [
{ email: email.toLowerCase() },
{ username: username.toLowerCase() }
]
});

if (isUserExists) {

if (isUserExists.email === email.toLowerCase()) {
return res.status(400).send("email already exists");
}
if (isUserExists.username === username.toLowerCase()) {
return res.status(400).send("username already exists");
}
}

const verificationToken = generateVerificationToken();

const tokeExpireDate = new Date();

tokeExpireDate.setHours(tokeExpireDate.getHours() + 24);

console.log("tokeExpireDate", tokeExpireDate);

const userInfo = new User({
name,
username,
email,
password,
token: verificationToken,
expireDate: tokeExpireDate
});

await userInfo.save();

userInfo.password = undefined;

const verificationLink = `${Web_Url}/users/verify-user?token=${verificationToken}&userId=${userInfo._id}`;

sendVerificationEmail(email, verificationLink);

res.status(201).send(userInfo);

} catch (err) {
console.log("error at user registerUser", err);
res.status(400).send(err.message);
}

};

export const verifyUser = async (req, res) => {

try {

const { token, userId: _id } = req.query;

const user = await User.findOne({ _id, token });

if (!user) {
return res.status(400).send("Invalid Token...");
}

const expirationTime = user.expireDate;

if (!expirationTime || expirationTime < new Date()) {
return res.status(400).send("Token has expired");
}

const maxAge = new Date();

maxAge.setHours(maxAge.getHours() - 24);

if (expirationTime < maxAge) {
return res.status(400).send("Token has expired");
}

user.isEmailConfirmed = true;
user.token = undefined;

user.expireDate = undefined;

await user.save();

res.status(200).send({ status: true, message: "Token has been verified." });

} catch (err) {
console.log("error at user verification", err);
res.status(400).send(err.message);
}

};

export const loginUser = async (req, res) => {

try {

const { username, email, password } = req.body;

const isUserExists = await User.findOne({
$or: [
{ email: email?.toLowerCase() },
{ username: username?.toLowerCase() }
]
}).select("+password");

if (!isUserExists.isEmailConfirmed) {
return res.status(401).send("Confirm your email first");
}

if (!isUserExists) {
return res.status(400).send("Invalid username or password");
}

const validPassword = await isUserExists.comparePassword(password);


if (!validPassword) {
return res.status(400).send("Invalid username or password");
}
const expiresIn = 7 * 24 * 60 * 60;

const token = jwt.sign({ _id: isUserExists._id }, JWT_SECRET_KEY, { expiresIn });

res.cookie("token", token, {
httpOnly: true,
secure: false,
maxAge: expiresIn * 1000
});

isUserExists.password = undefined;

res.status(200).send({ ...isUserExists.toJSON(), expiresIn });

} catch (err) {
console.log("first login failed", err);
res.status(400).send(err.message);
}

};

export const logoutUser = (req, res) => {

try {

res.clearCookie("token");

res.send("Logout successfully");

} catch (error) {
console.log("error on logout", error);
}

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Entity,
ObjectIdColumn,
Column,
BeforeInsert,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
import { IsEmail, Matches } from "class-validator";
import bcrypt from "bcrypt";

@Entity()
export class User {
@ObjectIdColumn()
id;

@Column()
name;

@Column({
unique: true,
})
@Matches(/^[a-zA-Z0-9_.-]*$/, {
message: "username should not contain special characters except _. and -",
})
username;

@Column({
select: false,
})
password;

@Column({
unique: true,
})
@IsEmail({}, { message: "Invalid email" })
email;

@CreateDateColumn()
createdAt;

@UpdateDateColumn()
updatedAt;

@BeforeInsert()
async hashPassword() {
this.password = await bcrypt.hash(this.password, 10);
}

async comparePassword(attempt) {
return await bcrypt.compare(attempt, this.password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import express from "express";
import { registerUser } from "../controllers/user.controller.js";

const userRouter = express.Router();

userRouter.post("/register-user", registerUser);

export default userRouter;
Loading