Skip to content

Commit

Permalink
Merge pull request #34 from BrockAltug/feature
Browse files Browse the repository at this point in the history
Feature
  • Loading branch information
BrockAltug authored Jan 9, 2025
2 parents 02a1826 + f06f890 commit e040ea9
Show file tree
Hide file tree
Showing 187 changed files with 5,931 additions and 11,907 deletions.
3 changes: 0 additions & 3 deletions 12-model-view-controller/00-mini-project/.env.EXAMPLE

This file was deleted.

199 changes: 91 additions & 108 deletions 12-model-view-controller/00-mini-project/README.md
Original file line number Diff line number Diff line change
@@ -1,108 +1,91 @@
# Module 14 Mini-Project: Crowdfunding App

In this mini-project, you will work with a group to build a full-stack crowdfunding app using Node.js, Express.js, Sequelize, Handlebars.js, and MVC architecture.

## User Stories

* As a user, I want to see a list of current projects seeking funding.

* As a user, I want to be able to create an account.

* As a registered user, I want to post my own projects to ask for funding.

### Acceptance Criteria

* It's done when the `/` homepage route renders a list of all projects from the database.

* It's done when the `/project/:id` route renders an individual project's details based on the route parameter id.

* It's done when the `/login` route renders a form to log in and a form to create a new account.

* It's done when an existing user can enter their credentials on the login page to create a session on the server.

* It's done when a new user can create an account on the login page and then be immediately logged in with a session.

* It's done when the `/profile` route renders the logged-in user's projects and a form to create a new project.

* It's done when only a logged in user can visit the `/profile` route.

* It's done when a logged in user is redirected to `/profile` when they try to visit `/login` again.

* It's done when a user on the profile page can use the form to create a new project in the database.

* It's done when a user on the profile page can select a "Delete" button to remove their project from the database.

* It's done when a logged-in user can select a "Logout" button to remove their session.

* It's done when the session for a logged-in user expires after a set time.

* It's done when the API routes to create and delete posts are protected from non logged-in users.

* It's done when the code is organized using MVC architecture.

* It's done when the views are rendered with Handlebars.js templates.

## Specifications

* The database models have the following fields and associations:

* `User`

* `id`: primary key

* `name`

* `email`

* `password`

* `Project`

* `id`: primary key

* `name`

* `description`

* `date_created`

* `needed_funding`

* `user_id`: foreign key that references `User.id`

* Users have many projects, and projects belong to a user.

* If a user is deleted, all associated projects are also deleted.

---

## Getting Started

The following should be created for the Mini-Project:

* Be sure to change the `.env.EXAMPLE` file to just `.env` and update the credentials correctly.

* Create a `Views` folder to setup the folder structure to follow the MVC paradigm.

* Be sure to review over the [Express Handlebars](https://www.npmjs.com/package/express-handlebars) if you need a refresher on how to set up Handlebars for your `Views` folder.

* Consider the task based on the Acceptance Criteria. Which folder should you work to see data returning from an API call?

## 💡 Hints

* What tools can you use to test the existing API routes if you don't yet have a front end?

* Where would you place the client-side JavaScript for capturing form data?

* How can middleware help protect routes from non logged-in users?

* How can Handlebars.js helpers (both built-in and custom) be used to render the desired results?

## 🏆 Bonus

If you have completed this activity, work through the following challenge with your partner to further your knowledge:

* Add an `/edit/:id` route for logged in users to update their projects' details. Then deploy the app to Render!

---
© 2024 edX Boot Camps LLC. Confidential and Proprietary. All Rights Reserved.
# MVC Mini Project

## Concepts Covered

### **1. Authentication**

- Implemented user login and signup functionalities.
- Used session storage to maintain user sessions with `express-session` and `connect-session-sequelize`.
- Redirected unauthenticated users to the login page using a custom `withAuth` middleware.

### **2. Models**

- **User Model**:
- Stores user information (e.g., name, email, password).
- Includes password hashing with bcrypt during user creation and updates.
- Provides a `checkPassword` method for authentication.
- **Project Model**:
- Stores project details such as name, description, funding required, and creation date.
- Associated with the `User` model for relational data management.

### **3. Routes**

- **User Routes**:
- `POST /api/users`: Handles user registration.
- `POST /api/users/login`: Handles user login.
- `POST /api/users/logout`: Handles user logout.
- **Project Routes**:
- `POST /api/projects`: Allows logged-in users to create a new project.
- `DELETE /api/projects/:id`: Allows logged-in users to delete a project they created.
- **Home Routes**:
- `GET /`: Renders the homepage with a list of projects.
- `GET /project/:id`: Displays details for a specific project.
- `GET /profile`: Displays the user's profile and their projects (protected route).

### **4. Views**

- **Homepage**:
- Displays a list of projects, including funding requirements, creators, and creation dates.
- Utilizes Handlebars.js to dynamically render project data.
- **Profile Page**:
- Allows users to view and manage their created projects.
- Provides options to add new projects or delete existing ones.
- **Login Page**:
- Enables users to log in or create an account.

### **5. Helpers**

- Custom Handlebars helpers:
- `get_emoji`: Dynamically renders emoji based on certain conditions.
- `format_amount`: Formats numbers (e.g., funding required).
- `format_date`: Converts and formats dates.

### **6. Middleware**

- **Custom Authentication Middleware**:
- `withAuth`: Redirects unauthenticated users to the login page.

### **7. Database Setup**

- Utilized Sequelize to define and interact with PostgreSQL.
- Seeded the database with initial user and project data.

### **8. Client-Side Functionality**

- **Login & Signup (public/js/login.js)**:
- Handles user login and account creation.
- Redirects users to the profile page upon successful authentication.
- **Profile Management (public/js/profile.js)**:
- Allows users to add new projects or delete existing ones.
- **Logout (public/js/logout.js)**:
- Ends the user's session and redirects them to the homepage.

### **9. Server-Side Configuration**

- Configured `express-session` with Sequelize as the session store.
- Used `dotenv` to manage environment variables.
- Set up Handlebars as the templating engine for server-rendered views.

## How to Use

1. Clone the repository and install dependencies using `npm install`.
2. Create a `.env` file with the following variables:
```env
DB_NAME=your_database_name
DB_USER=your_database_user
DB_PASSWORD=your_database_password
DB_URL=your_database_url # Optional for hosted databases
```
3. Seed the database with `npm run seed`.
4. Start the application with `npm start`.
5. Access the app in your browser at `http://localhost:3001`.
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Imports the Sequelize library
const Sequelize = require('sequelize');
// Utilizes the 'dotenv' package in order to load the .env file and sets the environment variables to the process.env object.
require('dotenv').config();

let sequelize;
// Checks to see if the application is deployed. If DB_URL environment variable exists, then that is used. If not, it determines that you're on your local machine and utilizes the environment variables from the .env file to set up Sequelize.

if (process.env.DB_URL) {
sequelize = new Sequelize(process.env.DB_URL);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const router = require('express').Router();
// Import the routes. This is how we make our routes modular.
const userRoutes = require('./userRoutes');
const projectRoutes = require('./projectRoutes');

// When a request is made to the /users or /projects path, it will be directed to the index.js in the /users or /projects folder.
router.use('/users', userRoutes);
router.use('/projects', projectRoutes);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
const router = require('express').Router();
// Import the Project model from the models folder
const { Project } = require('../../models');
const withAuth = require('../../utils/auth');

// If a POST request is made to /api/projects, a new project is created. If there is an error, the function returns with a 400 error.
router.post('/', async (req, res) => {
router.post('/', withAuth, async (req, res) => {
try {
const newProject = await Project.create({
...req.body,
Expand All @@ -16,8 +15,7 @@ router.post('/', async (req, res) => {
}
});

// If a DELETE request is made to /api/projects/:id, that project is deleted.
router.delete('/:id', async (req, res) => {
router.delete('/:id', withAuth, async (req, res) => {
try {
const projectData = await Project.destroy({
where: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const router = require('express').Router();
// Import the User model from the models folder
const { User } = require('../../models');

// If a POST request is made to /api/users, a new user is created. The user id and logged in state is saved to the session within the request object.
router.post('/', async (req, res) => {
try {
const userData = await User.create(req.body);
Expand All @@ -18,7 +16,6 @@ router.post('/', async (req, res) => {
}
});

// If a POST request is made to /api/users/login, the function checks to see if the user information matches the information in the database and logs the user in. If correct, the user ID and logged-in state are saved to the session within the request object.
router.post('/login', async (req, res) => {
try {
const userData = await User.findOne({ where: { email: req.body.email } });
Expand Down Expand Up @@ -51,7 +48,6 @@ router.post('/login', async (req, res) => {
}
});

// If a POST request is made to /api/users/logout, the function checks the logged_in state in the request.session object and destroys that session if logged_in is true.
router.post('/logout', (req, res) => {
if (req.session.logged_in) {
req.session.destroy(() => {
Expand Down
82 changes: 82 additions & 0 deletions 12-model-view-controller/00-mini-project/controllers/homeRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const router = require('express').Router();
const { Project, User } = require('../models');
const withAuth = require('../utils/auth');

router.get('/', async (req, res) => {
try {
// Get all projects and JOIN with user data
const projectData = await Project.findAll({
include: [
{
model: User,
attributes: ['name'],
},
],
});

// Serialize data so the template can read it
const projects = projectData.map((project) => project.get({ plain: true }));

// Pass serialized data and session flag into template
res.render('homepage', {
projects,
logged_in: req.session.logged_in
});
} catch (err) {
res.status(500).json(err);
}
});

router.get('/project/:id', async (req, res) => {
try {
const projectData = await Project.findByPk(req.params.id, {
include: [
{
model: User,
attributes: ['name'],
},
],
});

const project = projectData.get({ plain: true });

res.render('project', {
...project,
logged_in: req.session.logged_in
});
} catch (err) {
res.status(500).json(err);
}
});

// Use withAuth middleware to prevent access to route
router.get('/profile', withAuth, async (req, res) => {
try {
// Find the logged in user based on the session ID
const userData = await User.findByPk(req.session.user_id, {
attributes: { exclude: ['password'] },
include: [{ model: Project }],
});

const user = userData.get({ plain: true });

res.render('profile', {
...user,
logged_in: true
});
} catch (err) {
res.status(500).json(err);
}
});

router.get('/login', (req, res) => {
// If the user is already logged in, redirect the request to another route
if (req.session.logged_in) {
res.redirect('/profile');
return;
}

res.render('login');
});

module.exports = router;
6 changes: 3 additions & 3 deletions 12-model-view-controller/00-mini-project/controllers/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Import just the router express
const router = require('express').Router();
// Import the index.js from 'api' folder

const apiRoutes = require('./api');
const homeRoutes = require('./homeRoutes');

// When a request is made to the /api route, it will be directed to the index.js in the 'api' folder.
router.use('/', homeRoutes);
router.use('/api', apiRoutes);

module.exports = router;
1 change: 0 additions & 1 deletion 12-model-view-controller/00-mini-project/models/Project.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/connection');

// Create Project model and datatypes, including the user_id foreign key.
class Project extends Model {}

Project.init(
Expand Down
2 changes: 0 additions & 2 deletions 12-model-view-controller/00-mini-project/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const bcrypt = require('bcrypt');
const sequelize = require('../config/connection');

class User extends Model {
// Checks the password against the encrypted password in the database.
checkPassword(loginPw) {
return bcrypt.compareSync(loginPw, this.password);
}
Expand Down Expand Up @@ -38,7 +37,6 @@ User.init(
},
},
{
// Hooks are used so that if a user is created or updated, the password is encrypted before being stored in the database.
hooks: {
beforeCreate: async (newUserData) => {
newUserData.password = await bcrypt.hash(newUserData.password, 10);
Expand Down
Loading

0 comments on commit e040ea9

Please sign in to comment.