Skip to content

Commit 3c44bfb

Browse files
committed
feat: add user regiteration logic
- create project skeleton - create registration page - create registration form - create error page - add authorization error handling - add custom error handling
1 parent 76aee67 commit 3c44bfb

19 files changed

+687
-5
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
node_modules
1+
node_modules
2+
.env

app.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,33 @@ const path = require('path');
22
const express = require('express');
33
const app = express();
44
const indexRouter = require('./routes/indexRouter.js');
5+
const loginRouter = require('./routes/loginRouter.js');
6+
const registerRouter = require('./routes/registerRouter.js');
7+
const customErrors = require('./errors/CustomErrors.js');
8+
9+
// EXPRESS SETUP //
510

611
const assestsPath = path.join(__dirname, 'public');
712
app.use(express.static(assestsPath));
8-
913
app.use(express.urlencoded({ extended: true }));
10-
1114
app.set('view engine', 'ejs');
1215
app.set('views', path.join(__dirname, 'views'));
1316

17+
// ROUTERS //
18+
1419
app.use('/', indexRouter);
20+
app.use('/login', loginRouter);
21+
app.use('/register', registerRouter);
22+
app.use((req, res, next) => {
23+
const err = new customErrors.CustomNotFoundError('This page doesnot exist.');
24+
next(err);
25+
});
26+
app.use((err, req, res, next) => {
27+
console.log(err);
28+
res.status(err.status || 500).render('error', { err });
29+
});
30+
31+
// SERVER STARTUP //
1532

1633
app.listen(process.env.PORT || 3000, () => {
1734
console.log('Server started');

controllers/registerController.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const {body, validationResult} = require('express-validator');
2+
const db = require('../db/query.js');
3+
const asyncHandler = require('express-async-handler');
4+
const bcrypt = require('bcryptjs');
5+
const customErrors = require('../errors/CustomErrors.js');
6+
7+
// VALIDATIONS //
8+
9+
const validateUsername = [
10+
body('username').trim().notEmpty().withMessage('Username is required'),
11+
body('username').custom(async (value) => {
12+
const count = await db.filterUsername(value);
13+
if (count > 0) {
14+
Promise.reject();
15+
};
16+
Promise.resolve();
17+
}).withMessage('Username already exists')
18+
];
19+
20+
const validatePassword = [
21+
body('password')
22+
.isLength({ min: 8 })
23+
.withMessage('Password must be at least 8 characters long')
24+
.matches(/[A-Z]/)
25+
.withMessage('Password must contain at least one uppercase letter')
26+
.matches(/\d/)
27+
.withMessage('Password must contain at least one number'),
28+
body('password').custom((value, { req }) => {
29+
if (value !== req.body.verify_password) {
30+
throw new Error('Passwords do not match');
31+
}
32+
return true;
33+
})
34+
];
35+
36+
37+
const validateFirstName = [
38+
body('first_name')
39+
.trim()
40+
.notEmpty()
41+
.withMessage('First name is required'),
42+
body('first_name')
43+
.isAlpha()
44+
.withMessage('First name must contain only alphabetic characters')
45+
];
46+
47+
const validateLastName = [
48+
body('last_name')
49+
.trim()
50+
.notEmpty()
51+
.withMessage('Last name is required'),
52+
body('last_name')
53+
.isAlpha()
54+
.withMessage('Last name must contain only alphabetic characters')
55+
];
56+
57+
// CONTROLLER //
58+
59+
const registerController = {
60+
renderForm: asyncHandler((req, res) => {
61+
res.render('register');
62+
}),
63+
64+
registerUser: [
65+
validateUsername,
66+
validatePassword,
67+
validateFirstName,
68+
validateLastName,
69+
asyncHandler( async (req, res) => {
70+
const errors = validationResult(req);
71+
if (errors.isEmpty()) {
72+
const {username, password, first_name, last_name} = req.body;
73+
const hash = await bcrypt.hash(password, 10);
74+
await db.registerUser(username, hash, first_name, last_name);
75+
res.redirect('/login');
76+
} else {
77+
res.render('register', {errors: errors.array()});
78+
}
79+
})
80+
]
81+
};
82+
83+
module.exports = registerController;

db/pool.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
require('dotenv').config();
2+
const {Pool} = require('pg');
3+
4+
module.exports = new Pool({
5+
connectionString: process.env.DB_URL
6+
});

db/populatedb.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const {Client} = require('pg');
2+
3+
const SQL = `
4+
CREATE TABLE IF NOT EXISTS users (
5+
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
6+
username VARCHAR(255),
7+
password VARCHAR(255),
8+
first_name VARCHAR(255),
9+
last_name VARCHAR(255),
10+
is_member BOOLEAN DEFAULT FALSE
11+
);
12+
13+
CREATE TABLE IF NOT EXISTS posts (
14+
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
15+
user_id INTEGER,
16+
title VARCHAR(255),
17+
content TEXT,
18+
FOREIGN KEY (user_id) REFERENCES users(id)
19+
);
20+
`;
21+
22+
async function main() {
23+
const client = new Client({
24+
connectionString: process.argv[2],
25+
});
26+
await client.connect();
27+
await client.query(SQL);
28+
await client.end();
29+
console.log('Database populated');
30+
};
31+
32+
main();

db/query.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const pool = require('./pool.js');
2+
const asyncHandler = require('express-async-handler');
3+
4+
// DB QUERIES //
5+
6+
const filterUsername = asyncHandler(async (username) => {
7+
const isTableEmpty = async () => {
8+
const {rows} = await pool.query(
9+
'SELECT COUNT(*) FROM users;'
10+
);
11+
if( rows[0].count === 0){
12+
return true;
13+
}
14+
return false;
15+
}
16+
if(!isTableEmpty && username) {
17+
const {rows} = await pool.query(
18+
'SELECT COUNT(*) FROM users WHERE username = $1', [username]
19+
);
20+
return rows[0].count;
21+
} else {
22+
return 0;
23+
}
24+
});
25+
26+
const registerUser = asyncHandler(async (username, hash, first_name, last_name) => {
27+
console.log(hash);
28+
const result = await pool.query(
29+
'INSERT INTO users (username, password, first_name, last_name) VALUES ($1, $2, $3, $4)', [username, hash, first_name, last_name]
30+
);
31+
console.log(result);
32+
});
33+
34+
module.exports = {
35+
filterUsername,
36+
registerUser
37+
};

errors/CustomErrors.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class CustomNotFoundError extends Error {
2+
constructor(message){
3+
super(message);
4+
this.status = 404;
5+
this.name = "NotFoundError";
6+
}
7+
}
8+
9+
class HashingFailed extends Error {
10+
constructor(message){
11+
super(message);
12+
this.status = 500;
13+
this.name = "HashingFailed";
14+
}
15+
}
16+
17+
module.exports = {
18+
CustomNotFoundError,
19+
HashingFailed
20+
}

package-lock.json

+56
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
"author": "",
1111
"license": "ISC",
1212
"dependencies": {
13+
"bcryptjs": "^2.4.3",
14+
"dotenv": "^16.4.5",
1315
"ejs": "^3.1.10",
1416
"express": "^4.21.1",
17+
"express-async-handler": "^1.2.0",
1518
"express-session": "^1.18.1",
19+
"express-validator": "^7.2.0",
1620
"passport": "^0.7.0",
1721
"passport-local": "^1.0.0",
1822
"pg": "^8.13.1"

0 commit comments

Comments
 (0)