done
This commit is contained in:
commit
ecbbf046ce
20 changed files with 2475 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
0
.dockerignore
Normal file
0
.dockerignore
Normal file
6
.env.example
Normal file
6
.env.example
Normal file
|
@ -0,0 +1,6 @@
|
|||
DB_DATABASE=payplus_db
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=password
|
||||
JWT_SECRET=your_secret_key
|
||||
DATABASE_URL="URI"
|
||||
SENDGRID_API_KEY="API_KEY"
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
dist
|
||||
.env
|
||||
node_modules
|
||||
init.js
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
|
@ -0,0 +1,13 @@
|
|||
FROM node:14-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "start"]
|
251
README.md
Normal file
251
README.md
Normal file
|
@ -0,0 +1,251 @@
|
|||
# Ecomm Backend
|
||||
|
||||
This repository contains the backend implementation for the Ecomm e-commerce application.
|
||||
|
||||
## Description
|
||||
|
||||
This is a simple e-commerce backend application built with Node.js, TypeScript, Express.js, MongoDB, and Docker. It provides API endpoints to manage users, products and cart of user.
|
||||
|
||||
## My Approach
|
||||
|
||||
Creating a simple Express.js application with TypeScript.
|
||||
added the required dependencies and dev dependencies.
|
||||
added best ORM to connect with MongoDB and Node.js.
|
||||
created the required models and controllers for the application.
|
||||
added the required routes for the application.
|
||||
added the required environment variables for the application.
|
||||
added the required middleware for the application.
|
||||
added the required error handling for the application.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Technologies Used]
|
||||
- [How to Run]
|
||||
- [API Documentation]
|
||||
- [Users]
|
||||
- [Create a New User]
|
||||
- [Login]
|
||||
- [Products]
|
||||
- [Get All Products]
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- Node.js
|
||||
- TypeScript
|
||||
- Express.js
|
||||
- MongoDB
|
||||
- Mongoose ORM
|
||||
- Docker (docker-compose)
|
||||
- bcrypt
|
||||
- JWT
|
||||
- deep-email-validator
|
||||
|
||||
## How to Run
|
||||
|
||||
To run the Ecomm backend application, follow these steps:
|
||||
|
||||
1. Clone the repository.
|
||||
2. Ensure that you have Docker and Docker Compose installed.
|
||||
3. Implement the required environment variables by creating an `.env` file.
|
||||
4. Run the following command in the root directory:
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
The application will be running on port 3000, and the database will be running on port 27017.
|
||||
|
||||
API Documentation
|
||||
# Users
|
||||
Create a New User - POST /users
|
||||
Creates a new user.
|
||||
|
||||
Request Body
|
||||
|
||||
```
|
||||
{
|
||||
"name": "string",
|
||||
"email": "string",
|
||||
"password": "string"
|
||||
}
|
||||
```
|
||||
Response Body
|
||||
status code 200
|
||||
```
|
||||
{
|
||||
message:
|
||||
"User created successfully"
|
||||
}
|
||||
```
|
||||
status code 400
|
||||
```
|
||||
{
|
||||
message:
|
||||
"User already exists"
|
||||
}
|
||||
```
|
||||
status code 500
|
||||
```
|
||||
{
|
||||
message:
|
||||
"Internal server error"
|
||||
}
|
||||
```
|
||||
Login - POST /users/login
|
||||
Logs in a user.
|
||||
|
||||
Request Body
|
||||
```
|
||||
{
|
||||
"email": "string",
|
||||
"password": "string"
|
||||
}
|
||||
```
|
||||
Response Body
|
||||
status code 200
|
||||
```
|
||||
{
|
||||
access-token:
|
||||
"TOKEN"
|
||||
}
|
||||
```
|
||||
|
||||
# Products
|
||||
Get All Products - GET /products
|
||||
Gets all products.
|
||||
|
||||
Parameters
|
||||
```
|
||||
page: number(default: 0)
|
||||
limit: number(default: 50)
|
||||
```
|
||||
Response Body
|
||||
status code 200
|
||||
```
|
||||
{
|
||||
products: [
|
||||
{
|
||||
_id: "string",
|
||||
name: "string",
|
||||
description: "string",
|
||||
price: number,
|
||||
image: "string",
|
||||
createdAt: "string",
|
||||
updatedAt: "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
status code 404
|
||||
```
|
||||
{
|
||||
message:
|
||||
"Product not found."
|
||||
}
|
||||
````
|
||||
|
||||
# Cart
|
||||
|
||||
Add Product to Cart - POST /cart
|
||||
Adds a product to cart.
|
||||
(authentication required)
|
||||
|
||||
Request Body
|
||||
```
|
||||
{
|
||||
"productId": "string"
|
||||
}
|
||||
```
|
||||
Response Body
|
||||
status code 200
|
||||
```
|
||||
{
|
||||
Cart :{
|
||||
"{ProductId}": Quantity
|
||||
}
|
||||
}
|
||||
```
|
||||
status code 500
|
||||
```
|
||||
{
|
||||
message:
|
||||
"An error occurred while adding the product to the cart."
|
||||
}
|
||||
```
|
||||
|
||||
List Cart Items - GET /cart
|
||||
Lists all cart items.
|
||||
(authentication required)
|
||||
|
||||
Response Body
|
||||
status code 200
|
||||
```
|
||||
{
|
||||
Cart :{
|
||||
"{ProductId}": Quantity
|
||||
}
|
||||
}
|
||||
```
|
||||
status 404
|
||||
```
|
||||
{
|
||||
message:
|
||||
"Cart not found."
|
||||
}
|
||||
```
|
||||
status code 500
|
||||
```
|
||||
{
|
||||
message:
|
||||
"An error occurred while listing the cart."
|
||||
}
|
||||
```
|
||||
|
||||
checkout - POST /cart/checkout
|
||||
checkout the cart.
|
||||
(authentication required)
|
||||
|
||||
status 200
|
||||
```
|
||||
{
|
||||
order: {
|
||||
{productId}: quantity
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
status 404
|
||||
```
|
||||
{
|
||||
message:
|
||||
"Cart not found."
|
||||
}
|
||||
```
|
||||
|
||||
# Database Schema
|
||||
## User
|
||||
```
|
||||
{
|
||||
name: string,
|
||||
email: string,
|
||||
password: string,
|
||||
cart: {
|
||||
productId: number
|
||||
}
|
||||
}
|
||||
```
|
||||
## Product
|
||||
```
|
||||
{
|
||||
name: string,
|
||||
description: string,
|
||||
price: number
|
||||
userId: string
|
||||
}
|
||||
```
|
||||
## Cart
|
||||
```
|
||||
{
|
||||
userId: string,
|
||||
products: Map<string, number>
|
||||
}
|
||||
```
|
19
docker-compose.yaml
Normal file
19
docker-compose.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
version: '3'
|
||||
services:
|
||||
mongodb:
|
||||
image: arm64v8/mongo:4.0
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
volumes:
|
||||
- payplus_volume:/data/db
|
||||
- ./init-scripts/init.js:/docker-entrypoint-initdb.d/mongo-init.js
|
||||
environment:
|
||||
- MONGO_INITDB_DATABASE=your-database-name
|
||||
- MONGO_INITDB_ROOT_USERNAME=your-username
|
||||
- MONGO_INITDB_ROOT_PASSWORD=your-password
|
||||
platform: linux/arm64/v8
|
||||
expose:
|
||||
- 27017
|
||||
volumes:
|
||||
payplus_volume:
|
1572
package-lock.json
generated
Normal file
1572
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
33
package.json
Normal file
33
package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "payplus",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon dist/index.js",
|
||||
"start": "node dist/index.js",
|
||||
"build": "tsc -p ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"deep-email-validator": "^0.1.21",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"mongodb": "^5.6.0",
|
||||
"mongoose": "^7.3.1",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jsonwebtoken": "^9.0.2",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"nodemon": "^2.0.22"
|
||||
}
|
||||
}
|
156
src/controllers/UserController.ts
Normal file
156
src/controllers/UserController.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import { createUser, loginUser } from '../models/userModel';
|
||||
import { checkIn, checkOut, takeBreak, returnFromBreak, getWork } from '../models/workModel';
|
||||
import { ApiError } from '../utils/ApiError';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { clearJwtCookie, setJwtCookie } from '../middlewares/checkAuth';
|
||||
|
||||
|
||||
const getUserWork = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { userId } = req.body;
|
||||
const work = await getWork(userId);
|
||||
if(work instanceof ApiError) {
|
||||
return next(work);
|
||||
}
|
||||
res.status(200).json(work);
|
||||
} catch {
|
||||
const error = new ApiError('Error during user creation');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
const create = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { email, password, firstName, lastName } = req.body;
|
||||
const user = await createUser({
|
||||
email,
|
||||
password,
|
||||
firstName,
|
||||
lastName
|
||||
});
|
||||
if(user instanceof ApiError) {
|
||||
return next(user);
|
||||
}
|
||||
res.status(201).json(user);
|
||||
} catch {
|
||||
const error = new ApiError('Error during user creation');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
const login = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
const user: any = await loginUser({
|
||||
email,
|
||||
password
|
||||
});
|
||||
if(user instanceof ApiError) {
|
||||
console.log("Error in login")
|
||||
return res.status(user.statusCode).json({ error: user.message });
|
||||
}
|
||||
const payload = {
|
||||
userId: user._id
|
||||
}
|
||||
// Generate a JWT
|
||||
const token = jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: '1d' });
|
||||
setJwtCookie(res, token);
|
||||
// Send the JWT as the response
|
||||
res.status(200).json({
|
||||
token
|
||||
});
|
||||
} catch {
|
||||
const error = new ApiError('Error during user login');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const checkin = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { userId } = req.body;
|
||||
const work = await checkIn(userId);
|
||||
res.status(201).json(work);
|
||||
} catch {
|
||||
const error = new ApiError('Error during user checkin');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
const takeBreakHandler = async (req: Request, res: Response, next: NextFunction) => {
|
||||
console.log("takeBreakHandler")
|
||||
try {
|
||||
const { userId } = req.body;
|
||||
const work = await takeBreak(userId);
|
||||
if(work instanceof ApiError) {
|
||||
return next(work);
|
||||
}
|
||||
res.status(201).json(work);
|
||||
} catch {
|
||||
const error = new ApiError('Error during user take break');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
const returnFromBreakHandler = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { userId } = req.body;
|
||||
const work = await returnFromBreak(userId);
|
||||
if(work instanceof ApiError) {
|
||||
return next(work);
|
||||
}
|
||||
res.status(201).json(work);
|
||||
} catch {
|
||||
const error = new ApiError('Error during user return from break');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
const checkout = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { userId } = req.body;
|
||||
const work = await checkOut(userId);
|
||||
res.status(201).json(work);
|
||||
} catch {
|
||||
const error = new ApiError('Error during user checkout');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
clearJwtCookie(res);
|
||||
res.status(200).json({ message: 'Logout successful' });
|
||||
} catch {
|
||||
const error = new ApiError('Error during user logout');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
create,
|
||||
logout,
|
||||
login,
|
||||
checkin,
|
||||
checkout,
|
||||
takeBreakHandler,
|
||||
returnFromBreakHandler,
|
||||
getUserWork
|
||||
}
|
50
src/index.ts
Normal file
50
src/index.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import express from 'express';
|
||||
import mongoose from 'mongoose';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
import userRouter from './routes/userRouter';
|
||||
|
||||
|
||||
import { ApiError } from './utils/ApiError';
|
||||
|
||||
|
||||
const env = require('dotenv').config().parsed;
|
||||
|
||||
const app = express();
|
||||
const PORT = env.PORT || 3000;
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cookieParser())
|
||||
|
||||
|
||||
// Connect to MongoDB using Mongoose
|
||||
mongoose.connect(env.DATABASE_URL);
|
||||
|
||||
const db = mongoose.connection;
|
||||
|
||||
// Check for DB connection
|
||||
db.on('error', () => {
|
||||
console.error.bind(console, 'MongoDB connection error:')
|
||||
process.exit(1);
|
||||
});
|
||||
db.once('open', () => {
|
||||
console.log('Connected to MongoDB');
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/users', userRouter);
|
||||
// Routes
|
||||
|
||||
|
||||
app.all('*', (req, res, next) => {
|
||||
const error = new ApiError('Are you lost?');
|
||||
error.statusCode = 404;
|
||||
error.status = 'fail';
|
||||
next(error)
|
||||
});
|
||||
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server started on port ${PORT}`);
|
||||
});
|
33
src/middlewares/checkAuth.ts
Normal file
33
src/middlewares/checkAuth.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
// Middleware function to authenticate requests
|
||||
export function authenticateToken(req: AuthenticatedRequest, res: Response, next: NextFunction) {
|
||||
|
||||
const token = req.cookies.access_token;
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET as string, (err: any, decoded: { userId: any; }) => {
|
||||
if (err) {
|
||||
return res.status(401).json({ error: 'In Valid Token' });
|
||||
}
|
||||
req.body.userId = decoded.userId;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Set JWT as cookie in the response
|
||||
export function setJwtCookie(res: Response, token: string) {
|
||||
res.cookie('access_token', token, { httpOnly: true });
|
||||
}
|
||||
|
||||
// Clear JWT cookie in the response
|
||||
export function clearJwtCookie(res: Response) {
|
||||
res.clearCookie('access_token');
|
||||
}
|
41
src/middlewares/usersResourceValidation.ts
Normal file
41
src/middlewares/usersResourceValidation.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import { ApiError } from '../utils/ApiError';
|
||||
import validate from 'deep-email-validator';
|
||||
|
||||
const isValidLogin = async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
const user = req.body;
|
||||
if (!user.email || !user.password) {
|
||||
const error = new ApiError(`${!user.email ? 'email' : 'password'} is required`);
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return next(error);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const isValidCreateUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
const user = req.body;
|
||||
if (!user.email || !user.password || !user.firstName || !user.lastName ) {
|
||||
const error = new ApiError(`${!user.email ? 'email' : !user.password ? 'password' : !user.firstName ? 'firstName' : 'lastName'} is required`);
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const { valid, reason } = await validate(user.email);
|
||||
if (!valid) {
|
||||
const error = new ApiError(`Invalid email: ${reason}`);
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return next(error);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
export {
|
||||
isValidLogin,
|
||||
isValidCreateUser
|
||||
}
|
79
src/models/userModel.ts
Normal file
79
src/models/userModel.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { User } from "../schemas/userSchema";
|
||||
import { ApiError } from "../utils/ApiError";
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const createUser = async (user: any) => {
|
||||
|
||||
const userExists = await User.exists({ email: user.email });
|
||||
if (userExists) {
|
||||
const error = new ApiError('User already exists, Try login :)');
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password = await bcrypt.hash(user.password, salt);
|
||||
|
||||
const newUser = new User(user);
|
||||
try {
|
||||
await newUser.save();
|
||||
return {
|
||||
email: newUser.email,
|
||||
};
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
const loginUser = async (user: any) => {
|
||||
const { email, password } = user;
|
||||
const userExists = await User.findOne({ email });
|
||||
if (!userExists) {
|
||||
const error = new ApiError('Invalid email or password');
|
||||
error.statusCode = 404;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(password, userExists.password);
|
||||
if(!isMatch) {
|
||||
const error = new ApiError('Invalid email or password');
|
||||
error.statusCode = 404;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
return userExists;
|
||||
}
|
||||
|
||||
const getAllUsers = async () => {
|
||||
try {
|
||||
const users = await User.find();
|
||||
return users;
|
||||
} catch {
|
||||
const error = new ApiError('Error during fetching users');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
const deleteUser = async (id: string) => {
|
||||
try {
|
||||
const user = await User.findByIdAndDelete(id);
|
||||
return user;
|
||||
} catch {
|
||||
const error = new ApiError('Error during user deletion');
|
||||
error.statusCode = 500;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
createUser,
|
||||
loginUser,
|
||||
getAllUsers,
|
||||
deleteUser
|
||||
}
|
117
src/models/workModel.ts
Normal file
117
src/models/workModel.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { Work } from "../schemas/workSchema";
|
||||
import { ApiError } from "../utils/ApiError";
|
||||
|
||||
const checkIn = async (userId: string) => {
|
||||
const startTime = new Date();
|
||||
const work = await Work.findOne({ userId, endTime: null });
|
||||
if (work) {
|
||||
return work;
|
||||
}
|
||||
const newWork = new Work({ userId, startTime, endTime: null, isBreak: false, breakStartTime: null, breakEndTime: null });
|
||||
return await newWork.save();
|
||||
}
|
||||
|
||||
const checkOut = async (userId: string) => {
|
||||
const work = await Work.findOne({ userId, endTime: null });
|
||||
if (!work) {
|
||||
const error = new ApiError('User is not checked in');
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
const endTime = new Date();
|
||||
return await Work.findByIdAndUpdate(work._id, { endTime }, { new: true });
|
||||
}
|
||||
|
||||
const takeBreak = async (userId: string) => {
|
||||
try {
|
||||
const work = await Work.findOne({ userId, endTime: null });
|
||||
if (!work) {
|
||||
const error = new ApiError('Work not found');
|
||||
error.statusCode = 404;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
|
||||
if (work.isBreak) {
|
||||
const error = new ApiError('Already on a break');
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
|
||||
work.isBreak = true;
|
||||
work.breakStartTime = new Date(); // Set breakStartTime to present 0
|
||||
await work.save();
|
||||
return work;
|
||||
} catch (error) {
|
||||
const apiError = new ApiError('An error occurred');
|
||||
apiError.statusCode = 500;
|
||||
apiError.status = 'error';
|
||||
return apiError;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const returnFromBreak = async (userId: string) => {
|
||||
try {
|
||||
const work = await Work.findOne({ userId, endTime: null });
|
||||
if (!work) {
|
||||
const error = new ApiError('Work not found');
|
||||
error.statusCode = 404;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!work.isBreak) {
|
||||
const error = new ApiError('Not on a break');
|
||||
error.statusCode = 400;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
|
||||
// Update breakDuration and totalDuration
|
||||
const breakEndTime = new Date();
|
||||
const currentTimestamp = breakEndTime.getTime();
|
||||
const breakDurationInMillis = currentTimestamp - work.breakStartTime.getTime();
|
||||
const breakDurationInHours = Number((breakDurationInMillis / 3600000).toFixed(3));
|
||||
work.breakEndTime = breakEndTime;
|
||||
work.breakDuration = breakDurationInHours + Number(work.breakDuration);
|
||||
work.isBreak = false;
|
||||
work.updatedAt = new Date();
|
||||
|
||||
await work.save();
|
||||
return work;
|
||||
} catch (error) {
|
||||
const apiError = new ApiError('An error occurred');
|
||||
apiError.statusCode = 500;
|
||||
apiError.status = 'error';
|
||||
return apiError;
|
||||
}
|
||||
}
|
||||
|
||||
const getWork = async (userId: string) => {
|
||||
try {
|
||||
const work = await Work.find({ userId });
|
||||
if (!work) {
|
||||
const error = new ApiError('Work not found');
|
||||
error.statusCode = 404;
|
||||
error.status = 'fail';
|
||||
return error;
|
||||
}
|
||||
return work;
|
||||
} catch (error) {
|
||||
const apiError = new ApiError('An error occurred');
|
||||
apiError.statusCode = 500;
|
||||
apiError.status = 'error';
|
||||
return apiError;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
checkIn,
|
||||
checkOut,
|
||||
takeBreak,
|
||||
returnFromBreak,
|
||||
getWork
|
||||
}
|
21
src/routes/userRouter.ts
Normal file
21
src/routes/userRouter.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import express from 'express';
|
||||
import { create, login, logout, checkin, checkout, takeBreakHandler, returnFromBreakHandler, getUserWork } from '../controllers/UserController';
|
||||
import { isValidCreateUser, isValidLogin } from '../middlewares/usersResourceValidation';
|
||||
import { authenticateToken } from '../middlewares/checkAuth';
|
||||
|
||||
const userRouter = express.Router();
|
||||
|
||||
userRouter.post('/', isValidCreateUser, create);
|
||||
userRouter.post('/login', isValidLogin, login);
|
||||
userRouter.post('/logout', logout);
|
||||
|
||||
userRouter.post('/checkin', authenticateToken, checkin);
|
||||
userRouter.post('/checkout', authenticateToken, checkout);
|
||||
|
||||
userRouter.post('/breaktime', authenticateToken, takeBreakHandler);
|
||||
userRouter.post('/returnfrombreak', authenticateToken, returnFromBreakHandler);
|
||||
|
||||
userRouter.get('/work', authenticateToken, getUserWork);
|
||||
|
||||
|
||||
export default userRouter;
|
24
src/schemas/userSchema.ts
Normal file
24
src/schemas/userSchema.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
interface IUser extends Document {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
const UserSchema: Schema = new Schema({
|
||||
email: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true },
|
||||
firstName: { type: String, required: true },
|
||||
lastName: { type: String, required: true },
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now },
|
||||
});
|
||||
|
||||
const User = mongoose.model<IUser>('User', UserSchema);
|
||||
|
||||
export {
|
||||
User,
|
||||
IUser
|
||||
}
|
32
src/schemas/workSchema.ts
Normal file
32
src/schemas/workSchema.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
interface IWork extends Document {
|
||||
userId: mongoose.Types.ObjectId;
|
||||
startTime: Date;
|
||||
endTime?: Date;
|
||||
isBreak: boolean;
|
||||
breakStartTime?: Date;
|
||||
breakEndTime?: Date;
|
||||
breakDuration?: Number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const workSchema: Schema = new Schema({
|
||||
userId: { type: mongoose.Types.ObjectId, ref: 'User', required: true },
|
||||
startTime: { type: Date, required: true },
|
||||
endTime: { type: Date },
|
||||
isBreak: { type: Boolean, default: false },
|
||||
breakStartTime: { type: Date },
|
||||
breakEndTime: { type: Date },
|
||||
breakDuration: { type: Number, default: 0 },
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now },
|
||||
});
|
||||
|
||||
const Work = mongoose.model<IWork>('Work', workSchema);
|
||||
|
||||
export {
|
||||
Work,
|
||||
IWork
|
||||
}
|
10
src/utils/ApiError.ts
Normal file
10
src/utils/ApiError.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
class ApiError extends Error {
|
||||
statusCode: number;
|
||||
status: string;
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export { ApiError };
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"exclude": ["__tests__"],
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"noImplicitAny": true,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue