Akwya is an Online Learning System that works with professional instructors to offer online courses, through which individuals can attend pre-recorded courses online. These courses provide quizzes for the user to test his/her understanding of the course; and upon completion, certifications shall be acquired.
- This Website was created as a group project for the
CSEN704 Course
(Advanced Computer Lab). - After the COVID-19 pandemic that started in late 2019, learning methods have been shifted worldwide to online methodologies. Since then, online courses have been more convenient to most students so the demand for websites like Akwya has been on the rise.
- Shaping the ultimate online learning experience was a very fruitful challenge for us.
- The purpose of creating such project was to practice
- The MERN (MongoDB , ExpressJS , ReactJS, NodeJS) Technology Stack.
- Agile software development
- Up-to-date backend and frontend development techniques
The current build of the project contains no bugs, errors or malfunctions, except for the following:
- Logging in after resetting password requires hashing the password
- Some UI elements need to look more consistent
Future releases will contain additional features and unit tests.
- We used MVC (Model View Controller). For more info visit https://en.wikipedia.org/wiki/Model–view–controller
- Standard coding style is used to ensure our project's readability and maintainability to developers at any level.
- Local variables were declared using the standard naming convention, camel case.
- Prettier extension in Visual Studio Code was used in important files to organize spacing and readability.
This system is used by the following:
- Admins: Administrators who run the website and are in control of everything.
- Instructors: Highly qualified instructors bring a wealth of knowledge and experience to our courses and provide students with exceptional material.
- Individual Trainees: Regular registered user.
- Corporate Trainees: User who is part of partnered company.
- Guests: Unregistered user who can only preview features without using them.
-
Different Access levels:
-
Admin:
- Add new users (other Admins, Corporate Trainees, Instructors) to the System
- Responsible for promotions
- Grant access to courses
- Resolve Problems
- Responsible for financial issues
-
Instructors:
- Manage their course content
- Edit their profile
- Create Quizzes for their courses
- Add promotions to their courses
- View their own and their courses' ratings and reviews
- View their monthly profit
- Report Problems
-
Trainees:
- Corporate trainees and individual trainees have similarities in the following features:
- Explore popular courses
- Register for courses
- Take Quiz for the Course
- View his/her progress in the course
- Rate the courses and the instructors
- Take notes while attending the online lecture
- Report Problems
- Differences in the following features:
- Individual trainees are responsible for their payments using the website's wallet system or credit card
- Individual trainees can request refunds and drop the course
- Corporate trainees can request access to courses
- Corporate trainees and individual trainees have similarities in the following features:
-
Guest:
Users with limited functionalities where they can view features but not fully make use of them - Explore popular courses -
Users:
- All users have the following features available:
- Search for courses
- Filter out courses
- All users have the following features available:
-
-
Fullscreen mode
-
Email Handler
- Sends Certificate upon Course Completion
- Used in Recovery of Forgotten Password
-
Well Planned Information Architecture
-
Well-Formatted Content That Is Easy to Scan
- Visibility
- Consistent Layout and Formatting
-
Fast Load Times
- Instant Feedback
- Fast & Responsive
-
Browser Consistency
-
Effective Navigation
-
Good Error Handling
Find below code snippets for how an admin can add a trainee to the system
import mongoose from 'mongoose'; //to connect with mongodb
const traineeSchema = mongoose.Schema({
username: String,
password: String,
email: String,
fname: String,
lname: String,
gender: String,
traineetype: String, //corporate or individual
courses: [{ courseid: String, progress: Number,courseName:String }],
country: String,
wallet: Number
}, { timestamps: true }
);
const trainee = mongoose.model('trainee', traineeSchema);
export default trainee;
import trainee from "../models/trainee.js"
export const createTrainee= async(req,res) => {
const {username,password,email}=req.body
const wallet=0;
const type = 'corporate'
//bykon corporate
try {
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(password, salt);
const newTrainee = await trainee.create({username:username,password:hashedPassword,email:email,traineetype:type,wallet:wallet});
const token = createToken(username);
res.cookie('jwt', token, { httpOnly: true, maxAge: maxAge * 1000 });
res.status(200).json(newTrainee)
} catch (error) {
res.status(400).json({error: error.message})
}
}
import express from "express";
const router =express.Router()
import {createTrainee} from "../controllers/adminController.js"
router.post('/newTrainee',createTrainee)
app.use('/admin',adminRoutes);
import traineeRoutes from './routes/trainee.js';
//connect to db
const PORT = process.env.PORT || 8000;
mongoose.connect(process.env.MONG_URI, { useNewURLParser: true, useUnifiedTopology: true })
.then(() => app.listen(PORT, () => console.log(`Server running on port: ${PORT}`)))
.catch((error) => console.log(error.message));
import { useState } from 'react'
import Swal from "sweetalert2";
import './admin.css'
const AddTrainee = () => {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [email, setEmail] = useState('')
const [error, setError] = useState(null)
const handleSubmit = async (e) => {
e.preventDefault()
const trainee = {username, password,email}
# Example of using an API endpoint
const respnse= await fetch('/admin/newTrainee', {
method: 'POST',
body: JSON.stringify(trainee) ,
headers: {
'Content-Type' : 'application/json'
}
})
const json= await respnse.json()
if(!respnse.ok){
setError(json.error)
}
if(respnse.ok){
console.log("new Trainee added")
Swal.fire({
title: 'New Trainee added!',
icon: 'success',
confirmButtonColor: '#38a53e',
confirmButtonText: 'OK'
})
setError(null)
setUsername('')
setPassword('')
setEmail('')
}
}
#Example of rendering a React component
return (
<div class="ganb">
<form className="create" onSubmit={handleSubmit}>
<h1>New Trainee</h1>
<div class="txt_field">
<input
type="username"
onChange={(e) => setUsername(e.target.value)}
value={username}
required
/>
<label>Username</label>
</div>
<div class="txt_field">
<input
type="email"
onChange={(e) => setEmail(e.target.value)}
value={email}
required/>
<label>Email</label>
</div>
<div class="txt_field">
<input
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
required/>
<label>Password</label>
</div>
<button>Add New Trainee</button>
{error && <div className="error">{error}</div>}
</form>
</div>
)
}
export default AddTrainee
cd server
npm Install
npm install @stripe/react-stripe-js
npm install @stripe/stripe-jest
npm install @testing-library/jest-dom
npm install @testing-library/react
npm install @testing-library/user-event axios bootstrap express react react-bootstrap react-dom react-paypal-button-v2 react-router-dom react-scripts react-smart-payment-buttons stripe
npm install jsonwebtoken
npm install bcrypt
npm install cookie-parser
npm install dotenv
npm install sweetalert
npm install sweetalert2
cd client
npm Install
npm install @stripe/react-stripe-js
npm install @stripe/stripe-jest
npm install @testing-library/jest-dom
npm install @testing-library/react
npm install @testing-library/user-event axios bootstrap express react react-bootstrap react-dom react-paypal-button-v2 react-router-dom react-scripts react-smart-payment-buttons stripe
npm install styled-components
To run this project, you will need to add the following environment variables to your .env file
- PORT=
Port number to be used
- MONG_URI=
MONG_URI
- AUTH_EMAIL=
Mongo DB Cluster email
- AUTH_PASS=
Mongo DB Cluster password
- token=
Authentication token
Clone the project
git clone https://github.com/Advanced-Computer-Lab-2022/Akwya.git
Go to the project directory
cd Akwya
Install dependencies
```refer to installation section
```Split Terminal
Start the server for the back end
cd server
npm start
Start the client for the front end
cd client
npm start
POST admin/newTrainee/
Request body
Parameter | Type | Description |
---|---|---|
username |
string |
trainee's username |
password |
string |
trainee's initially assigned password |
email |
string |
trainee's email |
Example http://localhost:PORT/admin/newTrainee/
Request
{ "username":"Farah", "password":"Password123", "email":"farah@hotmail.com" }Response
{
"username": "Farah",
"password": "$2b$10$wBkOfHhfB/bzaCWSZekwo.bfSvy5AtR5kM3AcVZvnMZLV6jj9CdfK",
"email": "farah@hotmail.com",
"traineetype": "corporate",
"wallet": 0,
"_id": "63b86ec2d563ccbfe98c485d",
"courses": [],
"createdAt": "2023-01-06T18:56:02.476Z",
"updatedAt": "2023-01-06T18:56:02.476Z",
"__v": 0
}
GET /trainee/getWallet/:id
Example http://localhost:PORT/trainee/getWallet/6396424da56263086dde2489
Response
{
"_id": "6396424da56263086dde2489",
"wallet": 24377
}
PATCH /trainee/:id/rateCourse
Example http://localhost:PORT/trainee/63822dd272c5cd10a8568cfb/rateCourse?rating=4&review="My favorite!"
Response
{ "acknowledged": true, "modifiedCount": 1, "upsertedId": null, "upsertedCount": 0, "matchedCount": 1 } GET /instructor/viewEmail/:id
Example http://localhost:PORT/instructor/viewEmail/6380fb3a0e91fe67a1baf48c
Response
[
{
"_id": "6380fb3a0e91fe67a1baf48c",
"email": "ayten@hotmail.com"
}
]
app.use('/user', userRoutes); app.use('/course', courseRoutes); app.use('/admin',adminRoutes); app.use('/instructor', instructorRoutes); app.use('/trainee', traineeRoutes); app.use('/Quiz', quizRoutes);
router.get('/',getAdmins) router.get('/refundTrainee',refundTrainee) router.post('/newAdmin',createAdmin) router.post('/newInstructor',createInstructor) router.post('/newTrainee',createTrainee) router.get('/courseDiscountAdmin/:id',courseDiscountAdmin) router.get('/promotionFound/:id', promotionFound) router.get('/GrantAccess/:TraineeID/:CourseID', grantAccess) router.get('/RequestAccess/:TraineeID/:CourseID', requestAccess) router.get('/viewRequests/', viewRequests) router.get('/viewRefunds/', viewRefunds)
router.get('/viewCourseDeets',viewCourses) router.get('/viewCoursePrices',viewCoursesPrices) router.get('/filterCoursesOnSubjAndRating/:id/:title',filterCoursesOnSubjAndRating) router.get('/filterCoursesByPrice', filterCoursesByPrice) router.get('/', getCourses) router.get('/search/:search',searchCourse) router.get('/viewACourse/:titlee',viewACourse) router.post('/report',reportAProblem) router.post('/followUp',followUpOnAProblem) router.post('/followUp2',adminFollowUpOnAProblem) router.post('/problemState',problemState) router.post('/:id',createCourse) router.get('/getProblems/:id',getProblems) router.get('/getAllProblems',getAllProblems) router.get('/getMyCourseName/:id',getMyCourseName) router.delete('/:id',deleteCourse) router.delete('/',deleteAllCourses) router.patch('/:id',(req,res)=>{ res.json({mssg:'update a guest'}) }) router.get('/courseDiscount/:id',courseDiscount)
router.get('/viewCoursestitleI/:id', viewCoursestitleI ) router.get('/filterMyCoursesByPrice/:id',filterCoursesByPriceI) router.get('/filterMyCoursesBySubject/:id',filterCoursesBySubjectI) router.get('/search/:id/:search',searchCourseI) router.get('/filterCoursesByRatingAndSubject',filterCoursesByRatingAndSubject) router.post('/:id', createCourse ) router.delete('/', deleteAllInstructors ) router.post('/addVideo/:courseID', addVideo ) router.post('/addPreview/:courseID', addPreview ) router.get('/viewVideos/:courseID', viewVideos ) router.get('/viewPreview/:courseID', viewPreview ) router.get('/CanViewVideos/:courseID/:instructorID', CanViewVideos ) router.get('/viewEmail/:id', viewEmail ) router.get('/editEmail/:id', editEmail ) router.get('/viewRating/:id', ViewRating ) router.get('/changePassword/:id', changePassword) router.get('/checkPassword/:id', checkPassword) router.get('/viewBio/:id', viewBio ) router.get('/editBio/:id', editBio ) router.get('/getRatings/:id', getRatings ) router.get('/:id/myRating', ViewRating) router.get('/notFirst/:id', notFirst) router.get('/moneyOwed/:id', moneyOwed)
router.post('/create',createQuiz); router.get('/TakeQuiz/:id',getQuiz) router.post('/TakeQuiz/submitQuiz',submitQuiz) router.post('/TakeQuiz/resetQuiz/:id',resetQuiz) router.get('/TakeQuiz/getMyCourseName/:id',getMyCourseName) router.get('/TakeQuiz/viewGrade/:id/:level',viewGrade) router.get('/TakeQuiz/viewQuestionGrade/:id/:quiz',viewQuestionGrade)
router.patch('/:id/rateCourse',rateCourse) router.patch('/:id/rateInstructor',rateInstructor) router.get('/',getTrainee) router.get('/register/:courseID/:traineeID',registerCourse) router.get('/drop/:courseID/:traineeID',dropCourse) router.get('/isRegistered/:courseID/:traineeID',isRegistered) router.get('/changePassword/:id',changePassword) router.get('/checkPassword/:id',checkPassword) router.get('/resetPassword/', resetPassword) router.get('/getWallet/:id', getWallet) router.get('/videoCount/:CourseID', videoCount) router.get('/sendCertificate/:TraineeID/:CourseID', sendCertificate) router.post('/signup', signUp); router.post('/login', login); router.get('/logout', logout); router.post('/stripe', generatePayment); router.post('/confirm-payment', confirmPayment); router.get('/userWatchVideo/:TraineeID/:VideoID', userWatchVideo); router.get('/getUserProgress/:TraineeID/:CourseID', getUserProgress); router.get('/refund/:TraineeID/:CourseID', refundCourse); router.get('/requestRefund/:TraineeID/:CourseID', requestRefund); router.get('/myCourses/:TraineeID', myCourses); router.get('/registerCourseWallet/:courseID/:traineeID',registerCourseWallet);
router.get('/',getAllUser) router.get('/:id',getUser) router.post('/',createUser) router.delete('/:id',deleteUser) router.patch('/:id',updateUser)
app.post('/confirm-payment') app.post('/stripe')
- Open Postman and create a new workspace
- Set your HTTP request (GET/POST/..)
- In the request URL field, input link
- Click Send You will see 200 OK Message The response will be shown below according to your requested link
Examples
Useful Resources that were used in the development of this project:
Stripe API:
Node.js
https://www.youtube.com/playlist?list=PLZlA0Gpn_vH_uZs4vJMIhcinABSTUH2bY
Express.js
https://www.youtube.com/watch?v=fgTGADljAeg
ES6:
https://www.youtube.com/playlist?list=PLZlA0Gpn_vH-0FlQnruw2rd1HuiYJHHkm
React introduction:
https://www.youtube.com/playlist?list=PLZlA0Gpn_vH_NT5zPVp18nGe_W9LqBDQK
React Hooks -- functional components
https://www.youtube.com/playlist?list=PLZlA0Gpn_vH8EtggFGERCwMY5u5hOjf-h
JWT authentication:
https://www.youtube.com/watch?v=mbsmsi7l3r4
https://www.youtube.com/watch?v=-RCnNyD0L-s
https://dev.to/salarc123/mern-stack-authentication-tutorial-part-1-the-backend-1c57
Copyright (c) 2023 Advanced-Computer-Lab-2022/Akwya
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Contributions are always welcome!
Please adhere to this project's code of conduct
.
-
Contributors:
-
Special thanks to
- Noha Hamid
- Hadwa Pasha
- Nada Ibrahim
For support, email akwyaawy@hotmail.com