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

Routes #23

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
1,141 changes: 795 additions & 346 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
"author": "",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.18.2",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"express-favicon": "^2.0.4",
"mongoose": "^7.0.1",
"morgan": "^1.10.0"
"http-status-codes": "^2.3.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.8.2",
"morgan": "^1.10.0",
"punycode": "^2.3.1"
},
"devDependencies": {
"nodemon": "^2.0.21"
Expand Down
14 changes: 13 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ const app = express();
const cors = require('cors')
const favicon = require('express-favicon');
const logger = require('morgan');
const notFoundMiddleware = require('./middleware/not_found');
const errorHandleMiddleware = require('./middleware/error_handler');


const mainRouter = require('./routes/mainRouter.js');
const userRouter = require('./routes/user');
const itineraryRouter = require('./routes/itineraryRouter');

// middleware
app.use(cors());
Expand All @@ -14,7 +19,14 @@ app.use(logger('dev'));
app.use(express.static('public'))
app.use(favicon(__dirname + '/public/favicon.ico'));


// routes
app.use('/api/v1', mainRouter);
app.use('/api/v1/user', userRouter);
app.use('/api/v1/itinerary', itineraryRouter);

// error handling middleware
app.use(notFoundMiddleware);
app.use(errorHandleMiddleware);

module.exports = app;
module.exports = app;
124 changes: 124 additions & 0 deletions src/controllers/itineraryItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const ItineraryItem = require('../models/itineraryItem');
const { StatusCodes } = require('http-status-codes');
const { BadRequestError } = require('../errors/bad_request');
const { NotFoundError } = require('../errors/not_found');
const { default: mongoose } = require('mongoose');

const getAllItineraryItems = async (req, res) => {
const itineraryItems = await ItineraryItem.find({ createdBy: req.userId }).sort('createdAt');
res.status(StatusCodes.OK).json({ itineraryItems, count: itineraryItems.length });
}

const getSingleItineraryItem = async (req, res) => {
const { id } = req.params;
const { userId } = req.user.userId;

console.log('User:', userId);
console.log('Itinerary Id:', id);

const itineraryItem = await ItineraryItem.findOne({
_id: id,
createdBy: userId
});
if (!itineraryItem) {
throw new NotFoundError(`No itineraryItem with id ${id} for use ${userId}`);
}
res.status(StatusCodes.OK).json({ itineraryItem });
}

const validateNestedFields = (requiredFields, data, prefix = '') => {
const missingFields = [];
for (const field of requiredFields) {
const fieldName = prefix ? `${prefix}.${field}` : field;
if (!data[field]) {
missingFields.push(fieldName);
}
}
return missingFields;
}

const createItineraryItem = async (req, res) => {
req.body.createdBy = req.user;

const locationRequiredFields = ['address', 'city', 'state', 'postalCode'];
const coordinatesRequiredFields = ['lat', 'lng'];

const missingLocationFields = validateNestedFields(locationRequiredFields, req.body.location || {}, 'location');
const missingCoordinatesRequiredFields = validateNestedFields(coordinatesRequiredFields, req.body.location?.coordinates || {}, 'location.coordinates');

const missingFields = [
...missingLocationFields,
...missingCoordinatesRequiredFields,
];

if (missingFields.length > 0) {
throw new BadRequestError(`Missing fields: ${missingFields.join(', ')}`);
}

const itineraryItem = await ItineraryItem.create(req.body);
res.status(StatusCodes.CREATED).json({ itineraryItem });
}

const updateItineraryItem = async (req, res) => {
const { id } = req.params;
console.log(id);
const updates = req.body;

const locationRequiredFields = ['address', 'city', 'state', 'postalCode'];
const coordinatesRequiredFields = ['lat', 'lng'];

const missingLocationFields = validateNestedFields(locationRequiredFields, updates.location || {}, 'location');
const missingCoordinatesRequiredFields = validateNestedFields(coordinatesRequiredFields, updates.location?.coordinates || {}, 'location.coordinates');

const missingFields = [
...missingLocationFields,
...missingCoordinatesRequiredFields,
]

if (missingFields.length > 0) {
throw new BadRequestError(`Missing fields: ${missingFields.join(', ')}`);
}

const itineraryItem = await ItineraryItem.findOneAndUpdate(
{ _id: id },
updates,
{
new: true,
runValidators: true,
}
);

if (!itineraryItem) {
throw new NotFoundError(`No itinerary item found with id: ${id}`);
}

res.status(StatusCodes.OK).json({ itineraryItem, message: 'Itinerary item updated successfully.' });
};

const deleteItineraryItem = async (req, res) => {
const { id } = req.params;

if (!mongoose.Types.ObjectId.isValid(id)) {
throw new BadRequestError(`Invalid ID format: ${id}`);
}

const objectId = new mongoose.Types.ObjectId(id);
const itineraryItem = await ItineraryItem.findOneAndDelete({
_id: objectId,
createdBy: req.userId
});

if (!itineraryItem) {
throw new NotFoundError(`No itinerary item found with id: ${id}`);
}

res.status(StatusCodes.OK).json({ message: 'Itinerary item deleted successfully.'})
};

module.exports = {
getAllItineraryItems,
getSingleItineraryItem,
createItineraryItem,
updateItineraryItem,
deleteItineraryItem,
}
58 changes: 58 additions & 0 deletions src/controllers/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const User = require('../models/User');
const { StatusCodes } = require('http-status-codes');
const { BadRequestError } = require('../errors/bad_request');
const { UnauthenticatedError } = require('../errors/unauthenticated');

const register = async (req, res) => {
try {
const user = await User.create({ ...req.body });
const token = user.createJWT();
res.status(StatusCodes.CREATED).json({
user: { name: `${user.firstName} ${user.lastName}`},
token,
});
} catch (error) {
if (error.code === 11000) {
res.status(StatusCodes.BAD_REQUEST).json({ msg: 'Email already exist' });
} else {
console.error("Registration Error:", error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: 'Registration failed' });
}
}
};

const login = async (req, res, next) => {
try {
const {email, password } = req.body;
// check if email and password are provided
if ( !email || !password) {
throw new BadRequestError('Please provide email and password');
}

// find user by email
const user = await User.findOne({ email });
if (!user) {
throw new UnauthenticatedError('Invalid credentials');
}

// compare passwords
const isPasswordCorrect = await user.comparePassword(password);
if (!isPasswordCorrect) {
throw new UnauthenticatedError("Incorrect password");
}

// generate JWT token and response
const token = user.createJWT();
res.status(StatusCodes.OK).json({
user: { name: `${user.firstName} ${user.lastName}` },
token,
});
} catch (error) {
next(error);
}
};

module.exports = {
register,
login,
};
22 changes: 22 additions & 0 deletions src/db/connect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const mongoose = require('mongoose')
require('dotenv').config();


const connectDB = (url) => {
if (!url) {
throw new Error("Database connection string is undefined. Check your environment variables.");
}
return mongoose.connect(url, {
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
})
.then(() => {
console.log("Connected to the DB ...");
})
.catch((err) => {
console.error("Database connection Error: ", err);
process.exit(1); // Exit process on connection failure
})
}

module.exports = connectDB;
11 changes: 11 additions & 0 deletions src/errors/bad_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom_api');

class BadRequestError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.BAD_REQUEST;
}
}

module.exports = { BadRequestError };
8 changes: 8 additions & 0 deletions src/errors/custom_api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class CustomAPIError extends Error {
constructor(message) {
super(message);
this.statusCode = 400;
}
}

module.exports = CustomAPIError;
11 changes: 11 additions & 0 deletions src/errors/not_found.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom_api');

class NotFoundError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.NOT_FOUND;
}
}

module.exports = { NotFoundError };
11 changes: 11 additions & 0 deletions src/errors/unauthenticated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { StatusCodes } = require('http-status-codes');
const CustomAPIError = require('./custom_api');

class UnauthenticatedError extends CustomAPIError {
constructor(message) {
super(message);
this.statusCode = StatusCodes.UNAUTHORIZED;
}
}

module.exports = { UnauthenticatedError };
11 changes: 11 additions & 0 deletions src/hh-team1-back.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"folders": [
{
"path": ".."
},
{
"path": "../../hh-team1-back-main"
}
],
"settings": {}
}
28 changes: 28 additions & 0 deletions src/middleware/authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require('dotenv').config();
const jwt = require('jsonwebtoken');
const UnauthenticatedError = require('../errors/unauthenticated');

const auth = async (req, res, next) => {
const authHeader = req.headers.authorization;
console.log(authHeader);

if (!authHeader || !authHeader.startsWith('Bearer')) {
throw new UnauthenticatedError('Authentication invalid');
}
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
console.log('JWT Payload:', payload);
req.user = {
userId: payload.userId,
name: `${payload.firstName} ${payload.lastName}`
};
console.log(req.user);
next();
} catch (error) {
throw new UnauthenticatedError('Authentication invalid');
}
};
// console.log('jwt secret', process.env.JWT_SECRET);

module.exports = auth;
11 changes: 11 additions & 0 deletions src/middleware/error_handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const CustomAPIError = require('../errors/custom_api');
const { StatusCodes } = require('http-status-codes');

const errorHandlerMiddleware = (err, req, res, next) => {
if (err instanceof CustomAPIError) {
return res.status(err.statusCode).json({ msg: err.message });
}
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ err: err.message || 'Internal Server Error'});
};

module.exports = errorHandlerMiddleware;
3 changes: 3 additions & 0 deletions src/middleware/not_found.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const notFound = (req, res) => res.status(404).send('Route does not exist')

module.exports = notFound
Loading