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

Prepared app for deployment #1

Open
wants to merge 52 commits into
base: natours-api
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c8306c5
Prepared app for deployment
nolzart Dec 6, 2020
7b6fe72
update package.json
nolzart Dec 6, 2020
8f685cf
Added heroku config
nolzart Dec 7, 2020
caa8730
Improve stripe implementation
nolzart Dec 7, 2020
15aef72
stripe checkout bug fixed
nolzart Dec 7, 2020
3579a04
Create booking checkout bug fixed
nolzart Dec 7, 2020
8b05e5e
tested stripe metadata
nolzart Dec 7, 2020
da3f5f4
integrated react to the project
nolzart Dec 17, 2020
380efd8
converting styles from css to sass
nolzart Dec 17, 2020
fb7aae3
Added overview page component
nolzart Dec 21, 2020
ba4f0d0
Added Tour Details and Login page
nolzart Dec 22, 2020
5dca4a6
Implemented authentication with redux
nolzart Dec 22, 2020
ca51f66
Added tours to the redux global state
nolzart Dec 22, 2020
bd3c4af
integrated the functionality of updating user information to the fron…
nolzart Jan 4, 2021
70838ca
migrating code to nextjs
nolzart Jan 6, 2021
6518177
Adjusted project to send actions from server side and persist data
nolzart Jan 8, 2021
4a74006
connect frontend to backend with http-proxy-middleware
nolzart Jan 8, 2021
64eb139
added functionality to keep authentication even after page reload
nolzart Jan 11, 2021
6998515
photo upload bug fixed
nolzart Jan 12, 2021
f609b5d
added my-tours page
nolzart Jan 12, 2021
21b4d9e
Added alert component
nolzart Jan 13, 2021
f886a7a
bug fixed, two functions with the same name on page [slug]
nolzart Jan 13, 2021
99a8d5f
payment enabled with stripe
nolzart Jan 13, 2021
c236a5e
added loader component
nolzart Jan 13, 2021
071bd63
A responsive layout was implemented for tablets in landscape mode
nolzart Jan 15, 2021
17ddf71
A responsive layout has been implemented for the overview page for ph…
nolzart Jan 25, 2021
b1a3723
A responsive layout has been implemented for the tour page for phones…
nolzart Jan 26, 2021
43031ca
bug fixed, incompatibility between mapboxgl and stripejs packages
nolzart Jan 26, 2021
eb00aa7
react-mapbox-gl package replaced by mapboxgl cdn due to incompatibili…
nolzart Jan 27, 2021
38b7218
fixed bug in navigation section
nolzart Jan 27, 2021
d2626f5
Responsive layout implemented for user account page and login page fo…
nolzart Jan 27, 2021
e5c973d
added sign up page to the frontend
nolzart Jan 28, 2021
fc60b87
using react-hook-form on all forms in the app
nolzart Feb 1, 2021
6e1d5c3
using the Image component of nextjs for logos
nolzart Feb 1, 2021
0f35a42
removed unnecessary imports from react package
nolzart Feb 2, 2021
82bd05a
scrollbar style fixed for firefox
nolzart Feb 2, 2021
e287db1
improved project structure
nolzart Feb 2, 2021
2cdc5d8
unnecessary backend files removed
nolzart Feb 2, 2021
6c0525c
added eslint and prettier to the project
nolzart Feb 2, 2021
66ea09f
refactored the TourDetails component
nolzart Feb 2, 2021
2da0cca
Fixed various bugs, errors getting tours, errors getting a tour, not …
nolzart Feb 3, 2021
502e9ce
refactored forms
nolzart Feb 3, 2021
fcc7f7a
refactored getTour endpoint
nolzart Feb 4, 2021
be9f3fc
fixed bug, Image Optimization using Next.js' default loader is not co…
nolzart Feb 4, 2021
c83d609
fixed bug, pages with getServerSideProps can not be exported
nolzart Feb 4, 2021
cd897ba
configuration for deployment
nolzart Feb 5, 2021
a9748e4
setting to heroku
nolzart Feb 5, 2021
cc30e64
fixed bug, route to log in page fail
nolzart Feb 5, 2021
d03062f
routes added to views
nolzart Feb 5, 2021
cc99117
Added README file
nolzart Feb 6, 2021
c1eaa27
conflicts when merging with main branch resolved
nolzart Feb 6, 2021
f05f0b6
fixed alignment in README file
nolzart Feb 6, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package-lock.json
node_modules
config.env
*.env
*.next
*out
.cache

public/js/bundle.js.map

public/js/bundle.js.map
node_modules
195 changes: 195 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<p align="center"><img src='api/public/img/logo-green-round.png' align="center" height="160px"/></p>

<h1 align="center">
Natours
</h1>

<p align="justify">A page for a travel agency startup built on top of NodeJS and NextJS, here is the <a href="https://mern-natours.herokuapp.com/">live version</a></p>

<ul>
<li>
<a href="#description">Description</a>
</li>
<li>
<a href="#requirements">Few Requirements</a>
</li>
<li>
<a href="#key-features">Key Features</a>
</li>
<li>
<a href="#how-to-use">How To Use</a>
</li>
<li>
<a href="#api-usage">API Usage</a>
</li>
<li>
<a href="#enviroment-variables">Enviroment Variables</a>
</li>
<li>
<a href="#build-with">Build With</a>
</li>
<li>
<a href="#acknowledgement">Acknowledgement</a>
</li>
</ul>

<h1 id="description">Description</h1>

<p align="justify">
Natours is an application where the API is built with <a href="https://nodejs.org/en/docs/">NodeJS</a> and <a href="https://expressjs.com/">express</a>. the frontend created in <a href="https://nextjs.org/docs/getting-started">Nextjs</a> is served by node. Once you enter the main page, all the tours available in the application will be displayed, each tour has a button that guides you to the details section, in this you can see all the information related to the tour, such as the routes to follow with <a href="https://docs.mapbox.com/mapbox-gl-js/api/">mapboxgl</a> and user reviews, at the end you will find a section to buy the tour with your credit card using <a href="https://stripe.com/docs/payments/checkout">stripejs checkout</a>, to access the application you can use the route <b>/login</b> or <b>/signup</b>. All authentication and authorization is handled with <a href="https://jwt.io/">JWT</a> and cookies.
</p>

<h1 id="requirements">Few Requirements</h1>

<ul>
<li><a href="https://nodejs.org/">NodeJS >= 12.x</a></li>
<li><a href="https://www.npmjs.com/">npm >= 6.x</a></li>
<li><a href="https://www.mongodb.com/">MongoDB</a></li>
</ul>


<h1 id="key-features">Key Features</h1>

<ul>
<li>Authentication and Authorization <ul><li>Sign up, Log in and Log out</li></ul></li>
<li>Tour<ul><li>See tour guides, check the tour route with maps, check user reviews, ratings and book the tour</li></ul></li>
<li>User Profile <ul><li>Update username, photo, email and password</li></ul></li>
<li>Credit card payment</li>
</ul>

<h1 id="how-to-use">How To Use</h1>
<h2>Book a tour</h2>
<ul>
<li>Log in to the site.</li>
<li>Choose the tour you like the most.</li>
<li>Book a tour.</li>
<li>You will be redirected to the payment page.</li>
<li>Enter the card details (Stripe is in test mode).</li>
<pre>
- Card No: 4242 4242 4242 4242
- Expiry date: 02 / 24
- CVV: 222
</pre>
</ul>

<h2>Manage your booking</h2>
<ul>
<li>On the account settings page, you will find a button in the "Manage Booking" navigation that will show you the tours you have booked.</li>
</ul>

<h2>Update your profile</h2>
<ul>
<li>On the account settings page, you can update your own username, profile photo, email and password.</li>
</ul>

</div>

<h1 id="api-usage">API usage</h1>

<p align="justify">
Before using the API, you need to set the variables in your API testing (like Postman or Insomnia) depending on your enviroment.
<pre>
- {{URL}} with your hostname as value (Eg. http://127.0.0.1:3000 or https://www.production.com)
- {{JWT}} with your Json Web Token as value.
</pre>
</p>

<h2 id="api-features">API Features</h2>
<p align="justify">
<b>Filtering</b> 👉 You can include filters in the URL by including additional query parameters. To start filtering add a <code>?</code> followed by the query <code>[query]=[value]</code>. If you want to chain several queries in the same call, use & followed by the query.
</p>

<p>For example<code>/api/v1/tours?duration=7&maxGroupSize=15</code></p>

<p align="justify">
You can use mongoose operators, specifying the operator you want to use encapsulation in brackets in front of the property
</p>
<p>For Example: <code>api/v1/tours?duration[gte]=5&price[lte]=2000</code></p>

<p align="justify">
<b>Sorting</b> 👉 You can sort results based on a certain field using <code>sort</code>parameter.
</p>

<p>For Example: <code>api/v1/tours?sort=price</code> upward</p>
<p><code>api/v1/tours?sort=-price</code> falling</p>

<p align="justify">
<b>Limit Fields</b> 👉 You can specify which fields you want to get back in the response using the <code>fields</code> parameter.
</p>
<p>
For example: <code>api/v1/tours?fields=name</code> the response will return the name field.
</p>
<p>
<code>api/v1/tours?fields=-guides</code> the response will not return the guides field.
</p>
<p align="justify">
<b>Pagination</b> 👉 You can select a certain page of the results using the <code>limit</code> parameter to indicate how many results you want per page and the <code>page</code> parameter to indicate the page.
</p>

<p>For exapmle <code>api/v1/tours?page=2&limit=3</code></p>

<p><b>Tours List</b> 👉 return all tours https://mern-natours.herokuapp.com/api/v1/tours</p>
<p><b>Tour Stats</b> 👉 returns the statistics of all tours https://mern-natours.herokuapp.com/api/v1/tours/tours-stats
<p><b>Get Tours Within Radius</b> 👉 It shows the cheapest tours and the ones that are best rated by users https://mern-natours.herokuapp.com/api/v1/tours/top-5-cheap</p>
<p><b>Get Tours Within Radius</b> 👉 You can get tours around a certain area, specifying the coordinates (in the form of lat, lng), the distance and the unit (mi or km)</p>
<p><code>/tours-within/distance/:distance/center/:latlng/unit/:unit</code> 👉 https://mern-natours.herokuapp.com/api/v1/tours/tours-within/distance/200/center/34.371714,%20-117.825382/unit/mi</p>

<h1 id="enviroment-variables">Enviroment Variables</h1>

<h2>Server and Database Keys</h2>
<ol>
<li>NODE_ENV 👉 node enviroment (development or production)</li>
<li>PORT</li>
<li>DATABASE 👉 Hosted database</li>
<li>DATABASE_PASSWORD 👉 Hosted database password</li>
</ol>

<h2>Authentication Keys</h2>
<ol>
<li>JWT_SECRET 👉 Generate your secret jwt key</li>
<li>JWT_EXPIRES_IN 👉 expiration time of jwt</li>
<li>JWT_COOKIE_EXPIRES_IN 👉 expiration time of cookie</li>
</ol>

<h2>Email Keys</h2>
<ol>
<li>EMAIL_HOST 👉 Mailtrap host</li>
<li>EMAIL_PORT 👉 Mailtrap port</li>
<li>EMAIL_USERNAME 👉 Mailtrap username</li>
<li>EMAIL_PASSWORD 👉 Your mailtrap password</li>
</ol>

<h2>Admin details</h2>
<ol>
<li>EMAIL_FROM 👉 receiver will see this email of admin in his email</li>
<li>SENDGRID_USERNAME 👉 Sengrid username</li>
<li>SENDGRID_PASSWORD 👉 Sengrid password</li>
<li>SENGRID_API_KEY 👉 Your sengrid api key</li>
</ol>

<h2>Stripe Keys</h2>

<ol>
<li>STRIPE_API_KEY 👉 Your stripe secret key </li>
</ol>

<h1 id="build-with"> Build With</h1>

<ul>
<li><a href="https://nodejs.org/en/">NodeJS</a> 👉 JS runtime enviroment</li>
<li><a href="https://expressjs.com/">ExpressJS</a> 👉 Web framework for Node.js</li>
<li><a href="https://nextjs.org/">NextJS</a> 👉 The react framework for production</li>
<li><a href="https://redux.js.org/">Redux</a> 👉 A Predictable State Container for JS Apps</li>
<li><a href="https://sass-lang.com/">SASS</a> 👉 CSS extension language</li>
<li><a href="https://mongoosejs.com/">Mongoose</a> 👉 MongoDB object modeling for node.js</li>
<li><a href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas</a> 👉 Cloud database service</li>
<li><a href="https://jwt.io/">JSON Web Token</a> 👉 open, industry standard RFC 7519 method for representing claims securely between two parties</li>
<li><a href="https://stripe.com/">Stripe</a> 👉 Payments infrastructure for the internet</li>
<li><a href="https://mailtrap.io/">Mailtrap</a> & <a href="https://sendgrid.com/">Sengrid</a> 👉 Email Delivery Service</li>
<li><a href="https://www.heroku.com/">Heroku</a> 👉 Cloud platform</li>
</ul>

<h1 id="acknowledgement">Acknowledgement</h1>
<p>
The API and design for this project are part of a udemy online course I did. building the frontend with nextjs is my own work 😎. Thanks to <a href="https://codingheroes.io/">Jonas Schmedtmann</a> for this great <a href="https://www.udemy.com/course/nodejs-express-mongodb-bootcamp/">course</a>.
</p>
File renamed without changes.
File renamed without changes.
File renamed without changes.
23 changes: 18 additions & 5 deletions app.js → api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ const globalErrorHandler = require('./controllers/errorController');
const tourRouter = require('./routes/tourRoutes');
const userRouter = require('./routes/userRoutes');
const reviewRouter = require('./routes/reviewRoutes');
const viewRouter = require('./routes/viewRoutes');
const bookingRouter = require('./routes/bookingRoutes');
const viewRouter = require('./routes/viewRoutes');
const bookingController = require('./controllers/bookingController');

const app = express();

app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));
app.enable('trust proxy');

app.set('view engine', 'html');

app.engine('html', require('ejs').renderFile);

app.set('views', path.join(__dirname, 'public'));

// Implement cors
app.use(cors());
Expand All @@ -46,7 +52,14 @@ const limiter = rateLimit({
max: 100,
message: 'Too many requests from this IP, please try again in 30m',
});
app.use('/api', limiter);

if (process.env.NODE_ENV !== 'development') app.use('/api', limiter);

app.post(
'/webhook-checkout',
express.raw({ type: 'application/json' }),
bookingController.webhookCheckout
);

// Body parser, reading data from body into req.body
app.use(express.json({ limit: '10kb' }));
Expand Down Expand Up @@ -76,11 +89,11 @@ app.use(
app.use(compression());

// ROUTES
app.use('/', viewRouter);
app.use('/api/v1/tours', tourRouter);
app.use('/api/v1/users', userRouter);
app.use('/api/v1/reviews', reviewRouter);
app.use('/api/v1/bookings', bookingRouter);
app.use('/', viewRouter);

app.all('*', (req, res, next) => {
next(new AppError(`Can't find ${req.originalUrl} on this server`, 404));
Expand Down
27 changes: 17 additions & 10 deletions controllers/authController.js → api/controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ const signToken = id =>
expiresIn: process.env.JWT_EXPIRES_IN,
});

const createSendToken = (user, statusCode, res) => {
const createSendToken = (user, statusCode, req, res) => {
const token = signToken(user._id);

const cookieOptions = {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
),
httpOnly: true,
};
if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;

if (process.env.NODE_ENV === 'production')
cookieOptions.secure =
req.secure || req.headers['x-forwarded-proto'] === 'https';

res.cookie('jwt', token, cookieOptions);

user.password = undefined;

res.status(statusCode).json({
Expand All @@ -35,6 +38,9 @@ const createSendToken = (user, statusCode, res) => {
});
};

exports.refreshToken = (req, res, next) =>
createSendToken(req.user, 200, req, res);

exports.signup = catchAsync(async (req, res, next) => {
// NOTE: SERIUS SECURITY FLAW --> const newUser = await User.creatE(req.body);
const newUser = await User.create({
Expand All @@ -47,7 +53,7 @@ exports.signup = catchAsync(async (req, res, next) => {
const url = `${req.protocol}://${req.get('host')}/me`;
await new Email(newUser, url).sendWelcome();

createSendToken(newUser, 201, res);
createSendToken(newUser, 201, req, res);
});

exports.login = catchAsync(async (req, res, next) => {
Expand All @@ -62,7 +68,7 @@ exports.login = catchAsync(async (req, res, next) => {
return next(new AppError('Incorrect email or password', 401));

// If everything ok, send token to client
createSendToken(user, 200, res);
createSendToken(user, 200, req, res);
});

exports.logout = (req, res, next) => {
Expand All @@ -83,7 +89,6 @@ exports.protect = catchAsync(async (req, res, next) => {
)
token = req.headers.authorization.split(' ')[1];
else if (req.cookies.jwt) token = req.cookies.jwt;

if (!token || token === 'loggedout')
return next(
new AppError('You are not logged in! Please log in to get access'),
Expand Down Expand Up @@ -133,7 +138,8 @@ exports.isLoggedIn = async (req, res, next) => {
// Check if user changed password after the token was issued
if (currentUser.changedPasswordAfter(decoded.iat)) return next();

res.locals.user = currentUser;
// res.locals.user = currentUser; used for pug templates
req.user = currentUser;
return next();
} catch (err) {
return next();
Expand Down Expand Up @@ -213,13 +219,12 @@ exports.resetPassword = catchAsync(async (req, res, next) => {

await user.save();
//Log the user in, send JWT
createSendToken(user, 200, res);
createSendToken(user, 200, req, res);
});

exports.updatePassword = catchAsync(async (req, res, next) => {
// Get user from collection
const user = await User.findById(req.user.id).select('+password');

// Check if POSTed current password is correct
if (
!user ||
Expand All @@ -228,10 +233,12 @@ exports.updatePassword = catchAsync(async (req, res, next) => {
return next(new AppError('Your current password is wrong.', 401));

// If so, update password

user.password = req.body.password;
user.passwordConfirm = req.body.passwordConfirm;
await user.save();
// User.findByIdAndUpdate wil NOT work as intended!
// Log user in, send JWT
createSendToken(user, 200, res);
// user.passwordConfirm = undefined;
createSendToken(user, 200, req, res);
});
Loading