Initial commit for aws_deployment

develop
MarvsTech 2 months ago
commit f882f2cf56

@ -0,0 +1 @@
REACT_APP_API_URL=http://<ec2-instance-Public-IPv4-DNS>:4000

23
client/.gitignore vendored

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

@ -0,0 +1,42 @@
{
"name": "s54",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.0",
"react": "^18.2.0",
"react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0",
"react-router-dom": "^6.13.0",
"react-scripts": "5.0.1",
"sweetalert2": "^11.7.12",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -0,0 +1,9 @@
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.cardHighlight {
min-height: 100%;
}

@ -0,0 +1,101 @@
import { useState, useEffect } from 'react';
import { Container } from 'react-bootstrap';
import { BrowserRouter as Router } from 'react-router-dom';
import { Route, Routes } from 'react-router-dom';
import AppNavbar from './components/AppNavbar';
// import Banner from './components/Banner';
// import Highlights from './components/Highlights';
import Courses from './pages/Courses';
import CourseView from './pages/CourseView';
import Error from './pages/Error';
import Home from './pages/Home';
import Login from './pages/Login';
import Logout from './pages/Logout';
import Register from './pages/Register';
import AddCourse from './pages/AddCourse';
import Profile from './pages/Profile';
import './App.css';
import { UserProvider } from './UserContext';
// React JS is a single page application (SPA)
// Whenever a link is clicked, it functions as if the page is being reloaded but what it actually does is it goes through the process of rendering, mounting, rerendering and unmounting components
// When a link is clicked, React JS changes the url of the application to mirror how HTML accesses its urls
// It renders the component executing the function component and it's expressions
// After rendering it mounts the component displaying the elements
// Whenever a state is updated or changes are made with React JS, it rerenders the component
// Lastly, when a different page is loaded, it unmounts the component and repeats this process
// The updating of the user interface closely mirrors that of how HTML deals with page navigation with the exception that React JS does not reload the whole page
function App() {
// State hook for the user state that's defined here for a global scope
// Initialized as an object with properties from the localStorage
// This will be used to store the user information and will be used for validating if a user is logged in on the app or not
const [user, setUser] = useState({
id: null,
isAdmin: null
});
// Function for clearing localStorage on logout
const unsetUser = () => {
localStorage.clear();
};
//Because our user state's values are reset to null every time the user reloads the page (thus logging the user out), we want to use React's useEffect hook to fetch the logged-in user's details when the page is reloaded. By using the token saved in localStorage when a user logs in, we can fetch the their data from the database, and re-set the user state values back to the user's details.
useEffect(() => {
// console.log(user);
fetch(`${process.env.REACT_APP_API_URL}/users/details`, {
headers: {
Authorization: `Bearer ${ localStorage.getItem('token') }`
}
})
.then(res => res.json())
.then(data => {
console.log(data)
// Set the user states values with the user details upon successful login.
if (typeof data._id !== "undefined") {
setUser({
id: data._id,
isAdmin: data.isAdmin
});
// Else set the user states to the initial values
} else {
setUser({
id: null,
isAdmin: null
});
}
})
}, []);
return (
<UserProvider value={{user, setUser, unsetUser}}>
<Router>
<AppNavbar />
<Container>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/courses" element={<Courses />} />
<Route path="/courses/:courseId" element={<CourseView />}/>
<Route path="/register" element={<Register />} />
<Route path="/login" element={<Login />} />
<Route path="/logout" element={<Logout />} />
<Route path="/addCourse" element={<AddCourse />} />
<Route path="/profile" element={<Profile />} />
<Route path="*" element={<Error />} />
</Routes>
</Container>
</Router>
</UserProvider>
);
}
export default App;

@ -0,0 +1,11 @@
import React from 'react';
// Creates a Context object
// A context object as the name states is a data type of an object that can be used to store information that can be shared to other components within the app
// The context object is a different approach to passing information between components and allows easier access by avoiding the use of prop-drilling
const UserContext = React.createContext();
// The "Provider" component allows other components to consume/use the context object and supply the necessary information needed to the context object
export const UserProvider = UserContext.Provider;
export default UserContext;

@ -0,0 +1,60 @@
import { useState, useEffect } from 'react';
import { Table } from 'react-bootstrap';
import EditCourse from './EditCourse';
import ArchiveCourse from './ArchiveCourse';
export default function AdminView({ coursesData, fetchData }) {
const [courses, setCourses] = useState([])
//Getting the coursesData from the courses page
useEffect(() => {
const coursesArr = coursesData.map(course => {
return (
<tr key={course._id}>
<td>{course._id}</td>
<td>{course.name}</td>
<td>{course.description}</td>
<td>{course.price}</td>
<td className={course.isActive ? "text-success" : "text-danger"}>
{course.isActive ? "Available" : "Unavailable"}
</td>
<td> <EditCourse course={course._id} fetchData={fetchData} /> </td>
<td><ArchiveCourse course={course._id} isActive={course.isActive} fetchData={fetchData}/></td>
</tr>
)
})
setCourses(coursesArr)
}, [coursesData])
return(
<>
<h1 className="text-center my-4"> Admin Dashboard</h1>
<Table striped bordered hover responsive>
<thead>
<tr className="text-center">
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th>Availability</th>
<th colSpan="2">Actions</th>
</tr>
</thead>
<tbody>
{courses}
</tbody>
</Table>
</>
)
}

@ -0,0 +1,51 @@
import { useState, useContext } from 'react';
import Container from 'react-bootstrap/Container';
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import { Link, NavLink } from 'react-router-dom';
import UserContext from '../UserContext';
export default function AppNavbar() {
// State to store the user information stored in the login page.
// const [user, setUser] = useState(localStorage.getItem("access"));
// console.log(user);
const { user } = useContext(UserContext);
return(
<Navbar bg="light" expand="lg">
<Container fluid>
<Navbar.Brand as={Link} to="/">Zuitt</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ms-auto">
<Nav.Link as={NavLink} to="/" exact>Home</Nav.Link>
<Nav.Link as={NavLink} to="/courses" exact>Courses</Nav.Link>
{(user.id !== null) ?
user.isAdmin
?
<>
<Nav.Link as={Link} to="/addCourse">Add Course</Nav.Link>
<Nav.Link as={Link} to="/logout">Logout</Nav.Link>
</>
:
<>
<Nav.Link as={Link} to="/profile">Profile</Nav.Link>
<Nav.Link as={Link} to="/logout">Logout</Nav.Link>
</>
:
<>
<Nav.Link as={Link} to="/login">Login</Nav.Link>
<Nav.Link as={Link} to="/register">Register</Nav.Link>
</>
}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
)
}

@ -0,0 +1,87 @@
import { Button } from 'react-bootstrap';
import Swal from 'sweetalert2';
export default function ArchiveCourse({course, isActive, fetchData}) {
const archiveToggle = (courseId) => {
fetch(`${process.env.REACT_APP_API_URL}/courses/${courseId}/archive`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(data => {
console.log(data)
if(data === true) {
Swal.fire({
title: 'Success',
icon: 'success',
text: 'Course successfully disabled'
})
fetchData();
}else {
Swal.fire({
title: 'Something Went Wrong',
icon: 'Error',
text: 'Please Try again'
})
fetchData();
}
})
}
const activateToggle = (courseId) => {
fetch(`${process.env.REACT_APP_API_URL}/courses/${courseId}/activate`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(data => {
console.log(data)
if(data === true) {
Swal.fire({
title: 'Success',
icon: 'success',
text: 'Course successfully enabled'
})
fetchData();
}else {
Swal.fire({
title: 'Something Went Wrong',
icon: 'Error',
text: 'Please Try again'
})
fetchData();
}
})
}
return(
<>
{isActive ?
<Button variant="danger" size="sm" onClick={() => archiveToggle(course)}>Archive</Button>
:
<Button variant="success" size="sm" onClick={() => activateToggle(course)}>Activate</Button>
}
</>
)
}

@ -0,0 +1,18 @@
import { Button, Row, Col } from 'react-bootstrap';
import { Link } from 'react-router-dom';
export default function Banner({data}) {
console.log(data);
const {title, content, destination, label} = data;
return (
<Row>
<Col className="p-4 text-center">
<h1>{title}</h1>
<p>{content}</p>
<Link className="btn btn-primary" to={destination}>{label}</Link>
</Col>
</Row>
)
}

@ -0,0 +1,56 @@
import { useState } from 'react';
import { Card, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
export default function CourseCard({courseProp}) {
// Checks to see if the data was successfully passed
// console.log(props);
// Every component recieves information in a form of an object
// console.log(typeof props);
// Deconstruct the course properties into their own variables
const { _id, name, description, price} = courseProp;
// const [count, setCount] = useState(0);
// console.log(useState(0));
// const [seats, setSeats] = useState(10);
// function enroll(){
// if (seats > 0) {
// setCount(count + 1);
// console.log('Enrollees: ' + count);
// setSeats(seats - 1);
// console.log('Seats: ' + seats)
// } else {
// alert("No more seats available");
// };
// }
return (
<Card className="mt-3">
<Card.Body>
<Card.Title>{name}</Card.Title>
<Card.Subtitle>Description:</Card.Subtitle>
<Card.Text>{description}</Card.Text>
<Card.Subtitle>Price:</Card.Subtitle>
<Card.Text>PhP {price}</Card.Text>
<Link className="btn btn-primary" to={`/courses/${_id}`}>Details</Link>
</Card.Body>
</Card>
)
}
CourseCard.propTypes = {
// The "shape" method is used to check if a prop object conforms to a specific "shape"
course: PropTypes.shape({
// Define the properties and their expected types
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
price: PropTypes.number.isRequired
})
}

@ -0,0 +1,50 @@
import React, { useState } from 'react';
import CourseCard from './CourseCard';
const CourseSearch = () => {
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const handleSearch = async () => {
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/courses/searchByName`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ courseName: searchQuery })
});
const data = await response.json();
setSearchResults(data);
} catch (error) {
console.error('Error searching for courses:', error);
}
};
return (
<div className='pt-5 container'>
<h2>Course Search</h2>
<div className="form-group">
<label htmlFor="courseName">Course Name:</label>
<input
type="text"
id="courseName"
className="form-control"
value={searchQuery}
onChange={event => setSearchQuery(event.target.value)}
/>
</div>
<button className="btn btn-primary my-4" onClick={handleSearch}>
Search
</button>
<h3>Search Results:</h3>
<ul>
{searchResults.map(course => (
// <li key={course._id}>{course.name}</li>
<CourseCard courseProp={course} key={course._id}/>
))}
</ul>
</div>
);
};
export default CourseSearch;

@ -0,0 +1,135 @@
import React, { useState } from 'react';
import { Button, Modal, Form } from 'react-bootstrap';
import Swal from 'sweetalert2';
export default function EditCourse({ course, fetchData }) {
//state for courseId for the fetch URL
const [courseId, setCourseId] = useState('');
//Forms state
//Add state for the forms of course
const [name, setName] = useState('');
const [description, setDescription] = useState('')
const [price, setPrice] = useState('')
//state for editCourse Modals to open/close
const [showEdit, setShowEdit] = useState(false)
//function for opening the modal
const openEdit = (courseId) => {
//to still get the actual data from the form
fetch(`${process.env.REACT_APP_API_URL}/courses/${ courseId }`)
.then(res => res.json())
.then(data => {
//populate all the input values with course info that we fetched
setCourseId(data._id);
setName(data.name);
setDescription(data.description);
setPrice(data.price)
})
//Then, open the modal
setShowEdit(true)
}
const closeEdit = () => {
setShowEdit(false);
setName('')
setDescription('')
setPrice(0)
}
//function to update the course
const editCourse = (e, courseId) => {
e.preventDefault();
fetch(`${process.env.REACT_APP_API_URL}/courses/${ courseId }`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
name: name,
description: description,
price: price
})
})
.then(res => res.json())
.then(data => {
console.log(data)
if(data === true) {
Swal.fire({
title: 'Success!',
icon: 'success',
text: 'Course Successfully Updated'
})
closeEdit();
fetchData();
} else {
Swal.fire({
title: 'Error!',
icon: 'error',
text: 'Please try again'
})
closeEdit();
fetchData();
}
})
}
return(
<>
<Button variant="primary" size="sm" onClick={() => openEdit(course)}> Edit </Button>
{/*Edit Modal Forms*/}
<Modal show={showEdit} onHide={closeEdit}>
<Form onSubmit={e => editCourse(e, courseId)}>
<Modal.Header closeButton>
<Modal.Title>Edit Course</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group>
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
value={name}
onChange={e => setName(e.target.value)}
required/>
</Form.Group>
<Form.Group>
<Form.Label>Description</Form.Label>
<Form.Control
type="text"
value={description}
onChange={e => setDescription(e.target.value)}
required/>
</Form.Group>
<Form.Group>
<Form.Label>Price</Form.Label>
<Form.Control
type="number"
value={price}
onChange={e => setPrice(e.target.value)}
required/>
</Form.Group>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeEdit}>Close</Button>
<Button variant="success" type="submit">Submit</Button>
</Modal.Footer>
</Form>
</Modal>
</>
)
}

@ -0,0 +1,51 @@
import { useState, useEffect } from 'react'
import { CardGroup } from 'react-bootstrap'
import PreviewCourses from './PreviewCourses'
export default function FeaturedCourses(){
const [previews, setPreviews] = useState([])
useEffect(() => {
fetch(`${ process.env.REACT_APP_API_URL}/courses/`)
.then(res => res.json())
.then(data => {
console.log(data)
const numbers = []
const featured = []
const generateRandomNums = () => {
let randomNum = Math.floor(Math.random() * data.length)
if(numbers.indexOf(randomNum) === -1){
numbers.push(randomNum)
}else{
generateRandomNums()
}
}
for(let i = 0; i < 5; i++){
generateRandomNums()
featured.push(
<PreviewCourses data={data[numbers[i]]} key={data[numbers[i]]._id} breakPoint={2} />
)
}
setPreviews(featured)
})
}, [])
return(
<>
<h2 className="text-center">Featured Courses</h2>
<CardGroup className="justify-content-center">
{previews}
</CardGroup>
</>
)
}

@ -0,0 +1,44 @@
import { Row, Col, Card } from 'react-bootstrap';
export default function Highlights() {
return (
<Row className="mt-3 mb-3">
<Col xs={12} md={4}>
<Card className="cardHighlight p-3">
<Card.Body>
<Card.Title>
<h2>Learn from Home</h2>
</Card.Title>
<Card.Text>
Pariatur adipisicing aute do amet dolore cupidatat. Eu labore aliqua eiusmod commodo occaecat mollit ullamco labore minim. Minim irure fugiat anim ea sint consequat fugiat laboris id. Lorem elit irure mollit officia incididunt ea ullamco laboris excepteur amet. Cillum pariatur consequat adipisicing aute ex.
</Card.Text>
</Card.Body>
</Card>
</Col>
<Col xs={12} md={4}>
<Card className="cardHighlight p-3">
<Card.Body>
<Card.Title>
<h2>Study Now, Pay Later</h2>
</Card.Title>
<Card.Text>
Ex Lorem cillum consequat ad. Consectetur enim sunt amet sit nulla dolor exercitation est pariatur aliquip minim. Commodo velit est in id anim deserunt ullamco sint aute amet. Adipisicing est Lorem aliquip anim occaecat consequat in magna nisi occaecat consequat et. Reprehenderit elit dolore sunt labore qui.
</Card.Text>
</Card.Body>
</Card>
</Col>
<Col xs={12} md={4}>
<Card className="cardHighlight p-3">
<Card.Body>
<Card.Title>
<h2>Be Part of Our Community</h2>
</Card.Title>
<Card.Text>
Minim nostrud dolore consequat ullamco minim aliqua tempor velit amet. Officia occaecat non cillum sit incididunt id pariatur. Mollit tempor laboris commodo anim mollit magna ea reprehenderit fugiat et reprehenderit tempor. Qui ea Lorem dolor in ad nisi anim. Culpa adipisicing enim et officia exercitation adipisicing.
</Card.Text>
</Card.Body>
</Card>
</Col>
</Row>
)
}

@ -0,0 +1,26 @@
import { Col, Card } from 'react-bootstrap'
import { Link } from 'react-router-dom'
export default function Product(props){
const { breakPoint, data } = props
const { _id, name, description, price } = data
return(
<Col xs={12} md={breakPoint}>
<Card className="cardHighlight mx-2">
<Card.Body>
<Card.Title className="text-center">
<Link to={`/courses/${ _id}`}>{name}</Link>
</Card.Title>
<Card.Text>{description}</Card.Text>
</Card.Body>
<Card.Footer>
<h5 className="text-center">{price}</h5>
<Link className="btn btn-primary d-block" to={`/courses/${ _id}`}>Details</Link>
</Card.Footer>
</Card>
</Col>
)
}

@ -0,0 +1,80 @@
import React, { useState } from 'react';
const ResetPassword = () => {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [message, setMessage] = useState('');
const handleResetPassword = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
setMessage('Passwords do not match');
return;
}
try {
const token = localStorage.getItem('token'); // Replace with your actual JWT token
const response = await fetch(`${process.env.REACT_APP_API_URL}/users/reset-password`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ newPassword: password }),
});
if (response.ok) {
setMessage('Password reset successfully');
setPassword('');
setConfirmPassword('');
} else {
const errorData = await response.json();
setMessage(errorData.message);
}
} catch (error) {
setMessage('An error occurred. Please try again.');
console.error(error);
}
};
return (
<div className="container">
<h2>Reset Password</h2>
<form onSubmit={handleResetPassword}>
<div className="mb-3">
<label htmlFor="password" className="form-label">
New Password
</label>
<input
type="password"
className="form-control"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-3">
<label htmlFor="confirmPassword" className="form-label">
Confirm Password
</label>
<input
type="password"
className="form-control"
id="confirmPassword"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
{message && <div className="alert alert-danger">{message}</div>}
<button type="submit" className="btn btn-primary">
Reset Password
</button>
</form>
</div>
);
};
export default ResetPassword;

@ -0,0 +1,77 @@
import React, { useState } from 'react';
const SearchByPrice = () => {
const [minPrice, setMinPrice] = useState('');
const [maxPrice, setMaxPrice] = useState('');
const [courses, setCourses] = useState([]);
const handleMinPriceChange = (e) => {
setMinPrice(e.target.value);
};
const handleMaxPriceChange = (e) => {
setMaxPrice(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ minPrice, maxPrice }),
};
fetch(`${process.env.REACT_APP_API_URL}/courses/searchByPrice`, requestOptions)
.then((response) => response.json())
.then((data) => {
setCourses(data.courses);
})
.catch((error) => {
console.error('Error:', error);
});
};
return (
<div className="container">
<h2>Search Courses by Price Range</h2>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="minPrice" className="form-label">
Min Price:
</label>
<input
type="number"
className="form-control"
id="minPrice"
value={minPrice}
onChange={handleMinPriceChange}
/>
</div>
<div className="mb-3">
<label htmlFor="maxPrice" className="form-label">
Max Price:
</label>
<input
type="number"
className="form-control"
id="maxPrice"
value={maxPrice}
onChange={handleMaxPriceChange}
/>
</div>
<button type="submit" className="btn btn-primary">
Search
</button>
</form>
<h3>Search Results:</h3>
<ul>
{courses.map((course) => (
<li key={course.id}>{course.name}</li>
))}
</ul>
</div>
);
};
export default SearchByPrice;

@ -0,0 +1,90 @@
import React, { useState } from 'react';
const UpdateProfileForm = () => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [mobileNo, setMobileNo] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const token = localStorage.getItem('token');
const profileData = {
firstName,
lastName,
mobileNo,
};
fetch('http://localhost:4000/users/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(profileData),
})
.then(res=>res.json())
.then((response) => {
if (response._id) {
// Profile update was successful, refresh the page
window.location.reload();
} else {
throw new Error('Profile update failed');
}
})
.catch((error) => {
console.error(error);
// Handle error here
});
};
return (
<div className='container'>
<h2 className='my-4'>Update Profile</h2>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="firstName" className="form-label">
First Name
</label>
<input
type="text"
className="form-control"
id="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
<div className="mb-3">
<label htmlFor="lastName" className="form-label">
Last Name
</label>
<input
type="text"
className="form-control"
id="lastName"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
<div className="mb-3">
<label htmlFor="mobileNo" className="form-label">
Mobile Number
</label>
<input
type="text"
className="form-control"
id="mobileNo"
value={mobileNo}
onChange={(e) => setMobileNo(e.target.value)}
/>
</div>
<button type="submit" className="btn btn-primary">
Update Profile
</button>
</form>
</div>
);
};
export default UpdateProfileForm;

@ -0,0 +1,33 @@
import React, { useState, useEffect } from 'react';
import CourseCard from './CourseCard';
import CourseSearch from './CourseSearch';
export default function UserView({coursesData}) {
const [courses, setCourses] = useState([])
useEffect(() => {
const coursesArr = coursesData.map(course => {
//only render the active courses
if(course.isActive === true) {
return (
<CourseCard courseProp={course} key={course._id}/>
)
} else {
return null;
}
})
//set the courses state to the result of our map function, to bring our returned course component outside of the scope of our useEffect where our return statement below can see.
setCourses(coursesArr)
}, [coursesData])
return(
<>
<CourseSearch />
{ courses }
</>
)
}

@ -0,0 +1,25 @@
const coursesData = [
{
id: "wdc001",
name: "PHP - Laravel",
description: "Nostrud velit dolor excepteur ullamco consectetur aliquip tempor. Consectetur occaecat laborum exercitation sint reprehenderit irure nulla mollit. Do dolore sint deserunt quis ut sunt ad nulla est consectetur culpa. Est esse dolore nisi consequat nostrud id nostrud sint sint deserunt dolore.",
price: 45000,
onOffer: true
},
{
id: "wdc002",
name: "Python - Django",
description: "Eu non commodo et eu ex incididunt minim aliquip anim. Aliquip voluptate ut velit fugiat laborum. Laborum dolore anim pariatur pariatur commodo minim ut officia mollit ad ipsum ex. Laborum veniam cupidatat veniam minim occaecat veniam deserunt nulla irure. Enim elit sint magna incididunt occaecat in dolor amet dolore consectetur ad mollit. Exercitation sunt occaecat labore irure proident consectetur commodo ad anim ea tempor irure.",
price: 50000,
onOffer: true
},
{
id: "wdc003",
name: "Java - Springboot",
description: "Proident est adipisicing est deserunt cillum dolore. Fugiat incididunt quis aliquip ut aliquip est mollit officia dolor ea cupidatat velit. Consectetur aute velit aute ipsum quis. Eiusmod dolor exercitation dolor mollit duis velit aliquip dolor proident ex exercitation labore cupidatat. Eu aliquip mollit labore do.",
price: 55000,
onOffer: true
}
]
export default coursesData;

@ -0,0 +1,29 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// const name = 'John Smith';
// const user = {
// firstName: 'Jane',
// lastName: 'Smith'
// }
// function formatName(user) {
// return user.firstName + ' ' + user.lastName;
// }
// const element = <h1>Hello, {formatName(user)}</h1>
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render(element)

@ -0,0 +1,101 @@
import {useState,useEffect, useContext} from 'react';
import {Form,Button} from 'react-bootstrap';
import { Navigate, useNavigate } from 'react-router-dom';
import Swal from 'sweetalert2';
import UserContext from '../UserContext';
export default function AddCourse(){
const navigate = useNavigate();
const {user} = useContext(UserContext);
//input states
const [name,setName] = useState("");
const [description,setDescription] = useState("");
const [price,setPrice] = useState("");
function createCourse(e){
//prevent submit event's default behavior
e.preventDefault();
let token = localStorage.getItem('token');
console.log(token);
fetch(`${process.env.REACT_APP_API_URL}/courses/`,{
method: 'POST',
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
name: name,
description: description,
price: price
})
})
.then(res => res.json())
.then(data => {
//data is the response of the api/server after it's been process as JS object through our res.json() method.
console.log(data);
if(data){
Swal.fire({
icon:"success",
title: "Course Added"
})
navigate("/courses");
} else {
Swal.fire({
icon: "error",
title: "Unsuccessful Course Creation",
text: data.message
})
}
})
setName("")
setDescription("")
setPrice(0);
}
return (
(user.isAdmin === true)
?
<>
<h1 className="my-5 text-center">Add Course</h1>
<Form onSubmit={e => createCourse(e)}>
<Form.Group>
<Form.Label>Name:</Form.Label>
<Form.Control type="text" placeholder="Enter Name" required value={name} onChange={e => {setName(e.target.value)}}/>
</Form.Group>
<Form.Group>
<Form.Label>Description:</Form.Label>
<Form.Control type="text" placeholder="Enter Description" required value={description} onChange={e => {setDescription(e.target.value)}}/>
</Form.Group>
<Form.Group>
<Form.Label>Price:</Form.Label>
<Form.Control type="number" placeholder="Enter Price" required value={price} onChange={e => {setPrice(e.target.value)}}/>
</Form.Group>
<Button variant="primary" type="submit" className="my-5">Submit</Button>
</Form>
</>
:
<Navigate to="/courses" />
)
}

@ -0,0 +1,105 @@
import { useState, useEffect, useContext } from 'react';
import { Container, Card, Button, Row, Col } from 'react-bootstrap';
import { useParams, useNavigate, Link } from 'react-router-dom';
import Swal from 'sweetalert2';
import UserContext from '../UserContext';
export default function CourseView() {
// The "useParams" hook allows us to retrieve any parameter or the courseId passed via the URL
const { courseId } = useParams();
const { user } = useContext(UserContext);
// Allows us to gain access to methods that will allow us to redirect a user to a different page after enrolling a course
//an object with methods to redirect the user
const navigate = useNavigate();
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [price, setPrice] = useState(0);
const enroll = (courseId) => {
fetch(`${process.env.REACT_APP_API_URL}/users/enroll`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${ localStorage.getItem('access') }`
},
body: JSON.stringify({
courseId: courseId
})
})
.then(res => res.json())
.then(data => {
console.log(data.message);
if (data.message === 'Enrolled Successfully.') {
Swal.fire({
title: "Successfully enrolled",
icon: 'success',
text: "You have successfully enrolled for this course."
});
// The "navigate" method allows us to redirect the user to a different page and is an easier approach rather than using the "Navigate" component
navigate("/courses");
} else {
Swal.fire({
title: "Something went wrong",
icon: "error",
text: "Please try again."
});
}
});
};
useEffect(()=> {
console.log(courseId);
fetch(`${process.env.REACT_APP_API_URL}/courses/${courseId}`)
.then(res => res.json())
.then(data => {
console.log(data);
setName(data.name);
setDescription(data.description);
setPrice(data.price);
});
}, [courseId]);
return(
<Container className="mt-5">
<Row>
<Col lg={{ span: 6, offset: 3 }}>
<Card>
<Card.Body className="text-center">
<Card.Title>{name}</Card.Title>
<Card.Subtitle>Description:</Card.Subtitle>
<Card.Text>{description}</Card.Text>
<Card.Subtitle>Price:</Card.Subtitle>
<Card.Text>PhP {price}</Card.Text>
<Card.Subtitle>Class Schedule</Card.Subtitle>
<Card.Text>8 am - 5 pm</Card.Text>
{ user.id !== null ?
<Button variant="primary" block onClick={() => enroll(courseId)}>Enroll</Button>
:
<Link className="btn btn-danger btn-block" to="/login">Log in to Enroll</Link>
}
</Card.Body>
</Card>
</Col>
</Row>
</Container>
)
}

@ -0,0 +1,74 @@
import { useEffect, useState, useContext } from 'react';
import CourseCard from '../components/CourseCard';
// import coursesData from '../data/coursesData';
import UserContext from '../UserContext';
import UserView from '../components/UserView';
import AdminView from '../components/AdminView';
export default function Courses() {
const { user } = useContext(UserContext);
// Checks to see if the mock data was captured
// console.log(coursesData);
// console.log(coursesData[0]);
// State that will be used to store the courses retrieved from the database
const [courses, setCourses] = useState([]);
const fetchData = () => {
fetch(`${process.env.REACT_APP_API_URL}/courses/all`)
.then(res => res.json())
.then(data => {
console.log(data);
// Sets the "courses" state to map the data retrieved from the fetch request into several "CourseCard" components
setCourses(data);
});
}
// Retrieves the courses from the database upon initial render of the "Courses" component
useEffect(() => {
fetchData()
}, []);
// The "map" method loops through the individual course objects in our array and returns a component for each course
// Multiple components created through the map method must have a unique key that will help React JS identify which components/elements have been changed, added or removed
// Everytime the map method loops through the data, it creates a "CourseCard" component and then passes the current element in our coursesData array using the courseProp
// const courses = coursesData.map(course => {
// return (
// <CourseCard key={course.id} courseProp={course}/>
// );
// })
return(
<>
{
(user.isAdmin === true) ?
<AdminView coursesData={courses} fetchData={fetchData} />
:
<UserView coursesData={courses} />
}
</>
)
}

@ -0,0 +1,15 @@
import Banner from '../components/Banner';
export default function Error() {
const data = {
title: "404 - Not found",
content: "The page you are looking for cannot be found",
destination: "/",
label: "Back home"
}
return (
<Banner data={data}/>
)
}

@ -0,0 +1,23 @@
import Banner from '../components/Banner';
import Highlights from '../components/Highlights';
import FeaturedCourses from '../components/FeaturedCourses';
// import CourseCard from '../components/CourseCard';
export default function Home() {
const data = {
title: "Zuitt Coding Bootcamp",
content: "Opportunities for everyone, everywhere",
destination: "/courses",
label: "Enroll now!"
}
return (
<>
<Banner data={data}/>
<FeaturedCourses />
<Highlights />
</>
)
}

@ -0,0 +1,152 @@
import { useState, useEffect, useContext } from 'react';
import { Form, Button } from 'react-bootstrap';
import { Navigate } from 'react-router-dom';
import Swal from 'sweetalert2';
import UserContext from '../UserContext';
export default function Login() {
const { user, setUser } = useContext(UserContext);
// State hooks to store the values of the input fields
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// State to determine whether submit button is enabled or not
const [isActive, setIsActive] = useState(true);
function authenticate(e) {
// Prevents page redirection via form submission
e.preventDefault();
fetch(`${process.env.REACT_APP_API_URL}/users/login`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: email,
password: password
})
})
.then(res => res.json())
.then(data => {
if(data.access){
// Set the email of the authenticated user in the local storage
// Syntax
// localStorage.setItem('propertyName', value);
localStorage.setItem('token', data.access);
retrieveUserDetails(data.access);
setUser({
access: localStorage.getItem('token')
})
Swal.fire({
title: "Login Successful",
icon: "success",
text: "Welcome to Zuitt!"
});
} else {
Swal.fire({
title: "Authentication failed",
icon: "error",
text: "Check your login details and try again."
});
}
})
// Clear input fields after submission
setEmail('');
setPassword('');
}
const retrieveUserDetails = (token) => {
// The token will be sent as part of the request's header information
// We put "Bearer" in front of the token to follow implementation standards for JWTs
fetch(`${process.env.REACT_APP_API_URL}/users/details`, {
headers: {
Authorization: `Bearer ${ token }`
}
})
.then(res => res.json())
.then(data => {
console.log(data);
// localStorage.setItem('token', data.access);
// Changes the global "user" state to store the "id" and the "isAdmin" property of the user which will be used for validation across the whole application
setUser({
id: data._id,
isAdmin: data.isAdmin
});
})
};
useEffect(() => {
// Validation to enable submit button when all fields are populated and both passwords match
if(email !== '' && password !== ''){
setIsActive(true);
}else{
setIsActive(false);
}
}, [email, password]);
return (
(user.id !== null) ?
<Navigate to="/courses" />
:
<Form onSubmit={(e) => authenticate(e)}>
<h1 className="my-5 text-center">Login</h1>
<Form.Group controlId="userEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</Form.Group>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</Form.Group>
{ isActive ?
<Button variant="primary" type="submit" id="submitBtn">
Submit
</Button>
:
<Button variant="danger" type="submit" id="submitBtn" disabled>
Submit
</Button>
}
</Form>
)
}

@ -0,0 +1,29 @@
import { useContext, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import UserContext from '../UserContext';
export default function Logout() {
// Consume the UserContext object and destructure it to access the user state and unsetUser function from the context provider
const { unsetUser, setUser } = useContext(UserContext);
// Clear the localStorage of the user's information
unsetUser();
// Placing the "setUser" setter function inside of a useEffect is necessary because of updates within React JS that a state of another component cannot be updated while trying to render a different component
// By adding the useEffect, this will allow the Logout page to render first before triggering the useEffect which changes the state of our user
useEffect(() => {
// Set the user state back to it's original value
setUser({
id: null,
isAdmin: null
});
}, [])
// Navigate back to login
return (
<Navigate to='/login' />
)
}

@ -0,0 +1,71 @@
import {useState,useEffect, useContext} from 'react';
import {Row, Col} from 'react-bootstrap';
import UserContext from '../UserContext';
import { useNavigate,Navigate } from 'react-router-dom';
import ResetPassword from '../components/ResetPassword';
import UpdateProfileForm from '../components/UpdateProfile';
export default function Profile(){
const {user} = useContext(UserContext);
const [details,setDetails] = useState({})
useEffect(()=>{
fetch(`${process.env.REACT_APP_API_URL}/users/details`, {
headers: {
Authorization: `Bearer ${ localStorage.getItem('token') }`
}
})
.then(res => res.json())
.then(data => {
console.log(data)
// Set the user states values with the user details upon successful login.
if (typeof data._id !== "undefined") {
setDetails(data);
}
});
},[])
return (
// (user.email === null) ?
// <Navigate to="/courses" />
// :
(user.id === null) ?
<Navigate to="/courses" />
:
<>
<Row>
<Col className="p-5 bg-primary text-white">
<h1 className="my-5 ">Profile</h1>
{/* <h2 className="mt-3">James Dela Cruz</h2> */}
<h2 className="mt-3">{`${details.firstName} ${details.lastName}`}</h2>
<hr />
<h4>Contacts</h4>
<ul>
{/* <li>Email: {user.email}</li> */}
<li>Email: {details.email}</li>
{/* <li>Mobile No: 09266772411</li> */}
<li>Mobile No: {details.mobileNo}</li>
</ul>
</Col>
</Row>
<Row className='pt-4 mt-4'>
<Col>
<ResetPassword />
</Col>
</Row>
<Row className='pt-4 mt-4'>
<Col>
<UpdateProfileForm />
</Col>
</Row>
</>
)
}

@ -0,0 +1,164 @@
import { useState, useEffect, useContext } from 'react';
import { Form, Button } from 'react-bootstrap';
import { Navigate } from 'react-router-dom';
import UserContext from '../UserContext';
export default function Register() {
const {user} = useContext(UserContext);
// State hooks to store the values of the input fields
const [firstName,setFirstName] = useState("");
const [lastName,setLastName] = useState("");
const [email,setEmail] = useState("");
const [mobileNo,setMobileNo] = useState("");
const [password,setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
// State to determine whether submit button is enabled or not
const [isActive, setIsActive] = useState(false);
// Check if values are successfully binded
console.log(firstName);
console.log(lastName);
console.log(email);
console.log(mobileNo);
console.log(password);
console.log(confirmPassword)
function registerUser(e) {
// Prevents page redirection via form submission
e.preventDefault();
fetch(`${process.env.REACT_APP_API_URL}/users/register`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
firstName: firstName,
lastName: lastName,
email: email,
mobileNo: mobileNo,
password: password
})
})
.then(res => res.json())
.then(data => {
//data is the response of the api/server after it's been process as JS object through our res.json() method.
console.log(data);
//data will only contain an email property if we can properly save our user.
if(data){
setFirstName('');
setLastName('');
setEmail('');
setMobileNo('');
setPassword('');
setConfirmPassword('');
alert("Thank you for registering!")
} else {
alert("Please try again later.")
}
})
}
useEffect(()=>{
if((firstName !== "" && lastName !== "" && email !== "" && mobileNo !== "" && password !=="" && confirmPassword !=="") && (password === confirmPassword) && (mobileNo.length === 11)){
setIsActive(true)
} else {
setIsActive(false)
}
},[firstName,lastName,email,mobileNo,password,confirmPassword])
return (
(user.id !== null) ?
<Navigate to="/courses" />
:
<Form onSubmit={(e) => registerUser(e)}>
<h1 className="my-5 text-center">Register</h1>
<Form.Group>
<Form.Label>First Name:</Form.Label>
<Form.Control
type="text"
placeholder="Enter First Name"
required
value={firstName}
onChange={e => {setFirstName(e.target.value)}}
/>
</Form.Group>
<Form.Group>
<Form.Label>Last Name:</Form.Label>
<Form.Control
type="text"
placeholder="Enter Last Name"
required
value={lastName}
onChange={e => {setLastName(e.target.value)}}/>
</Form.Group>
<Form.Group>
<Form.Label>Email:</Form.Label>
<Form.Control
type="email"
placeholder="Enter Email"
required
value={email}
onChange={e => {setEmail(e.target.value)}}/>
</Form.Group>
<Form.Group>
<Form.Label>Mobile No:</Form.Label>
<Form.Control
type="number"
placeholder="Enter 11 Digit No."
required
value={mobileNo}
onChange={e => {setMobileNo(e.target.value)}}/>
</Form.Group>
<Form.Group>
<Form.Label>Password:</Form.Label>
<Form.Control
type="password"
placeholder="Enter Password"
required
value={password}
onChange={e => {setPassword(e.target.value)}}/>
</Form.Group>
<Form.Group>
<Form.Label>Confirm Password:</Form.Label>
<Form.Control
type="password"
placeholder="Confirm Password"
required
value={confirmPassword}
onChange={e => {setConfirmPassword(e.target.value)}}/>
</Form.Group>
{
isActive
? <Button variant="primary" type="submit">Submit</Button>
: <Button variant="primary" disabled>Submit</Button>
}
</Form>
)
}

@ -0,0 +1,2 @@
PORT=4000
CONNECTION_STRING=<Mongo-DB-Atlas-connection-string>/bookingAPI?retryWrites=true&w=majority

1
server/.gitignore vendored

@ -0,0 +1 @@
/node_modules

@ -0,0 +1,98 @@
// [Section] JSON Web Tokens
const jwt = require("jsonwebtoken");
// [Section] Secret Keyword
const secret = "CourseBookingAPI";
// [Section] Token creation
module.exports.createAccessToken = (user) => {
const data = {
id : user._id,
email : user.email,
isAdmin : user.isAdmin
};
return jwt.sign(data, secret, {});
};
//[Section] Token Verification
/*
- Analogy
Receive the gift and open the lock to verify if the the sender is legitimate and the gift was not tampered with
*/
module.exports.verify = (req, res, next) => {
console.log(req.headers.authorization);
//req.headers.authorization contains sensitive data and especially our token
let token = req.headers.authorization;
//This if statement will first check IF token variable contains undefined or a proper jwt. If it is undefined, we will check token's data type with typeof, then send a message to the client.
if(typeof token === "undefined"){
return res.send({auth: "Failed. No Token"});
} else {
console.log(token);
token = token.slice(7, token.length);
console.log(token);
//[SECTION] Token decryption
/*
- Analogy
Open the gift and get the content
*/
// Validate the token using the "verify" method decrypting the token using the secret code
jwt.verify(token, secret, function(err, decodedToken){
if(err){
return res.send({
auth: "Failed",
message: err.message
});
} else {
console.log(decodedToken);//contains the data from our token
req.user = decodedToken
next();
}
})
}
};
//[Section] verifyAdmin will also be used a middleware.
module.exports.verifyAdmin = (req, res, next) => {
if(req.user.isAdmin){
next();
} else {
return res.send({
auth: "Failed",
message: "Action Forbidden"
})
}
}

@ -0,0 +1,245 @@
//[SECTION] Dependencies and Modules
const Course = require("../models/Course");
const User = require("../models/User");
//[SECTION] Create a new course
module.exports.addCourse = (req, res) => {
let newCourse = new Course({
name : req.body.name,
description : req.body.description,
price : req.body.price
});
return newCourse.save().then((course, error) => {
// Course creation successful
if (error) {
return res.send(false);
// Course creation failed
} else {
return res.send(true);
}
})
.catch(err => res.send(err))
}
//[SECTION] Retrieve all courses
module.exports.getAllCourses = (req, res) => {
return Course.find({}).then(result => {
return res.send(result);
})
.catch(err => res.send(err))
};
//[SECTION] Retrieve all ACTIVE courses
module.exports.getAllActive = (req, res) => {
return Course.find({ isActive : true }).then(result => {
return res.send(result);
})
.catch(err => res.send(err))
};
//[SECTION] Retrieving a specific course
module.exports.getCourse = (req, res) => {
return Course.findById(req.params.courseId).then(result => {
return res.send(result);
})
.catch(err => res.send(err))
};
//[SECTION] Update a course
module.exports.updateCourse = (req, res) => {
// Specify the fields/properties of the document to be updated
let updatedCourse = {
name : req.body.name,
description : req.body.description,
price : req.body.price
};
// Syntax
// findByIdAndUpdate(document ID, updatesToBeApplied)
return Course.findByIdAndUpdate(req.params.courseId, updatedCourse).then((course, error) => {
// Course not updated
if (error) {
return res.send(false);
// Course updated successfully
} else {
return res.send(true);
}
})
.catch(err => res.send(err))
};
//SECTION] Archive a course
module.exports.archiveCourse = (req, res) => {
let updateActiveField = {
isActive: false
}
return Course.findByIdAndUpdate(req.params.courseId, updateActiveField)
.then((course, error) => {
//course archived successfully
if(error){
return res.send(false)
// failed
} else {
return res.send(true)
}
})
.catch(err => res.send(err))
};
//[SECTION] Activate a course
module.exports.activateCourse = (req, res) => {
let updateActiveField = {
isActive: true
}
return Course.findByIdAndUpdate(req.params.courseId, updateActiveField)
.then((course, error) => {
//course archived successfully
if(error){
return res.send(false)
// failed
} else {
return res.send(true)
}
})
.catch(err => res.send(err))
};
//ChatGPT Generated Code
module.exports.searchCoursesByName = async (req, res) => {
try {
const { courseName } = req.body;
// Use a regular expression to perform a case-insensitive search
const courses = await Course.find({
name: { $regex: courseName, $options: 'i' }
});
res.json(courses);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
// Controller to get the list of emails of users enrolled in a course
//Solution
module.exports.getEmailsOfEnrolledUsers = async (req, res) => {
const courseId = req.params.courseId;
try {
// Find the course by courseId
const course = await Course.findById(courseId);
if (!course) {
return res.status(404).json({ message: 'Course not found' });
}
// Get the userIds of enrolled users from the course
const userIds = course.enrollees.map(enrollee => enrollee.userId);
// Find the users with matching userIds
const enrolledUsers = await User.find({ _id: { $in: userIds } });
// Extract the emails from the enrolled users
const emails = enrolledUsers.map(user => user.email);
res.status(200).json({ emails });
} catch (error) {
res.status(500).json({ message: 'An error occurred while retrieving enrolled users' });
}
};
//Given to students:
/*
const getEmailsOfEnrolledUsers = async (req, res) => {
const courseId = req.body.courseId;
try {
// Find the course by courseId
const course = await Course.findById(courseId);
if (!course) {
return res.status(404).json({ message: 'Course not found' });
}
// Get the userIds of enrolled users from the course
const userIds = course.enrollees.map(enrollee => enrollee.userId);
// Find the users with matching userIds
const enrolledUsers = await User.find({ _id: { $in: users } });
// Extract the emails from the enrolled users
const emails = enrolledStudents.forEach(user => user.email);
res.status(200).json({ userEmails });
} catch (error) {
res.status(500).json({ message: 'An error occurred while retrieving enrolled users' });
}
};
*/
//[ACTIVITY]
exports.searchCoursesByPriceRange = async (req, res) => {
try {
const { minPrice, maxPrice } = req.body;
// Find courses within the price range
const courses = await Course.find({
price: { $gte: minPrice, $lte: maxPrice }
});
res.status(200).json({ courses });
} catch (error) {
res.status(500).json({ error: 'An error occurred while searching for courses' });
}
};

@ -0,0 +1,279 @@
//[SECTION] Dependencies and Modules
const User = require("../models/User");
const Course = require("../models/Course");
const bcrypt = require('bcrypt');
const auth = require("../auth");
//[SECTION] Check if the email already exists
module.exports.checkEmailExists = (reqBody) => {
// The result is sent back to the frontend via the "then" method found in the route file
return User.find({email : reqBody.email}).then(result => {
// The "find" method returns a record if a match is found
if (result.length > 0) {
return true;
// No duplicate email found
// The user is not yet registered in the database
} else {
return false;
}
})
.catch(err => res.send(err))
};
//[SECTION] User registration
module.exports.registerUser = (reqBody) => {
// Creates a variable "newUser" and instantiates a new "User" object using the mongoose model
// Uses the information from the request body to provide all the necessary information
let newUser = new User({
firstName : reqBody.firstName,
lastName : reqBody.lastName,
email : reqBody.email,
mobileNo : reqBody.mobileNo,
password : bcrypt.hashSync(reqBody.password, 10)
})
// Saves the created object to our database
return newUser.save().then((user, error) => {
// User registration failed
if (error) {
return false;
// User registration successful
} else {
return true;
}
})
.catch(err => err)
};
//[SECTION] User authentication
module.exports.loginUser = (req, res) => {
User.findOne({ email : req.body.email} ).then(result => {
console.log(result);
// User does not exist
if(result == null){
return res.send(false);
// User exists
} else {
const isPasswordCorrect = bcrypt.compareSync(req.body.password, result.password);
// If the passwords match/result of the above code is true
if (isPasswordCorrect) {
return res.send({ access : auth.createAccessToken(result) })
// Passwords do not match
} else {
return res.send(false);
}
}
})
.catch(err => res.send(err))
};
//[SECTION] Retrieve user details
module.exports.getProfile = (req, res) => {
return User.findById(req.user.id)
.then(result => {
result.password = "";
return res.send(result);
})
.catch(err => res.send(err))
};
//[SECTION] Enroll a registered User
module.exports.enroll = async (req, res) => {
console.log(req.user.id) //the user's id from the decoded token after verify()
console.log(req.body.courseId) //the course from our request body
//process stops here and sends response IF user is an admin
if(req.user.isAdmin){
return res.send("Action Forbidden")
}
let isUserUpdated = await User.findById(req.user.id).then(user => {
let newEnrollment = {
courseId: req.body.courseId,
courseName: req.body.courseName,
courseDescription: req.body.courseDescription,
coursePrice: req.body.coursePrice
}
user.enrollments.push(newEnrollment);
return user.save().then(user => true).catch(err => err.message)
})
if(isUserUpdated !== true) {
return res.send({message: isUserUpdated})
}
let isCourseUpdated = await Course.findById(req.body.courseId).then(course => {
let enrollee = {
userId: req.user.id
}
course.enrollees.push(enrollee);
return course.save().then(course => true).catch(err => err.message)
})
if(isCourseUpdated !== true) {
return res.send({ message: isCourseUpdated})
}
if(isUserUpdated && isCourseUpdated) {
return res.send({ message: "Enrolled Successfully."})
}
}
//[ACTIVITY] Getting user's enrolled courses
module.exports.getEnrollments = (req, res) => {
User.findById(req.user.id)
.then(result => res.send(result.enrollments))
.catch(err => res.send(err))
}
//ChatGPT Generated
//[SECTION] Reset Password
module.exports.resetPassword = async (req, res) => {
try {
//console.log(req.user)
//console.log(req.body)
const { newPassword } = req.body;
const { id } = req.user; // Extracting user ID from the authorization header
// Hashing the new password
const hashedPassword = await bcrypt.hash(newPassword, 10);
// Updating the user's password in the database
await User.findByIdAndUpdate(id, { password: hashedPassword });
// Sending a success response
res.status(200).send({ message: 'Password reset successfully' });
} catch (error) {
console.error(error);
res.status(500).send({ message: 'Internal server error' });
}
};
//[SECTION] Reset Profile
module.exports.updateProfile = async (req, res) => {
try {
console.log(req.user);
console.log(req.body);
// Get the user ID from the authenticated token
const userId = req.user.id;
// Retrieve the updated profile information from the request body
const { firstName, lastName, mobileNo } = req.body;
// Update the user's profile in the database
const updatedUser = await User.findByIdAndUpdate(
userId,
{ firstName, lastName, mobileNo },
{ new: true }
);
res.send(updatedUser);
} catch (error) {
console.error(error);
res.status(500).send({ message: 'Failed to update profile' });
}
}
//[ACTIVITY] Update Enrollment Status
module.exports.updateEnrollmentStatus = async (req, res) => {
try {
const { userId, courseId, status } = req.body;
// Find the user and update the enrollment status
const user = await User.findById(userId);
// Find the enrollment for the course in the user's enrollments array
const enrollment = user.enrollments.find((enrollment) => enrollment.courseId === courseId);
if (!enrollment) {
return res.status(404).json({ error: 'Enrollment not found' });
}
enrollment.status = status;
// Save the updated user document
await user.save();
res.status(200).json({ message: 'Enrollment status updated successfully' });
} catch (error) {
res.status(500).json({ error: 'An error occurred while updating the enrollment status' });
}
};
//[ACTIVITY] Update user as admin controller
exports.updateUserAsAdmin = async (req, res) => {
try {
const { userId } = req.body;
// Find the user and update isAdmin flag
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
user.isAdmin = true;
// Save the updated user document
await user.save();
res.status(200).json({ message: 'User updated as admin successfully' });
} catch (error) {
res.status(500).json({ error: 'An error occurred while updating the user as admin' });
}
};

@ -0,0 +1,45 @@
//[SECTION] Dependencies and Modules
const dotenv = require('dotenv').config();
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const userRoutes = require("./routes/user");
const courseRoutes = require("./routes/course");
//[SECTION] Environment Setup
const port = process.env.PORT;
//[SECTION] Server Setup
const app = express();
app.use(cors())
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//[SECTION] Database Connection
mongoose.connect(process.env.CONNECTION_STRING, {
useNewUrlParser: true,
useUnifiedTopology: true
});
mongoose.connection.once('open', () => console.log('Now connected to MongoDB Atlas.'));
//[SECTION] Backend Routes
//http://localhost:4000/users
app.use("/users", userRoutes);
//http://localhost:4000/courses
app.use("/courses", courseRoutes);
//[SECTION] Server Gateway Response
if(require.main === module) {
app.listen( process.env.PORT || port, () => {
console.log(`API is now online on port ${ process.env.PORT || port }`)
});
}
module.exports = app;

@ -0,0 +1,41 @@
//[SECTION] Dependencies and Modules
const mongoose = require('mongoose');
//[SECTION] Schema/Blueprint
const courseSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'is Required']
},
description: {
type: String,
required: [true, 'is Required']
},
price: {
type: Number,
required: [true, 'Course Price is Required']
},
isActive: {
type: Boolean,
default: true
},
createdOn: {
type: Date,
default: new Date()
},
enrollees: [
{
userId: {
type: String,
required: [true, 'Student ID is Required']
},
enrolledOn: {
type: Date,
default: new Date()
}
}
]
});
//[SECTION] Model
module.exports = mongoose.model('Course', courseSchema);

@ -0,0 +1,49 @@
//[SECTION] Modules and Dependencies
const mongoose = require('mongoose');
//[SECTION] Schema/Blueprint
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'First Name is Required']
},
lastName: {
type: String,
required: [true, 'Last Name is Required']
},
email: {
type: String,
required: [true, 'Email is Required']
},
password: {
type: String,
required: [true, 'Password is Required']
},
isAdmin: {
type: Boolean,
default: false
},
mobileNo: {
type: String,
required: [true, 'Mobile Number is Required']
},
enrollments: [
{
courseId: {
type: String,
required: [true, 'Subject ID is Required']
},
enrolledOn: {
type: Date,
default: new Date()
},
status: {
type: String,
default: 'Enrolled'
}
}
]
});
//[SECTION] Model
module.exports = mongoose.model('User', userSchema);

@ -0,0 +1,23 @@
{
"name": "s43-47",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index",
"dev": "nodemon index"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"mongoose": "^7.2.0",
"nodemon": "^2.0.22"
}
}

@ -0,0 +1,56 @@
//[SECTION] Dependencies and Modules
const express = require('express');
const courseController = require("../controllers/course");
const auth = require("../auth")
const {verify, verifyAdmin} = auth;
//[SECTION] Routing Component
const router = express.Router();
//[ACTIVITY] create a course POST
router.post("/", verify, verifyAdmin, courseController.addCourse);
//[SECTION] Route for retrieving all the courses
router.get("/all", courseController.getAllCourses);
//[SECTION] Route for retrieving all the ACTIVE courses for all users
// Middleware for verifying JWT is not required because users who aren't logged in should also be able to view the courses
router.get("/", courseController.getAllActive);
//[SECTION] Route for Search Course by Name
router.post('/searchByName', courseController.searchCoursesByName);
//[ACTIVITY] Search Courses By Price Range
router.post('/searchByPrice', courseController.searchCoursesByPriceRange);
//[SECTION] Route for retrieving a specific course
router.get("/:courseId", courseController.getCourse);
//[SECTION] Route for updating a course (Admin)
router.put("/:courseId", verify, verifyAdmin, courseController.updateCourse);
// [SECTION]Route to get the emails of users enrolled in a course
// Solution
router.get('/:courseId/enrolled-users', courseController.getEmailsOfEnrolledUsers);
//Given to students:
// router.post('/:courseId/enrolled-users', getEmailsOfEnrolledUsers;
//[ACTIVITY] Route to archiving a course (Admin)
router.put("/:courseId/archive", verify, verifyAdmin, courseController.archiveCourse);
//[ACTIVITY] Route to activating a course (Admin)
router.put("/:courseId/activate", verify, verifyAdmin, courseController.activateCourse);
// Allows us to export the "router" object that will be accessed in our "index.js" file
module.exports = router;

@ -0,0 +1,71 @@
//[SECTION] Dependencies and Modules
const express = require('express');
const userController = require("../controllers/user");
const auth = require("../auth")
const {verify, verifyAdmin} = auth;
//[SECTION] Routing Component
const router = express.Router();
//[SECTION] Routes - POST
router.post("/checkEmail", (req, res) => {
userController.checkEmailExists(req.body).then(resultFromController => res.send(resultFromController));
})
//[SECTION] Route for user registration
router.post("/register", (req, res) => {
userController.registerUser(req.body).then(resultFromController => res.send(resultFromController));
});
//[SECTION] Route for user authentication
router.post("/login", userController.loginUser);
//[ACTIVITY] Route for retrieving user details
//router.post("/details", verify, userController.getProfile);
//Refactor
router.get("/details", verify, userController.getProfile);
//[SECTION] Route to enroll user to a course
router.post('/enroll', verify, userController.enroll);
//[ACTIVITY] Get Logged User's Enrollments
router.get('/getEnrollments', verify, userController.getEnrollments)
//ChatGPT Generated Codes
//[SECTION] Reset Password
router.put('/reset-password', verify, userController.resetPassword);
//[SECTION] Update Profile
router.put('/profile', verify, userController.updateProfile);
//[ACTIVITY] Update enrollment status route
router.put('/enrollmentStatusUpdate', userController.updateEnrollmentStatus);
//[ACTIVITY] Update Admin route
router.put('/updateAdmin', verify, verifyAdmin, userController.updateUserAsAdmin);
//[SECTION] Export Route System
module.exports = router;
Loading…
Cancel
Save