An implementation of a full-stack web application. The application is a platform to manage an online learning platform that serves 4 different types of users : Individual/Corporate Trainees , Instructors and Admins. It was built using the MERN
stack.
- The project is currently in development.
- The admin need some improvements
- Course Page and API' needs Pagagination.
- The Unit tests needs modifications.
- A CI/CD pipeline needs to be migrated to Jenkins.
- A caching layer needs to be added to the application.
- A message broker needs to be added to the application to handle asynchronous tasks such as sending emails and notifications.
The code style is enforced using eslint
and prettier
. The code style is enforced using pre-commit
hooks and pre-commit github action.
The pre-commit hook is managed by pre-commit. It is a versatile way of managing the pre-commit tool but it also permits you to run the script on arbitrary files without committing. The module will take charge of installing your required dependencies (such as code-style tools: prettier, eslint, etc.) and will run them on the files you want to commit.
Install pre-commit package by running
> pip install pre-commit
Once installed, run the following for a one-time setup
> pre-commit install
Afterwards, the hook should run the next commit you will attempt!
- React
- Redux
- Node.js
- Express
- MongoDB
- Mongoose
- Jest
- Swagger
- Material-UI
- Stripe
- Typescript
- Git
- Github Actions
- NodeMailer
- Handlebars
- MongoDB Atlas
- Postman
- VSCode
- Pre-commit
- Mailtrap
The system serves different type of users (Admin, Instructor , Individual Trainee, Corporate Trainee)
As an Admin I can
- Add instructors and corporate trainees to the system
- View Reported problems and resolve them
- View access requests from Corporate Trainees and grant access
- View Refund Requests from Individual Trainees
As an Instructor I can
- Create and edit a draft Course
- Publish draft Course so trainees could enroll in
- Close a published course to prevent more trainees from enrolling in it
- View my settlements and update my profile
- Add a promotion for a specific period
As an Individual Trainee I can
- Search and filter Courses
- Pay for a course
- Report problems
- Watch a video and solve exercises from my courses
- See my progress
- Recieve a certificate by mail
- Request refund
- Rate a course and its instructor
As a Corporate Trainee I can
- Search and filter Courses
- Send access requests for specific course
- Watch a video and solve exercises from my courses
- See my progress
- Recieve a certificate by mail
- Rate a course and its instructor
As Guest I can
- Sign up as individual trainee
- Search and filter courses
Send Certificate Service
import { sendEmail } from "./sendMailService";
export const sendCertificateEmail = async (email: string, courseName: string, certificatePath: string) => {
const context = {
courseName: courseName,
email: email
};
const attachments = [
{
filename: "certificate.pdf",
path: certificatePath,
contentType: "application/pdf"
}
];
sendEmail(email, context, "certificateUponCompletion", "Linear Depression | Congrats 🎉", attachments);
};
Course Action Methods
courseSchema.methods.close = async function (this: ICourseModel) {
if (this.status !== CourseStatus.PUBLISHED) {
throw new Error("Invalid Transition, course must be published to be closed");
}
this.status = CourseStatus.CLOSED;
await this.save();
};
courseSchema.methods.publish = async function (this: ICourseModel) {
if (this.status !== CourseStatus.DRAFT) {
throw new Error("Invalid Transition, course must be draft to be published");
}
this.status = CourseStatus.PUBLISHED;
await this.save();
};
courseSchema.methods.reOpen = async function (this: ICourseModel) {
if (this.status !== CourseStatus.CLOSED) {
throw new Error("Invalid Transition, course must be closed to be re-opened");
}
this.status = CourseStatus.PUBLISHED;
await this.save();
};
Setting Rate Limiters
const rateLimiter = (requestsPerMinute: number = 120) => {
return rateLimit({
windowMs: 60 * 1000, // 1 minute
max: requestsPerMinute, // Limit each IP to n requests per `window` per minute
message: { message: `Too many requests from this IP, please try again after a 60 second pause` },
handler: (req: Request, res: Response, next: NextFunction, options: any) => {
res.status(options.statusCode).send(options.message);
},
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false // Disable the `X-RateLimit-*` headers
});
};
Caching Currency Rates
// run every day at 00:00
const getCurrencyRatesTask = new CronJob("0 0 0 * * *", async () => {
console.log("Cache currency rates");
const data = fs.readFileSync("src/media/country-currency.json", "utf8");
const currencies = JSON.parse(data);
// convert to distinct currency codes
const currencyCodes = currencies.map((currency: any) => currency.Code) as string[];
const distinctCurrencyCodes = [...new Set(currencyCodes)] as string[];
const currencyRates = {} as CurrencyRate;
for (const currencyCode of distinctCurrencyCodes) {
try {
const rate = await getCurrencyRate(currencyCode, "USD");
console.log(`Currency code: ${currencyCode}, rate: ${rate}`);
currencyRates[currencyCode] = rate;
} catch (error) {
console.log(error);
}
}
fs.writeFileSync("src/media/currency-rates.json", JSON.stringify(currencyRates));
});
Promotion Validator
export const PromotionValidator = {
validate: async (promotion: IPromotionModel) => {
if (!isValidStartDate(promotion)) {
throw new Error("Promotion start date is invalid");
}
if (!isValidEndDate(promotion)) {
throw new Error("Promotion end date is invalid");
}
if (!(await onlyIncludesPaidCourses(promotion))) {
throw new Error("Promotion can only include paid courses");
}
if (!(await noConflictWithAdminPromotion(promotion))) {
throw new Error("Promotion conflicts with Admin promotion");
}
return true;
}
};
Course NavBar
<HorizontalContainer>
<NavItem>
<Image src={logo} alt="logo" onClick={() => navigate("/")} />
</NavItem>
<CustomDivider orientation="vertical" flexItem />
<NavItem>
<ArrowBackIcon sx={{ marginRight: "10px" }} />
<Link className="navbar-brand" to={`/courses/${course.data?._id}`}>
{course.data?.title}
</Link>
</NavItem>
{enrollment.data && (
<ProgressContainer>
<NavItem>{enrollment.data && <CircularProgressBar value={enrollment.data?.progress} />}</NavItem>
{enrollment.data?.progress === 100 ? (
<Button
sx={{
color: "white",
textTransform: "none"
}}
onClick={handleDownloadCertificate}
>
Get Certificate
</Button>
) : (
"Your Progress"
)}
</ProgressContainer>
)}
</HorizontalContainer>
Question Card
<ErrorCourseCard>
<div key={index}>
<HorizontalContainer>
<QuestionTitle>{question.question}</QuestionTitle>
<QuestionAction>
<ModeEditIcon
onClick={() => {
handleOpenEditQuestion(index);
}}
sx={{
"&:hover": {
fontSize: "1.8rem"
},
margin: "5px"
}}
/>
<DeleteIcon
onClick={() => {
deleteQuestion(index);
}}
sx={{
color: "error.main",
"&:hover": {
fontSize: "1.8rem"
},
margin: "5px"
}}
/>
</QuestionAction>
</HorizontalContainer>
<GroupRadioButton
answer={question.answerIndex}
questionNumber={index}
choices={question.choices}
onChange={handleSetAnswer}
/>
</div>
</ErrorCourseCard>
The testing is done using jest
. To run the tests, run the following command
> cd server && npm run test
Click Me!
.
├── test_apis
│ ├── course
│ │ └── course.test.ts
│ ├── course_ratings
│ │ └── rating.test.ts
│ ├── example.test.ts
│ ├── instructor
│ │ └── instructor.test.ts
│ ├── instructor_ratings
│ │ └── instructor_ratings.test.ts
│ └── trainee
│ ├── corporateTrainee.test.ts
│ └── individualTrainee.test.ts
├── test_models
│ ├── answer
│ │ ├── answer.test.ts
│ │ └── factory.ts
│ ├── course
│ │ ├── course.test.ts
│ │ └── factory.ts
│ ├── enrollment
│ │ └── factory.ts
│ ├── exercise
│ │ ├── exercise.test.ts
│ │ └── factory.ts
│ ├── instructor
│ │ ├── factory.ts
│ │ └── instructor.test.ts
│ ├── lesson
│ │ ├── factory.ts
│ │ └── lesson.test.ts
│ ├── rating
│ │ ├── factory.ts
│ │ └── rating.test.ts
│ ├── trainee
│ │ ├── factory.ts
│ │ └── trainee.test.ts
│ └── userFactory.ts
├── test_services
│ └── CourseService.test.ts
└── test_utils
└── modelUtilities.test.ts
js-faker
is used to generate data to test different models
There is tests done for the following models : Trainee
, Course
, Exercise
, Rating
, Lesson
, Answer
, Instructor
Also curl
with used througout the process
Install my-project with npm
> git clone https://github.com/Advanced-Computer-Lab-2022/Linear-Depression
> cd Linear-Depression/
> cd server && npm i && cd -
> cd client && npm i -f && cd -
To run backend
cd server && nodemon src/start.ts
To run frontend
cd client && npm start
the backend server and client will be running on the specified ports on your env files.
To run this project, you will need to add the following environment variables to your .env file
envs
REACT_APP_API_URL
REACT_APP_STRIPE_PUBLISHABLE_KEY
MONGO_URL
MONGO_TEST_URL
SERVER_PORT
FRONT_END_URL
JWT_ACCESS_TOKEN_SECRET
JWT_REFRESH_TOKEN_SECRET
EMAIL_HOST
EMAIL_PORT
EMAIL_USER
EMAIL_PASSWORD
PASSWORD_RESET_EMAIL_FROM
PASSWORD_RESET_EMAIL_SUBJECT
EMAIL_FROM
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
- Currency rates are cached using an cron job that runs at 12 AM.
- Asynchronous programming was used.
- Index was used on db to optimize search
Contributions are always welcome!
See contributing.md
for ways to get started.
Please adhere to this project's code of conduct
.
Click to expand!
## Project Structure
.
├── client
│ ├── craco.config.js
│ ├── package.json
│ ├── package-lock.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── README.md
│ ├── src
│ │ ├── api
│ │ │ ├── endpoints.ts
│ │ │ ├── index.ts
│ │ │ └── report
│ │ │ ├── addReport.ts
│ │ │ ├── addThreadReply.ts
│ │ │ ├── getReport.ts
│ │ │ ├── getUserReports.ts
│ │ │ └── index.ts
│ │ ├── App.tsx
│ │ ├── components
│ │ │ ├── AuthHandler.tsx
│ │ │ ├── Avatar.tsx
│ │ │ ├── CircularProgressBar.tsx
│ │ │ ├── Copyright.tsx
│ │ │ ├── course
│ │ │ │ ├── courseContent
│ │ │ │ │ ├── ContentAccordion.css
│ │ │ │ │ ├── ContentAccordion.tsx
│ │ │ │ │ └── ContentItem.tsx
│ │ │ │ ├── CourseContent.tsx
│ │ │ │ ├── courseHeader
│ │ │ │ │ ├── CourseActions.tsx
│ │ │ │ │ ├── courseInfo
│ │ │ │ │ │ └── BadgeRatedEnrolled.tsx
│ │ │ │ │ └── CourseInfo.tsx
│ │ │ │ ├── CourseHeader.tsx
│ │ │ │ └── CourseReviews.tsx
│ │ │ ├── CourseNavbar.tsx
│ │ │ ├── CoursePrice.tsx
│ │ │ ├── coursesListWithFilters
│ │ │ │ ├── BrowseBy.tsx
│ │ │ │ ├── coursesList
│ │ │ │ │ └── CourseCard.tsx
│ │ │ │ ├── CoursesList.tsx
│ │ │ │ ├── filter
│ │ │ │ │ ├── PriceFilter.tsx
│ │ │ │ │ ├── RatingFilter.tsx
│ │ │ │ │ └── SubjectsFilter.tsx
│ │ │ │ └── Filter.tsx
│ │ │ ├── CoursesListWithFilters.tsx
│ │ │ ├── exercise
│ │ │ │ ├── Header.ts
│ │ │ │ ├── QuestionCard.ts
│ │ │ │ ├── QuestionTitle.ts
│ │ │ │ ├── SolvedQuestion.tsx
│ │ │ │ ├── SubmitButton.ts
│ │ │ │ ├── Title.ts
│ │ │ │ └── TotalGrade.tsx
│ │ │ ├── FloatingButton.ts
│ │ │ ├── GroupRadioButton.tsx
│ │ │ ├── index.ts
│ │ │ ├── modals
│ │ │ │ ├── AddCourse.tsx
│ │ │ │ ├── AddExercise.tsx
│ │ │ │ ├── AddLesson.tsx
│ │ │ │ ├── AddPromotion.tsx
│ │ │ │ ├── AddQuestion.tsx
│ │ │ │ ├── AddReview.tsx
│ │ │ │ ├── EditCourse.tsx
│ │ │ │ ├── EditLesson.tsx
│ │ │ │ ├── EditProfile.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── ViewAndAcceptContract.tsx
│ │ │ │ └── ViewMySettlements.tsx
│ │ │ ├── navbar
│ │ │ │ ├── CountrySelect.tsx
│ │ │ │ └── Navbar.css
│ │ │ ├── Navbar.tsx
│ │ │ ├── OptionsButton.tsx
│ │ │ ├── report
│ │ │ │ ├── index.ts
│ │ │ │ ├── listing
│ │ │ │ │ ├── table
│ │ │ │ │ │ ├── BodyContainer.tsx
│ │ │ │ │ │ ├── Header.tsx
│ │ │ │ │ │ └── Row.tsx
│ │ │ │ │ └── TableContainer.tsx
│ │ │ │ ├── new
│ │ │ │ │ ├── Form.tsx
│ │ │ │ │ └── HorizontalCourseCard.tsx
│ │ │ │ ├── PageContainter.tsx
│ │ │ │ ├── PageHeader.tsx
│ │ │ │ └── thread
│ │ │ │ ├── Author.tsx
│ │ │ │ ├── CardContainer.tsx
│ │ │ │ ├── Card.tsx
│ │ │ │ ├── Container.tsx
│ │ │ │ ├── ReplyForm.tsx
│ │ │ │ └── SubjectDivider.tsx
│ │ │ ├── ReviewItem.tsx
│ │ │ ├── SimpleAccordion.tsx
│ │ │ └── VideoPlayer.tsx
│ │ ├── config
│ │ │ └── config.ts
│ │ ├── contexts
│ │ │ ├── AuthProvider.tsx
│ │ │ ├── CountryContext.ts
│ │ │ ├── index.ts
│ │ │ └── ToastProvider.tsx
│ │ ├── hooks
│ │ │ ├── course
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFetchAllCourses.ts
│ │ │ │ ├── useFetchCourseById.ts
│ │ │ │ ├── useFetchMyCourses.ts
│ │ │ │ └── useFetchSubjects.ts
│ │ │ ├── enrollment
│ │ │ │ ├── index.ts
│ │ │ │ └── useFetchMyEnrollment.ts
│ │ │ ├── exercise
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFetchEvaluation.ts
│ │ │ │ └── useFetchExerciseById.ts
│ │ │ ├── index.ts
│ │ │ ├── instructor
│ │ │ │ ├── index.ts
│ │ │ │ └── useGetInstructorContractStatus.ts
│ │ │ ├── lesson
│ │ │ │ ├── index.ts
│ │ │ │ └── useFetchLessonById.ts
│ │ │ ├── localization
│ │ │ │ ├── index.ts
│ │ │ │ └── useGetLocalizationData.ts
│ │ │ ├── note
│ │ │ │ ├── index.ts
│ │ │ │ └── useFetchMyNote.ts
│ │ │ ├── profile
│ │ │ │ ├── index.ts
│ │ │ │ └── useFetchProfile.ts
│ │ │ ├── report
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFetchReports.ts
│ │ │ │ └── useFetchThread.ts
│ │ │ ├── request
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFetchMyAccessRequest.ts
│ │ │ │ └── useFetchMyRefundRequest.ts
│ │ │ ├── review
│ │ │ │ ├── index.ts
│ │ │ │ ├── useFetchCourseReviews.ts
│ │ │ │ ├── useFetchMyReviews.ts
│ │ │ │ └── useFetchMyReviewSubmission.ts
│ │ │ ├── settlements
│ │ │ │ ├── index.ts
│ │ │ │ └── useFetchMySettlements.ts
│ │ │ ├── useAuth.ts
│ │ │ └── useToast.ts
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── media
│ │ │ └── country-currency.json
│ │ ├── pages
│ │ │ ├── auth
│ │ │ │ ├── ChangePassword.tsx
│ │ │ │ ├── ForgotPassword.tsx
│ │ │ │ ├── Login.tsx
│ │ │ │ ├── PasswordReset.tsx
│ │ │ │ └── Register.tsx
│ │ │ ├── CorporateTraineeProfile.tsx
│ │ │ ├── Course.tsx
│ │ │ ├── CreateExercise.tsx
│ │ │ ├── Exercise.tsx
│ │ │ ├── home
│ │ │ │ └── AllCourses.tsx
│ │ │ ├── Home.tsx
│ │ │ ├── index.ts
│ │ │ ├── IndividualTraineeProfile.tsx
│ │ │ ├── InstructorCourse.tsx
│ │ │ ├── InstructorExercise.tsx
│ │ │ ├── instructorProfile
│ │ │ │ ├── MyReviews.tsx
│ │ │ │ └── ViewProfile.tsx
│ │ │ ├── InstructorProfile.tsx
│ │ │ ├── lesson
│ │ │ │ └── Note.tsx
│ │ │ ├── Lesson.tsx
│ │ │ ├── MyCourses.tsx
│ │ │ ├── payment
│ │ │ │ ├── Cancelled.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── Success.tsx
│ │ │ ├── PrivacyPolicy.tsx
│ │ │ ├── Profile.tsx
│ │ │ ├── report
│ │ │ │ ├── List.tsx
│ │ │ │ ├── New.tsx
│ │ │ │ └── Thread.tsx
│ │ │ └── TraineeExercise.tsx
│ │ ├── redux
│ │ │ ├── features
│ │ │ │ ├── course
│ │ │ │ │ └── courseSlice.ts
│ │ │ │ ├── courseList
│ │ │ │ │ └── coursesListSlice.ts
│ │ │ │ ├── enrollment
│ │ │ │ │ └── enrollmentSlice.ts
│ │ │ │ ├── profile
│ │ │ │ │ └── profileSlice.ts
│ │ │ │ └── subjects
│ │ │ │ └── subjectSlice.ts
│ │ │ ├── index.ts
│ │ │ └── store.ts
│ │ ├── reportWebVitals.ts
│ │ ├── services
│ │ │ ├── auth
│ │ │ │ ├── changePassword.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── login.ts
│ │ │ │ ├── logout.ts
│ │ │ │ ├── performPasswordReset.ts
│ │ │ │ ├── refresh.ts
│ │ │ │ ├── register.ts
│ │ │ │ ├── sendForgotPasswordRequest.ts
│ │ │ │ └── validatePasswordResetToken.ts
│ │ │ ├── course
│ │ │ │ ├── addCourse.ts
│ │ │ │ ├── addPromotion.ts
│ │ │ │ ├── editCourse.ts
│ │ │ │ ├── fetchAllCourses.ts
│ │ │ │ ├── fetchCourseById.ts
│ │ │ │ ├── fetchMyCourses.ts
│ │ │ │ ├── fetchSubjects.ts
│ │ │ │ └── index.ts
│ │ │ ├── enrollment
│ │ │ │ ├── downloadCertificate.ts
│ │ │ │ ├── enrollmentServices.ts
│ │ │ │ ├── enrollOnCourse.ts
│ │ │ │ ├── fetchMyEnrollment.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── updateEnrollment.ts
│ │ │ ├── exercise
│ │ │ │ ├── addExercise.ts
│ │ │ │ ├── fetchEvaluation.ts
│ │ │ │ ├── fetchExerciseById.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── submitExercise.ts
│ │ │ ├── index.ts
│ │ │ ├── instructor
│ │ │ │ ├── acceptInstructorContract.ts
│ │ │ │ ├── getInstructorContractStatus.ts
│ │ │ │ └── index.ts
│ │ │ ├── lesson
│ │ │ │ ├── addLesson.ts
│ │ │ │ ├── editLesson.ts
│ │ │ │ ├── fetchLessonById.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── videoServices.ts
│ │ │ ├── localization
│ │ │ │ ├── fetchCountryCode.ts
│ │ │ │ ├── getCurrency.ts
│ │ │ │ └── index.ts
│ │ │ ├── note
│ │ │ │ ├── addNote.ts
│ │ │ │ ├── downloadPDF.ts
│ │ │ │ ├── editNote.ts
│ │ │ │ ├── fetchMyNote.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── saveAsPDF.ts
│ │ │ ├── payment
│ │ │ │ ├── index.ts
│ │ │ │ └── payment.ts
│ │ │ ├── profile
│ │ │ │ ├── editProfile.ts
│ │ │ │ ├── fetchProfile.ts
│ │ │ │ └── index.ts
│ │ │ ├── request
│ │ │ │ ├── cancelRefundRequest.ts
│ │ │ │ ├── fetchMyAccessRequest.ts
│ │ │ │ ├── fetchMyRefundRequest.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── sendAccessRequest.ts
│ │ │ │ └── sendRefundRequest.ts
│ │ │ ├── review
│ │ │ │ ├── addCourseReview.ts
│ │ │ │ ├── addInstructorReview.ts
│ │ │ │ ├── fetchCourseReviews.ts
│ │ │ │ ├── fetchMyReviewForCourse.ts
│ │ │ │ ├── fetchMyReviewForInstructor.tsx
│ │ │ │ ├── fetchMyReviews.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── updateCourseReview.ts
│ │ │ │ └── updateInstructorReview.ts
│ │ │ └── settlements
│ │ │ ├── fetchMySettlements.ts
│ │ │ └── index.ts
│ │ ├── types
│ │ │ ├── AccessRequest.ts
│ │ │ ├── auth
│ │ │ │ ├── index.ts
│ │ │ │ ├── login.ts
│ │ │ │ └── register.ts
│ │ │ ├── Country.ts
│ │ │ ├── Course.ts
│ │ │ ├── Enrollment.ts
│ │ │ ├── enums
│ │ │ │ ├── index.ts
│ │ │ │ ├── ReportStatus.ts
│ │ │ │ ├── ReportType.ts
│ │ │ │ └── UserType.ts
│ │ │ ├── Evaluation.ts
│ │ │ ├── Exercise.ts
│ │ │ ├── FormProps.ts
│ │ │ ├── index.ts
│ │ │ ├── Instructor.ts
│ │ │ ├── Lesson.ts
│ │ │ ├── Note.ts
│ │ │ ├── Profile.ts
│ │ │ ├── Promotion.ts
│ │ │ ├── Question.ts
│ │ │ ├── RefundRequest.ts
│ │ │ ├── report
│ │ │ │ ├── index.ts
│ │ │ │ ├── ReportFormProps.ts
│ │ │ │ ├── ReportThread.ts
│ │ │ │ └── Report.ts
│ │ │ ├── ReviewSubmission.ts
│ │ │ ├── Review.ts
│ │ │ └── User.ts
│ │ └── utils
│ │ ├── index.ts
│ │ └── validateFormData.ts
│ ├── tsconfig.json
│ └── tsconfig.paths.json
├── contributing.md
├── docs
│ ├── AdminDashboard.png
│ └── APIDocs.png
├── LICENSE
├── README.md
└── server
├── babel.config.js
├── jest.config.ts
├── package.json
├── package-lock.json
├── public < -- public static files (images, fonts, etc.)
│ ├── admin
│ │ └── css
│ │ └── dashboard.css
│ ├── assets
│ │ ├── qr.png
│ │ └── winners.png
│ ├── certificates
│ │ ├── 63a5dd8a26d81baf0958bb2e.pdf
│ │ ├── 63a6000d6828d41508671a8d.pdf
│ │ ├── 63a6050cd7ed49254b880181.pdf
│ ├── fonts
│ │ ├── NotoSansJP-Bold.otf
│ │ ├── NotoSansJP-Light.otf
│ │ └── NotoSansJP-Regular.otf
│ └── notes
│ ├── 63a225e117897bfd964a8417.pdf
│ └── 63a89405c0fa640e7e80b26f.pdf
├── src
│ ├── admin
│ │ ├── components
│ │ │ ├── AddPromotion.tsx
│ │ │ └── dashboard.tsx
│ │ ├── config.ts
│ │ ├── hooks
│ │ │ └── hashPasswordInPayload.ts
│ │ ├── index.ts
│ │ ├── locale
│ │ │ └── en
│ │ │ ├── index.ts
│ │ │ └── report.json
│ │ └── resources
│ │ ├── AccessRequest.ts
│ │ ├── Admin.ts
│ │ ├── CorporateTrainee.ts
│ │ ├── Course.ts
│ │ ├── IndividualTrainee.ts
│ │ ├── Instructor.ts
│ │ ├── RefundRequest.ts
│ │ ├── Report.ts
│ │ └── User.ts
│ ├── config
│ │ └── config.ts
│ ├── controllers <--- API Controllers
│ │ ├── AccessRequest.ts
│ │ ├── Auth.ts
│ │ ├── CorporateTrainee.ts
│ │ ├── CourseRating.ts
│ │ ├── Course.ts
│ │ ├── Currency.ts
│ │ ├── Enrollment.ts
│ │ ├── Exercise.ts
│ │ ├── IndividualTrainee.ts
│ │ ├── InstructorRating.ts
│ │ ├── Instructor.ts
│ │ ├── Lesson.ts
│ │ ├── Note.ts
│ │ ├── PasswordResetToken.ts
│ │ ├── Payment.ts
│ │ ├── Profile.ts
│ │ ├── Promotion.ts
│ │ ├── RefundRequest.ts
│ │ ├── Report.ts
│ │ ├── Settlement.ts
│ │ └── UserType.ts
│ ├── enums
│ │ └── UserTypes.ts
│ ├── media
│ │ ├── country-currency.json
│ │ └── currency-rates.json
│ ├── middleware <----------------- Middlewares are here
│ │ ├── logger.ts
│ │ ├── permissions <----------------- Middlewares Permissions are here
│ │ │ ├── isAuthenticated.ts
│ │ │ ├── isAuthorized.ts
│ │ │ ├── isCourseOwner.ts
│ │ │ ├── isEnrolled.ts
│ │ │ ├── isOwnerOrEnrolled.ts
│ │ │ └── isRatingOwner.ts
│ │ └── rateLimiter.ts
│ ├── models <----------------- Models are here
│ │ ├── AccessRequest.ts
│ │ ├── Admin.ts
│ │ ├── Answer.ts
│ │ ├── CorporateTrainee.ts
│ │ ├── Course.ts
│ │ ├── Enrollment.ts
│ │ ├── Exercise.ts
│ │ ├── IndividualTrainee.ts
│ │ ├── Instructor.ts
│ │ ├── Lesson.ts
│ │ ├── Note.ts
│ │ ├── PasswordResetToken.ts
│ │ ├── Promotion.ts
│ │ ├── Rating.ts
│ │ ├── RefundRequest.ts
│ │ ├── ReportThread.ts
│ │ ├── Report.ts
│ │ ├── Settlement.ts
│ │ ├── Trainee.ts
│ │ └── User.ts
│ ├── routes <----------------- Routes are here
│ │ ├── Auth.ts
│ │ ├── CorporateTrainee.ts
│ │ ├── Course.ts
│ │ ├── Currency.ts
│ │ ├── Enrollment.ts
│ │ ├── IndividualTrainee.ts
│ │ ├── Instructor.ts
│ │ ├── Me.ts
│ │ ├── Payment.ts
│ │ ├── Promotion.ts
│ │ └── UserType.ts
│ ├── server.ts
│ ├── services
│ │ ├── certificateService.ts
│ │ ├── CourseServices.ts
│ │ ├── emails <----------------- Email services are here
│ │ │ ├── accessRequests
│ │ │ │ ├── sendAccessRequestApprovalEmail.ts
│ │ │ │ ├── sendAccessRequestCreationEmail.ts
│ │ │ │ └── sendAccessRequestRejectionEmail.ts
│ │ │ ├── refundRequests
│ │ │ │ ├── sendRefundRequestApprovalEmail.ts
│ │ │ │ ├── sendRefundRequestCreationEmail.ts
│ │ │ │ └── sendRefundRequestRejectionEmail.ts
│ │ │ ├── sendCertificateEmail.ts
│ │ │ ├── sendEnrollmentEmail.ts
│ │ │ ├── sendMailService.ts
│ │ │ ├── sendPasswordResetEmail.ts
│ │ │ └── templates <----------------- Email templates are here
│ │ │ ├── accessRequestApproval.html
│ │ │ ├── accessRequestCreation.html
│ │ │ ├── accessRequestRejection.html
│ │ │ ├── certificateUponCompletion.html
│ │ │ ├── instructorCredit.html
│ │ │ ├── partials
│ │ │ │ ├── footer.html
│ │ │ │ └── header.html
│ │ │ ├── passwordResetEmail.html
│ │ │ ├── refundRequestApproval.html
│ │ │ ├── refundRequestCreation.html
│ │ │ └── refundRequestRejection.html
│ │ ├── EnrollmentCreateServices.ts
│ │ ├── PasswordResetTokenServices.ts
│ │ ├── SettlementService.ts
│ │ ├── UserServices.ts
│ │ └── videoServices.ts
│ ├── start.ts
│ ├── swagger.json
│ ├── swagger.ts <------------------ Swagger Generation ------------------
│ ├── tasks
│ │ └── cacheCurrencyRates.ts
│ ├── tests <------------------ Tests ------------------
│ │ ├── test_apis <------------------ API Tests ------------------
│ │ │ ├── course
│ │ │ │ └── course.test.ts
│ │ │ ├── course_ratings
│ │ │ │ └── rating.test.ts
│ │ │ ├── example.test.ts
│ │ │ ├── instructor
│ │ │ │ └── instructor.test.ts
│ │ │ ├── instructor_ratings
│ │ │ │ └── instructor_ratings.test.ts
│ │ │ └── trainee
│ │ │ ├── corporateTrainee.test.ts
│ │ │ └── individualTrainee.test.ts
│ │ ├── test_models <------------------ Model Tests ------------------
│ │ │ ├── answer
│ │ │ │ ├── answer.test.ts
│ │ │ │ └── factory.ts <------------------ Factory ------------------
│ │ │ ├── course
│ │ │ │ ├── course.test.ts
│ │ │ │ └── factory.ts
│ │ │ ├── enrollment
│ │ │ │ └── factory.ts
│ │ │ ├── exercise
│ │ │ │ ├── exercise.test.ts
│ │ │ │ └── factory.ts
│ │ │ ├── instructor
│ │ │ │ ├── factory.ts
│ │ │ │ └── instructor.test.ts
│ │ │ ├── lesson
│ │ │ │ ├── factory.ts
│ │ │ │ └── lesson.test.ts
│ │ │ ├── rating
│ │ │ │ ├── factory.ts
│ │ │ │ └── rating.test.ts
│ │ │ ├── trainee
│ │ │ │ ├── factory.ts
│ │ │ │ └── trainee.test.ts
│ │ │ └── userFactory.ts
│ │ ├── test_services <------------------ Service Tests ------------------
│ │ │ └── CourseService.test.ts
│ │ └── test_utils
│ │ └── modelUtilities.test.ts
│ └── utils
│ ├── auth
│ │ └── token.ts
│ ├── loadModelsUtil.ts
│ ├── parseQueryParams.ts
│ ├── populateTestDb.ts
│ └── testUtilities.ts
└── tsconfig.json
The software is open source under the GPL.3 License.
how to run stripe in development mode
- update your .env file in both the client and server bu following the .env.example files
- install the stripe cli
sudo apt install stripe # for linux
brew install stripe/stripe-cli/stripe # for mac
you can refer to this documentation for more information
- login to to stripe cli using stripe api key
stripe login --api-key sk_test_example
# contact the team for the api key or use your own
- run the stripe cli
stripe listen --forward-to localhost:PORT/payment/stripe-webhook
# PORT is the port you are running the server on
Access the admin dashboard by going to the following URL
http://localhost:PORT/admin
> npm run generate-docs
- Clean code
- RESTful Web API Patterns and Practices Cookbook
- Designing Data Intensive Applications
- Mongoose docs
- Express docs
- ReactJs docs
- Redux docs
- NodeJs docs
If you have any feedback, please reach out to us at [email protected]