Skip to content

Commit

Permalink
🔀 Merge PR #1 from nmdra/user-login-feature
Browse files Browse the repository at this point in the history
User login feature
  • Loading branch information
nmdra authored Jul 10, 2024
2 parents aecfd5b + c1a1c97 commit d75d3d2
Show file tree
Hide file tree
Showing 14 changed files with 5,296 additions and 1,056 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/Eslint-Backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
with:
node-version: '22'
- name: Install Node Dependencies
run: npm ci
run: npm install
- name: Save Code Linting Report JSON
run: npm run lint -- --output-file eslint_report.json --format json .
continue-on-error: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/Eslint-Frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
node-version: '22'
- name: Install Node Dependencies
run: npm ci
run: npm install
- name: Save Code Linting Report JSON
run: npm run lint -- --output-file eslint_report.json --format json .
continue-on-error: true
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/Jest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Jest-Testing
# Controls when the workflow will run
run-name: Run Testing

on:
# Triggers the workflow on push or pull request events but only for the "main" branch
workflow_call:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
jest:
# The type of runner that the job will run on
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Node Dependencies
run: npm ci
- name: Run Jest Testing
run: npm run test -- --ci --json --coverage --testLocationInResults --outputFile=jestReport.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Lint Checks
name: Checks(Eslint+Jest)

on:
workflow_dispatch:
pull_request:
branches:
- master
Expand All @@ -23,4 +24,8 @@ jobs:

Lint-Frontend:
name: Lint frontend
uses: ./.github/workflows/Eslint-Frontend.yml
uses: ./.github/workflows/Eslint-Frontend.yml

Lint-Test:
name: Jest Testing
uses: ./.github/workflows/Jest.yml
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PORT=<PORT_NUMBER>
NODE_ENV=dev
NODE_ENV=development
MONGODB_URI=<MongoDB_URI>
JWT_SECRET=<JWT_SECRET>
4 changes: 2 additions & 2 deletions backend/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import globals from "globals";
import pluginJs from "@eslint/js";

import jest from 'eslint-plugin-jest'

export default [
{languageOptions: { globals: {...globals.browser, ...globals.node} }},
pluginJs.configs.recommended,
pluginJs.configs.recommended,jest.configs['flat/recommended'],
];
13 changes: 2 additions & 11 deletions backend/middlewares/errorMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
const notFound = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`)
res.status(404)
next(error)
}

const errorHandler = (err, req, res, next) => {
const errorHandler = (err, _req, res, next) => {
let statusCode = res.statusCode === 200 ? 500 : res.statusCode
let message = err.message

// NOTE: checking for invalid ObjectId moved to it's own middleware
// See README for further info.

res.status(statusCode).json({
message: message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack,
Expand All @@ -19,4 +10,4 @@ const errorHandler = (err, req, res, next) => {
next()
}

export { notFound, errorHandler }
export { errorHandler }
78 changes: 78 additions & 0 deletions backend/models/userModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'

const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
lowercase: true,
required: [true, 'Email not Provided`'],
unique: [true, 'Email already exists'],
validate: {
validator: function (v) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
},
message: '{VALUE} is not a valid email!',
},
},
role: {
type: String,
lowercase: true,
enum: ['regular', 'vip'],
default: 'regular',
required: [true, 'Please specify user role'],
},
password: {
type: String,
required: [true, 'Password not provided'],
},
defaultAddress: {
address: { type: String, required: false, default: '' },
city: { type: String, required: false, default: '' },
postalCode: { type: Number, required: false, default: '' },
country: { type: String, required: false, default: '' },
},
contactNumber: {
type: String,
required: true,
validate: {
validator: function (v) {
return /^[0]{1}[7]{1}[01245678]{1}[0-9]{7}$/.test(v);
},
message: '{VALUE} is not a valid contact number!',
},
},
},

{
timestamps: true,
}
)

userSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
return next()
}

try {
const salt = await bcrypt.genSalt(10)
this.password = await bcrypt.hash(this.password, salt)
} catch (error) {
next(error)
}
})

userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}

const User = mongoose.model('User', userSchema)

export default User

// TODO: Joi Validation schema support
// TODO: Addresses and more
74 changes: 74 additions & 0 deletions backend/models/userModel.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import mongoose from 'mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import User from './userModel'; // Adjust the path as needed

let mongoServer;

beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
await mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });
});

afterAll(async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
await mongoServer.stop();
});

describe('User Model', () => {
it('should create a new user', async () => {
const user = new User({
name: 'Test User',
email: 'testuser@example.com',
role: 'regular',
password: 'password123',
contactNumber: '0712345678',
});

await user.save();

expect(user.name).toBe('Test User');
expect(user.email).toBe('testuser@example.com');
expect(user.role).toBe('regular');
expect(user.contactNumber).toBe('0712345678');
});

it('should hash the password before saving', async () => {
const user = new User({
name: 'Test User',
email: 'testuser2@example.com',
role: 'regular',
password: 'password123',
contactNumber: '0712345678',
});

await user.save();

expect(user.password).not.toBe('password123');
});

it('should validate email format', async () => {
const user = new User({
name: 'Test User',
email: 'invalid_email',
role: 'regular',
password: 'password123',
contactNumber: '0712345678',
});

await expect(user.save()).rejects.toThrow('invalid_email is not a valid email!');
});

it('should validate contact number format', async () => {
const user = new User({
name: 'Test User',
email: 'testuser@example.com',
role: 'regular',
password: 'password123',
contactNumber: '1234567890',
});

await expect(user.save()).rejects.toThrow('1234567890 is not a valid contact number!');
});
});
Loading

0 comments on commit d75d3d2

Please sign in to comment.