├── .sample.env ├── Procfile ├── .dockerignore ├── .eslintignore ├── .prettierrc ├── .firebaserc ├── src ├── roles │ └── role.enum.ts ├── modules │ ├── course │ │ ├── review.enum.ts │ │ ├── course-tag.enum.ts │ │ ├── courseLevel.enum.ts │ │ ├── dto │ │ │ ├── course-filter.dto.ts │ │ │ ├── update-lecture.dto.ts │ │ │ ├── create-lecture.dto.ts │ │ │ ├── update-review.dto.ts │ │ │ ├── create-schedule.dto.ts │ │ │ ├── update-schedule.dto.ts │ │ │ ├── create-review.dto.ts │ │ │ ├── create-mentor.dto.ts │ │ │ ├── create-course.dto.ts │ │ │ └── course-update.dto.ts │ │ ├── docUtils │ │ │ ├── course.paramdocs.ts │ │ │ ├── apidoc.ts │ │ │ └── course.responsedoc.ts │ │ ├── schema │ │ │ ├── lecture.schema.ts │ │ │ ├── review.schema.ts │ │ │ ├── schedule.schema.ts │ │ │ ├── enrolledCourse.schema.ts │ │ │ └── course.schema.ts │ │ ├── course.module.ts │ │ ├── course.controller.ts │ │ └── course.controller.spec.ts │ ├── doubt │ │ ├── doubt-tag.enum.ts │ │ ├── docUtils │ │ │ ├── doubt.paramdocs.ts │ │ │ ├── apidoc.ts │ │ │ └── doubt.responsedoc.ts │ │ ├── dto │ │ │ ├── update-doubtAnswer.dto.ts │ │ │ ├── create-doubtAnswer.dto.ts │ │ │ ├── update-doubt.dto.ts │ │ │ └── create-doubt.dto.ts │ │ ├── doubt.controller.spec.ts │ │ ├── doubt.module.ts │ │ ├── schema │ │ │ ├── doubtAnswer.schema.ts │ │ │ └── doubt.schema.ts │ │ ├── doubt.service.spec.ts │ │ ├── doubt.controller.ts │ │ └── doubt.service.ts │ ├── user │ │ ├── dto │ │ │ ├── update-course.user.dto.ts │ │ │ ├── create-cartList.dto.ts │ │ │ ├── create-wishlist.dto.ts │ │ │ ├── update-enrolled.dto.ts │ │ │ ├── create-enrolled.dto.ts │ │ │ ├── create-user.dto.ts │ │ │ └── update-user.dto.ts │ │ ├── course-status.enum.ts │ │ ├── docUtils │ │ │ ├── user.paramdocs.ts │ │ │ ├── apidoc.ts │ │ │ └── user.responsedoc.ts │ │ ├── user.module.ts │ │ ├── user.service.spec.ts │ │ ├── schema │ │ │ └── user.schema.ts │ │ ├── user.controller.spec.ts │ │ └── user.controller.ts │ ├── chat │ │ ├── dto │ │ │ └── create-chat.dto.ts │ │ ├── chat.module.ts │ │ ├── chat.service.spec.ts │ │ ├── chat.controller.spec.ts │ │ ├── schema │ │ │ ├── room.schema.ts │ │ │ └── chat.schema.ts │ │ ├── chat.service.ts │ │ └── chat.controller.ts │ ├── assignment │ │ ├── docUtils │ │ │ ├── assignment.paramdocs.ts │ │ │ ├── assignment.responsedoc.ts │ │ │ └── apidoc.ts │ │ ├── assignment.module.ts │ │ ├── dto │ │ │ ├── create-assignment.dto.ts │ │ │ └── update-assignment.dto.ts │ │ ├── assignment.controller.spec.ts │ │ ├── schema │ │ │ └── assignment.schema.ts │ │ ├── assignment.service.spec.ts │ │ ├── assignment.controller.ts │ │ └── assignment.service.ts │ ├── announcements │ │ ├── docUtils │ │ │ ├── announcement.paramdocs.ts │ │ │ ├── announcement.responsedoc.ts │ │ │ └── apidoc.ts │ │ ├── announcement.module.ts │ │ ├── schema │ │ │ └── announcement.schema.ts │ │ ├── dto │ │ │ ├── create-announcement.dto.ts │ │ │ └── update-announcement.dto.ts │ │ ├── announcement.controller.ts │ │ ├── announcement.service.ts │ │ └── announcement.controller.spec.ts │ └── mentor │ │ ├── docUtils │ │ ├── mentor.paramdocs.ts │ │ ├── apidoc.ts │ │ └── mentor.responsedoc.ts │ │ ├── mentor.module.ts │ │ ├── schema │ │ └── mentor.schema.ts │ │ ├── dto │ │ ├── create-mentor.dto.ts │ │ └── update-mentor.dto.ts │ │ ├── mentor.controller.ts │ │ ├── mentor.service.ts │ │ ├── mentor.service.spec.ts │ │ └── mentor.controller.spec.ts ├── middleware │ ├── role.decorator.ts │ ├── roles.guard.ts │ └── preAuth.middleware.ts ├── app.service.ts ├── config │ ├── firebase.ts │ └── service.ts ├── app.controller.ts ├── swagger-ui │ └── index.ts ├── main-firebase.ts ├── main.ts └── app.module.ts ├── tsconfig.build.json ├── firebase.json ├── Dockerfile ├── test ├── jest-e2e.json └── app.e2e-spec.ts ├── docker-compose.yml ├── nest-cli.json ├── .vscode └── launch.json ├── tsconfig.json ├── .gitignore ├── .env.sample ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug_report.md ├── workflows │ ├── main.yml │ └── automatic_rebase.yml └── pull_request_template.md ├── LICENSE ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── package.json └── README.md /.sample.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start:prod -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ../node_modules 2 | ../dist -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/config/example.service.json 2 | src/config/service.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "keenwpractice" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/roles/role.enum.ts: -------------------------------------------------------------------------------- 1 | export enum Role { 2 | STUDENT = 'student', 3 | ADMIN = 'admin', 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/course/review.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ReviewType { 2 | STUDENT = 'Student', 3 | TEACHER = 'Teacher', 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/course/course-tag.enum.ts: -------------------------------------------------------------------------------- 1 | export enum TagType { 2 | WEB_DEV = 'Web Development', 3 | AND_DEV = 'Android Dev', 4 | AI = 'AI', 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/doubt/doubt-tag.enum.ts: -------------------------------------------------------------------------------- 1 | export enum TagType { 2 | WEB_DEV = 'Web Development', 3 | AND_DEV = 'Android Dev', 4 | AI = 'AI', 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/user/dto/update-course.user.dto.ts: -------------------------------------------------------------------------------- 1 | export class UpdateCourseDTO { 2 | enrolled_courses?: string[]; 3 | wishlist?: string[]; 4 | } 5 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "predeploy": [ 4 | "npm --prefix \"$RESOURCE_DIR\" run build" 5 | ], 6 | "source": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/user/dto/create-cartList.dto.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | export class CreateCartListDTO { 3 | cId?: Schema.Types.ObjectId; 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/user/dto/create-wishlist.dto.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | export class CreateWishlistDTO { 3 | cId?: Schema.Types.ObjectId; 4 | } 5 | -------------------------------------------------------------------------------- /src/middleware/role.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const Roles = (...roles: string[]) => SetMetadata('roles', roles); 4 | -------------------------------------------------------------------------------- /src/modules/user/course-status.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CourseType { 2 | WISHLIST = 'wishlist', 3 | ENROLLED = 'enrolled_courses', 4 | RECOMMENDED = 'recommended_courses', 5 | } 6 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/course/courseLevel.enum.ts: -------------------------------------------------------------------------------- 1 | export enum courseLevelType { 2 | BEGINNER = 'Beginner', 3 | INTERMEDIATE = 'Intermediate', 4 | TRAINING = 'Training', 5 | ADVANCED = 'Advanced', 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json /usr/src/app 6 | 7 | RUN npm install 8 | 9 | COPY . /usr/src/app 10 | 11 | EXPOSE 5000 12 | 13 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/course/dto/course-filter.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString } from 'class-validator'; 2 | 3 | export class GetCourseFilterDto { 4 | /** 5 | * The query string 6 | * @example 'Python DSA' 7 | */ 8 | @IsString() 9 | Query: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/chat/dto/create-chat.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateChatDTO { 4 | @IsNotEmpty() 5 | readonly sender: string; 6 | 7 | readonly original_sender: string; 8 | readonly message: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/user/docUtils/user.paramdocs.ts: -------------------------------------------------------------------------------- 1 | import { ApiParamOptions } from '@nestjs/swagger'; 2 | 3 | export const userId: ApiParamOptions = { 4 | name: 'userId', 5 | type: String, 6 | description: 'User Id in the form of MongoId', 7 | example: '60ccf3758dc53371bd4d0154', 8 | }; 9 | -------------------------------------------------------------------------------- /src/modules/assignment/docUtils/assignment.paramdocs.ts: -------------------------------------------------------------------------------- 1 | import { ApiParamOptions } from '@nestjs/swagger'; 2 | 3 | export const courseId: ApiParamOptions = { 4 | name: 'courseId', 5 | type: String, 6 | description: 'Course Id in the form of MongoId', 7 | example: '60ccf3758dc53371bd4d0154', 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/firebase.ts: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import firebaseAccountCredentials from './service'; 3 | 4 | const serviceAccount = firebaseAccountCredentials[ 5 | process.env.NODE_ENV 6 | ] as admin.ServiceAccount; 7 | 8 | export default { 9 | credential: admin.credential.cert(serviceAccount), 10 | }; 11 | -------------------------------------------------------------------------------- /src/modules/announcements/docUtils/announcement.paramdocs.ts: -------------------------------------------------------------------------------- 1 | import { ApiParamOptions } from '@nestjs/swagger'; 2 | 3 | export const announcementId: ApiParamOptions = { 4 | name: 'announcementId', 5 | type: String, 6 | description: 'Announcement Id in the form of MongoId', 7 | example: '60ccf3758dc53371bd4d0154', 8 | }; 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | edu-server-app: 4 | container_name: edu-server-app 5 | image: edu-server-app 6 | restart: always 7 | command: npm start 8 | build: . 9 | ports: 10 | - "5000:5000" 11 | volumes: 12 | - ./:/usr/src/app/ 13 | - /usr/src/app/node_modules 14 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "plugins": [ 6 | { 7 | "name": "@nestjs/swagger", 8 | "options": { 9 | "introspectComments": true, 10 | "dtoFileNameSuffix": [ 11 | ".dto.ts", 12 | ".responsedoc.ts" 13 | ] 14 | } 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Nest Framework", 8 | "args": ["${workspaceFolder}/src/main.ts"], 9 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], 10 | "sourceMaps": true, 11 | "cwd": "${workspaceRoot}", 12 | "protocol": "inspector" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "src", 13 | "incremental": true, 14 | "resolveJsonModule": true, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/chat/chat.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ChatController } from './chat.controller'; 3 | import { ChatService } from './chat.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { ChatSchema } from './schema/chat.schema'; 6 | @Module({ 7 | imports: [MongooseModule.forFeature([{ name: 'Chat', schema: ChatSchema }])], 8 | controllers: [ChatController], 9 | providers: [ChatService], 10 | }) 11 | export class ChatModule {} 12 | -------------------------------------------------------------------------------- /src/modules/mentor/docUtils/mentor.paramdocs.ts: -------------------------------------------------------------------------------- 1 | import { ApiParamOptions } from '@nestjs/swagger'; 2 | 3 | export const mentorId: ApiParamOptions = { 4 | name: 'mentorId', 5 | type: String, 6 | description: 'mentor Id in the form of MongoId', 7 | example: '60ccf3758dc53371bd4d0154', 8 | }; 9 | 10 | export const courseId: ApiParamOptions = { 11 | name: 'courseId', 12 | type: String, 13 | description: 'Course Id in the form of MongoId', 14 | example: '60ccf3758dc53371bd4d0154', 15 | }; 16 | -------------------------------------------------------------------------------- /src/modules/course/docUtils/course.paramdocs.ts: -------------------------------------------------------------------------------- 1 | import { ApiParamOptions } from '@nestjs/swagger'; 2 | 3 | export const courseId: ApiParamOptions = { 4 | name: 'courseId', 5 | type: String, 6 | description: 'Course Id in the form of MongoId', 7 | example: '60ccf3758dc53371bd4d0154', 8 | }; 9 | 10 | export const scheduleId: ApiParamOptions = { 11 | name: 'scheduleId', 12 | type: String, 13 | description: 'schedule Id in the form of MongoId', 14 | example: '60ccf3758dc53371bd4d0154', 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | 37 | .env 38 | service.json -------------------------------------------------------------------------------- /src/modules/user/dto/update-enrolled.dto.ts: -------------------------------------------------------------------------------- 1 | interface video { 2 | num: number; 3 | timestamp?: Date; 4 | } 5 | 6 | export class UpdateEnrolledDTO { 7 | /** 8 | * videos watched by the student 9 | * @example [false, false] 10 | */ 11 | videosWatched: boolean[]; 12 | 13 | /** 14 | * The assignments done by the student 15 | * @example [false, false] 16 | */ 17 | assignmentsDone: boolean[]; 18 | 19 | /** 20 | * The current video where student left 21 | * @example { num: 1, timestamp: Date.now() } 22 | */ 23 | currentVideo: video[]; 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/announcements/announcement.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AnnouncementController } from './announcement.controller'; 3 | import { AnnouncementService } from './announcement.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { AnnouncementSchema } from './schema/announcement.schema'; 6 | @Module({ 7 | imports: [ 8 | MongooseModule.forFeature([ 9 | { name: 'Announcement', schema: AnnouncementSchema }, 10 | ]), 11 | ], 12 | controllers: [AnnouncementController], 13 | providers: [AnnouncementService], 14 | }) 15 | export class AnnouncementModule {} 16 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | MONGOURL="mongodb+srv://:@/?retryWrites=true&w=majority" or your local mongo atlas url 2 | NODE_ENV=dev 3 | PORT=5000 4 | 5 | TYPE=service_account 6 | PROJECT_ID=YOUR'S CREDENTIAL FROM FIREBASE 7 | PRIVATE_KEY_ID=YOUR'S CREDENTIAL FROM FIREBASE 8 | PRIVATE_KEY=YOUR'S CREDENTIAL FROM FIREBASE 9 | CLIENT_EMAIL=YOUR'S CREDENTIAL FROM FIREBASE 10 | CLIENT_ID=YOUR'S CREDENTIAL FROM FIREBASE 11 | AUTH_URI=YOUR'S CREDENTIAL FROM FIREBASE 12 | TOKEN_URI=YOUR'S CREDENTIAL FROM FIREBASE 13 | AUTH_PROVIDER_X509_CERT_URL=YOUR'S CREDENTIAL FROM FIREBASE 14 | CLIENT_X509_CERT_URL=YOUR'S CREDENTIAL FROM FIREBASE 15 | -------------------------------------------------------------------------------- /src/modules/mentor/mentor.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MentorController } from './mentor.controller'; 3 | import { MentorService } from './mentor.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { MentorSchema } from './schema/mentor.schema'; 6 | import { CourseSchema } from '../course/schema/course.schema'; 7 | 8 | @Module({ 9 | imports: [ 10 | MongooseModule.forFeature([ 11 | { name: 'Mentor', schema: MentorSchema }, 12 | { name: 'Course', schema: CourseSchema }, 13 | ]), 14 | ], 15 | controllers: [MentorController], 16 | providers: [MentorService], 17 | }) 18 | export class MentorModule {} 19 | -------------------------------------------------------------------------------- /src/middleware/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | 4 | @Injectable() 5 | export class RolesGuard implements CanActivate { 6 | constructor(private reflector: Reflector) {} 7 | 8 | canActivate(context: ExecutionContext): boolean { 9 | const roles = this.reflector.get('roles', context.getHandler()); 10 | if (!roles) { 11 | return true; 12 | } 13 | const request = context.switchToHttp().getRequest(); 14 | const user = request['user']; 15 | const hasRole = () => roles.includes(user.role); 16 | return user && user.role && hasRole(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, UseGuards } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | import { ApiBearerAuth } from '@nestjs/swagger'; 4 | import { RolesGuard } from './middleware/roles.guard'; 5 | import { Roles } from './middleware/role.decorator'; 6 | import { Role } from './roles/role.enum'; 7 | 8 | @ApiBearerAuth() 9 | @Controller() 10 | @UseGuards(RolesGuard) 11 | export class AppController { 12 | constructor(private readonly appService: AppService) {} 13 | 14 | @Get('/hello') 15 | @Roles(Role.STUDENT) 16 | getHey(@Req() request): string { 17 | return 'Hello ' + request['user']?.email + request['user']?.role + '!'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/doubt/docUtils/doubt.paramdocs.ts: -------------------------------------------------------------------------------- 1 | import { ApiParamOptions } from '@nestjs/swagger'; 2 | 3 | export const courseId: ApiParamOptions = { 4 | name: 'courseId', 5 | type: String, 6 | description: 'Course Id in the form of MongoId', 7 | example: '60ccf3758dc53371bd4d0154', 8 | }; 9 | 10 | export const doubtId: ApiParamOptions = { 11 | name: 'doubtId', 12 | type: String, 13 | description: 'Doubt Id in the form of MongoId', 14 | example: '60ccf3758dc53371bd4d0154', 15 | }; 16 | 17 | export const doubtAnswerId: ApiParamOptions = { 18 | name: 'doubtAnswerId', 19 | type: String, 20 | description: 'DoubtAnswer Id in the form of MongoId', 21 | example: '60ccf3758dc53371bd4d0154', 22 | }; 23 | -------------------------------------------------------------------------------- /src/modules/assignment/docUtils/assignment.responsedoc.ts: -------------------------------------------------------------------------------- 1 | export default class AssignmentResponseBody { 2 | /** 3 | * CourseId 4 | * @example 60ccf3037025096f45cb87bf 5 | */ 6 | id: string; 7 | 8 | /** 9 | * The assignment link 10 | * @example 'https://codeforcause.org/courses/assignments/1' 11 | */ 12 | assignmentLink: string; 13 | 14 | /** 15 | * The assignment description 16 | * @example 'In this assignment you will have to implement the knowledge of functional component to make this component in React' 17 | */ 18 | assignmenDescription: string; 19 | 20 | /** 21 | * The creator of the assignment 22 | * @example 'John Doe' 23 | */ 24 | createdBy: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/assignment/assignment.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AssignmentController } from './assignment.controller'; 3 | import { AssignmentService } from './assignment.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { AssignmentSchema } from './schema/assignment.schema'; 6 | import { CourseSchema } from '../course/schema/course.schema'; 7 | @Module({ 8 | imports: [ 9 | MongooseModule.forFeature([ 10 | { name: 'Assignment', schema: AssignmentSchema }, 11 | { name: 'Course', schema: CourseSchema }, 12 | ]), 13 | ], 14 | controllers: [AssignmentController], 15 | providers: [AssignmentService], 16 | }) 17 | export class AssignmentModule {} 18 | -------------------------------------------------------------------------------- /src/modules/assignment/docUtils/apidoc.ts: -------------------------------------------------------------------------------- 1 | import AssignmentResponseBody from './assignment.responsedoc'; 2 | import { ApiResponseOptions } from '@nestjs/swagger'; 3 | 4 | const addAssignment: ApiResponseOptions = { 5 | description: 'Add an assignment', 6 | type: AssignmentResponseBody, 7 | }; 8 | 9 | const updateAssignment: ApiResponseOptions = { 10 | description: 'Update an assignment', 11 | type: AssignmentResponseBody, 12 | }; 13 | 14 | const deleteAssignment: ApiResponseOptions = { 15 | description: 'Delete an assignment', 16 | type: AssignmentResponseBody, 17 | }; 18 | 19 | const responses = { 20 | addAssignment, 21 | updateAssignment, 22 | deleteAssignment, 23 | }; 24 | 25 | export default responses; 26 | -------------------------------------------------------------------------------- /src/modules/chat/chat.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelToken } from '@nestjs/mongoose'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { ChatService } from './chat.service'; 4 | 5 | describe('ChatService', () => { 6 | let service: ChatService; 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ 10 | ChatService, 11 | { 12 | provide: getModelToken('Chat'), 13 | useValue: {}, 14 | }, 15 | ], 16 | }).compile(); 17 | service = module.get(ChatService); 18 | }); 19 | 20 | it('should be defined', () => { 21 | expect(service).toBeDefined(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/modules/course/dto/update-lecture.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsUrl } from 'class-validator'; 2 | 3 | export class UpdateLectureDto { 4 | /** 5 | * name of the lecture 6 | * @example "Play with pandas" 7 | */ 8 | @IsString() 9 | lectureName?: string; 10 | 11 | /** 12 | * The description of the lecture in the schedule 13 | * @example "The pandas library" 14 | */ 15 | @IsString() 16 | description?: string; 17 | 18 | /** 19 | * The length of the video 20 | * @example "5 minutes" 21 | */ 22 | @IsString() 23 | time?: string; 24 | 25 | /** 26 | * The url video 27 | * @example "https://codeforcause.org/video/1" 28 | */ 29 | @IsString() 30 | @IsUrl() 31 | lectureVideoUrl?: string; 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/doubt/dto/update-doubtAnswer.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsMongoId, IsNotEmpty, IsString } from 'class-validator'; 3 | import { Schema } from 'mongoose'; 4 | 5 | export class UpdateDoubtAnswerDto { 6 | /** 7 | * The name of the person who answered the doubt 8 | * @example '60ccf3037025096f45cb87bf' 9 | */ 10 | @IsNotEmpty() 11 | @IsMongoId() 12 | @IsNotEmpty() 13 | @ApiProperty({ type: Schema.Types.ObjectId }) 14 | answered_by: Schema.Types.ObjectId; 15 | 16 | /** 17 | * The asnwer to the doubt 18 | * @example "We use this to conserve time by applying alogorithm of lesser time complexity" 19 | */ 20 | @IsString() 21 | @IsNotEmpty() 22 | answer: string; 23 | } 24 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/modules/chat/chat.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatController } from './chat.controller'; 3 | import { ChatService } from './chat.service'; 4 | 5 | describe('ChatController', () => { 6 | let controller: ChatController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ChatController], 11 | providers: [ 12 | { 13 | provide: ChatService, 14 | useValue: {}, 15 | }, 16 | ], 17 | }).compile(); 18 | 19 | controller = module.get(ChatController); 20 | }); 21 | 22 | it('should be defined', () => { 23 | expect(controller).toBeDefined(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/modules/announcements/docUtils/announcement.responsedoc.ts: -------------------------------------------------------------------------------- 1 | export default class AnnouncementResponseBody { 2 | /** 3 | * The title of the announcement 4 | * @example 'New course on Web Development' 5 | */ 6 | title: string; 7 | 8 | /** 9 | * The description of the announcement 10 | * @example 'The wait is finally over as teh new course on Web Development has been released. The course will take you through the basics and help you learn and make projects along the way' 11 | */ 12 | description: string; 13 | 14 | /** 15 | * Whether the announcement was read or not 16 | * @example true 17 | */ 18 | read: boolean; 19 | 20 | /** 21 | * Creator of the announcement 22 | * @example 'John Doe' 23 | */ 24 | added_by: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/chat/schema/room.schema.ts: -------------------------------------------------------------------------------- 1 | import { Chat } from './chat.schema'; 2 | import { User } from '../../user/schema/user.schema'; 3 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 4 | import { Schema as SchemaTypes } from 'mongoose'; 5 | import { Document } from 'mongoose'; 6 | 7 | export type RoomDocument = Room & Document; 8 | 9 | @Schema() 10 | export class Room { 11 | @Prop() 12 | id: string; 13 | 14 | @Prop({ required: true, maxlength: 20, minlength: 5 }) 15 | name: string; 16 | 17 | @Prop({ type: SchemaTypes.Types.ObjectId, ref: 'Chat' }) 18 | chats: Chat; 19 | 20 | @Prop({ type: SchemaTypes.Types.ObjectId, ref: 'User' }) 21 | connectedUsers: User; 22 | } 23 | 24 | export const RoomSchema = SchemaFactory.createForClass(Room); 25 | -------------------------------------------------------------------------------- /src/modules/assignment/dto/create-assignment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, IsUrl } from 'class-validator'; 2 | 3 | export class CreateAssignmentDTO { 4 | /** 5 | * The assignment link 6 | * @example 'https://codeforcause.org/courses/assignments/1' 7 | */ 8 | @IsNotEmpty() 9 | @IsUrl() 10 | assignmentLink: string; 11 | 12 | /** 13 | * The assignment description 14 | * @example 'In this assignment you will have to implement the knowledge of functional component to make this component in React' 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | assignmenDescription: string; 19 | 20 | /** 21 | * The creator of the assignment 22 | * @example 'John Doe' 23 | */ 24 | @IsNotEmpty() 25 | @IsString() 26 | createdBy: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/assignment/dto/update-assignment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsUrl, IsString } from 'class-validator'; 2 | 3 | export class UpdateAssignmentDTO { 4 | /** 5 | * The assignment link 6 | * @example 'https://codeforcause.org/courses/assignments/1' 7 | */ 8 | @IsNotEmpty() 9 | @IsUrl() 10 | assignmentLink: string; 11 | 12 | /** 13 | * The assignment description 14 | * @example 'In this assignment you will have to implement the knowledge of functional component to make this component in React' 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | assignmenDescription: string; 19 | 20 | /** 21 | * The creator of the assignment 22 | * @example 'John Doe' 23 | */ 24 | @IsNotEmpty() 25 | @IsString() 26 | createdBy: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/doubt/doubt.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { DoubtController } from './doubt.controller'; 3 | import { DoubtService } from './doubt.service'; 4 | 5 | describe('DoubtController', () => { 6 | let controller: DoubtController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [DoubtController], 11 | providers: [ 12 | { 13 | provide: DoubtService, 14 | useValue: {}, 15 | }, 16 | ], 17 | }).compile(); 18 | 19 | controller = module.get(DoubtController); 20 | }); 21 | 22 | it('should be defined', () => { 23 | expect(controller).toBeDefined(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UserController } from './user.controller'; 3 | import { UserService } from './user.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import UserSchema from './schema/user.schema'; 6 | import { CourseSchema } from '../course/schema/course.schema'; 7 | import { EnrolledCourseSchema } from '../course/schema/enrolledCourse.schema'; 8 | @Module({ 9 | imports: [ 10 | MongooseModule.forFeature([ 11 | { name: 'User', schema: UserSchema }, 12 | { name: 'Course', schema: CourseSchema }, 13 | { name: 'Enrolled', schema: EnrolledCourseSchema }, 14 | ]), 15 | ], 16 | controllers: [UserController], 17 | providers: [UserService], 18 | }) 19 | export class UserModule {} 20 | -------------------------------------------------------------------------------- /src/modules/chat/schema/chat.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Schema as SchemaType, Document } from 'mongoose'; 3 | import { User } from '../../user/schema/user.schema'; 4 | import { Room } from './room.schema'; 5 | 6 | export type ChatDocument = Chat & Document; 7 | 8 | @Schema() 9 | export class Chat { 10 | @Prop({ required: true }) 11 | sender: string; 12 | 13 | @Prop({ required: true }) 14 | original_sender: string; 15 | 16 | @Prop({ required: true }) 17 | message: string; 18 | 19 | @Prop({ type: SchemaType.Types.ObjectId, ref: 'User' }) 20 | owner: User; 21 | 22 | @Prop({ type: SchemaType.Types.ObjectId, ref: 'Room' }) 23 | room: Room | string; 24 | } 25 | 26 | export const ChatSchema = SchemaFactory.createForClass(Chat); 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | 15 | **Describe the solution you'd like** 16 | 17 | 18 | **Describe alternatives you've considered** 19 | 20 | 21 | **Additional context** 22 | 23 | -------------------------------------------------------------------------------- /src/modules/course/dto/create-lecture.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, IsUrl } from 'class-validator'; 2 | 3 | export class CreateLectureDto { 4 | /** 5 | * name of the lecture 6 | * @example "Play with pandas" 7 | */ 8 | @IsNotEmpty() 9 | @IsString() 10 | lectureName: string; 11 | 12 | /** 13 | * The description of the lecture in the schedule 14 | * @example "The pandas library" 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | description: string; 19 | 20 | /** 21 | * The length of the video 22 | * @example "5 minutes" 23 | */ 24 | @IsNotEmpty() 25 | @IsString() 26 | time: string; 27 | 28 | /** 29 | * The url video 30 | * @example "https://codeforcause.org/video/1" 31 | */ 32 | @IsNotEmpty() 33 | @IsString() 34 | @IsUrl() 35 | lectureVideoUrl: string; 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/assignment/assignment.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AssignmentController } from './assignment.controller'; 3 | import { AssignmentService } from './assignment.service'; 4 | 5 | describe('AssignmentController', () => { 6 | let controller: AssignmentController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [AssignmentController], 11 | providers: [ 12 | { 13 | provide: AssignmentService, 14 | useValue: {}, 15 | }, 16 | ], 17 | }).compile(); 18 | 19 | controller = module.get(AssignmentController); 20 | }); 21 | 22 | it('should be defined', () => { 23 | expect(controller).toBeDefined(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/modules/assignment/schema/assignment.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | 4 | export type AssignmentDocument = Assignment & Document; 5 | 6 | @Schema({ timestamps: true }) 7 | export class Assignment { 8 | @Prop({ required: true }) 9 | assignmentLink: string; 10 | 11 | @Prop({ required: true }) 12 | assignmenDescription: string; 13 | 14 | @Prop({ required: true }) 15 | createdBy: string; 16 | } 17 | 18 | export const AssignmentSchema = SchemaFactory.createForClass(Assignment); 19 | 20 | AssignmentSchema.methods.toJSON = function () { 21 | const assignmentObject = this.toObject(); 22 | assignmentObject.id = assignmentObject._id; 23 | delete assignmentObject.__v; 24 | delete assignmentObject._id; 25 | return assignmentObject; 26 | }; 27 | -------------------------------------------------------------------------------- /src/modules/course/schema/lecture.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | 4 | export type LectureDocument = Lecture & Document; 5 | 6 | @Schema({ timestamps: true }) 7 | export class Lecture { 8 | @Prop({ required: true }) 9 | lectureName: string; 10 | 11 | @Prop({ required: true }) 12 | description: string; 13 | 14 | @Prop({ required: true }) 15 | lectureVideoUrl: string; 16 | 17 | @Prop({ required: true }) 18 | time: string; 19 | } 20 | 21 | export const LectureSchema = SchemaFactory.createForClass(Lecture); 22 | 23 | LectureSchema.methods.toJSON = function () { 24 | const lectureObject = this.toObject(); 25 | 26 | lectureObject.id = lectureObject._id; 27 | 28 | delete lectureObject.__v; 29 | delete lectureObject._id; 30 | 31 | return lectureObject; 32 | }; 33 | -------------------------------------------------------------------------------- /src/modules/assignment/assignment.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelToken } from '@nestjs/mongoose'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { AssignmentService } from './assignment.service'; 4 | 5 | describe('AssignmentService', () => { 6 | let service: AssignmentService; 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ 10 | AssignmentService, 11 | { 12 | provide: getModelToken('Assignment'), 13 | useValue: {}, 14 | }, 15 | { 16 | provide: getModelToken('Course'), 17 | useValue: {}, 18 | }, 19 | ], 20 | }).compile(); 21 | service = module.get(AssignmentService); 22 | }); 23 | 24 | it('should be defined', () => { 25 | expect(service).toBeDefined(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/modules/doubt/doubt.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { DoubtSchema } from './schema/doubt.schema'; 4 | import { DoubtController } from './doubt.controller'; 5 | import { DoubtService } from './doubt.service'; 6 | import { CourseSchema } from '../course/schema/course.schema'; 7 | import { DoubtAnswerSchema } from './schema/doubtAnswer.schema'; 8 | import UserSchema from '../user/schema/user.schema'; 9 | 10 | @Module({ 11 | imports: [ 12 | MongooseModule.forFeature([ 13 | { name: 'Doubt', schema: DoubtSchema }, 14 | { name: 'DoubtAnswer', schema: DoubtAnswerSchema }, 15 | { name: 'Course', schema: CourseSchema }, 16 | { name: 'User', schema: UserSchema }, 17 | ]), 18 | ], 19 | controllers: [DoubtController], 20 | providers: [DoubtService], 21 | }) 22 | export class DoubtModule {} 23 | -------------------------------------------------------------------------------- /src/modules/doubt/dto/create-doubtAnswer.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsMongoId, IsNotEmpty, IsString } from 'class-validator'; 3 | import { Schema } from 'mongoose'; 4 | 5 | export class CreateDoubtAnswerDto { 6 | /** 7 | * The id of the person who answered the doubt 8 | * @example '60ccf3037025096f45cb87bf' 9 | */ 10 | @IsNotEmpty() 11 | @IsMongoId() 12 | @IsNotEmpty() 13 | @ApiProperty({ type: Schema.Types.ObjectId }) 14 | answered_by: Schema.Types.ObjectId; 15 | 16 | /** 17 | * The anwwer to the doubt 18 | * @example "We use this to conserve time by applying alogorithm of lesser time complexity" 19 | */ 20 | @IsString() 21 | @IsNotEmpty() 22 | answer: string; 23 | 24 | /** 25 | * The person who answered the doubt 26 | * @example "John Doe" 27 | */ 28 | @IsString() 29 | @IsNotEmpty() 30 | answeredBy_name: string; 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/announcements/schema/announcement.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | 4 | export type AnnouncementDocument = Announcement & Document; 5 | 6 | @Schema({ timestamps: true }) 7 | export class Announcement { 8 | @Prop({ required: true }) 9 | title: string; 10 | 11 | @Prop({ required: true }) 12 | description: string; 13 | 14 | @Prop({ default: false }) 15 | read: boolean; 16 | 17 | @Prop({ required: true }) 18 | added_by: string; 19 | } 20 | 21 | export const AnnouncementSchema = SchemaFactory.createForClass(Announcement); 22 | 23 | AnnouncementSchema.methods.toJSON = function () { 24 | const announcementObject = this.toObject(); 25 | announcementObject.id = announcementObject._id; 26 | 27 | delete announcementObject.__v; 28 | delete announcementObject._id; 29 | 30 | return announcementObject; 31 | }; 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Node Workflows 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 18 | uses: actions/setup-node@v1 19 | - name: install and then run linter 20 | run: npm install eslint && npm run lint 21 | 22 | unit-test: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Use Node.js 27 | uses: actions/setup-node@v1 28 | - name: install 29 | run: npm ci 30 | - name: run-test 31 | run: npm run test 32 | -------------------------------------------------------------------------------- /src/modules/course/dto/update-review.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsEnum, 3 | IsNotEmpty, 4 | IsNumber, 5 | IsString, 6 | Max, 7 | Min, 8 | } from 'class-validator'; 9 | import { ReviewType } from '../review.enum'; 10 | 11 | export class UpdateReviewDto { 12 | /** 13 | * name of the reviewer 14 | * @example "John Doe" 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | reviewerName: string; 19 | 20 | /** 21 | * The description of the Reviw 22 | * @example "The course was just fantastic" 23 | */ 24 | @IsNotEmpty() 25 | @IsString() 26 | reviewDescription: string; 27 | 28 | /** 29 | * The type of Review 30 | * @example STUDENT 31 | */ 32 | @IsNotEmpty() 33 | @IsEnum(ReviewType) 34 | occupation: ReviewType; 35 | 36 | /** 37 | * The number of stars given in the review 38 | * @example 5 39 | */ 40 | @IsNotEmpty() 41 | @IsNumber() 42 | @Max(5) 43 | @Min(0) 44 | stars: number; 45 | } 46 | -------------------------------------------------------------------------------- /src/modules/doubt/schema/doubtAnswer.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | import { Schema as SchemaType } from 'mongoose'; 4 | 5 | export type DoubtAnswerDocument = DoubtAnswer & Document; 6 | 7 | @Schema({ timestamps: true }) 8 | export class DoubtAnswer { 9 | @Prop({ required: true }) 10 | answered_by: SchemaType.Types.ObjectId; 11 | 12 | @Prop({ required: true }) 13 | answer: string; 14 | 15 | @Prop() 16 | photoUrl: string; 17 | 18 | @Prop() 19 | answeredBy_name: string; 20 | } 21 | 22 | export const DoubtAnswerSchema = SchemaFactory.createForClass(DoubtAnswer); 23 | 24 | DoubtAnswerSchema.methods.toJSON = function () { 25 | const doubtAnswerObject = this.toObject(); 26 | doubtAnswerObject.id = doubtAnswerObject._id; 27 | 28 | delete doubtAnswerObject._id; 29 | delete doubtAnswerObject.__v; 30 | 31 | return doubtAnswerObject; 32 | }; 33 | -------------------------------------------------------------------------------- /src/swagger-ui/index.ts: -------------------------------------------------------------------------------- 1 | import { DocumentBuilder, SwaggerCustomOptions } from '@nestjs/swagger'; 2 | 3 | // configure swagger 4 | export const swaggerConfig = new DocumentBuilder() 5 | .setTitle('Edu Server') 6 | .setDescription('Edu Server API documentation') 7 | .setVersion('0.0.1') 8 | .setContact( 9 | 'Code for Cause', 10 | 'https://codeforcause.org/', 11 | 'team@codeforcause.org', 12 | ) 13 | .setLicense( 14 | 'MIT', 15 | 'https://github.com/codeforcauseorg/edu-server/blob/master/LICENSE', 16 | ) 17 | .addServer('http://localhost:5000/', 'Development Server') 18 | .addBearerAuth() 19 | .build(); 20 | 21 | // adding custom options 22 | export const customOptions: SwaggerCustomOptions = { 23 | swaggerOptions: { 24 | persistAuthorization: true, 25 | }, 26 | customCss: '.swagger-ui .topbar { display: none }', 27 | customSiteTitle: 'Edu Server API Docs', 28 | customfavIcon: 'https://codeforcause.org/favicon.ico', 29 | }; 30 | -------------------------------------------------------------------------------- /src/modules/announcements/dto/create-announcement.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsBoolean, IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class CreateAnnouncementDTO { 4 | /** 5 | * The title of the announcement 6 | * @example 'New course on Web Development' 7 | */ 8 | @IsNotEmpty() 9 | @IsString() 10 | title: string; 11 | 12 | /** 13 | * The description of the announcement 14 | * @example 'The wait is finally over as teh new course on Web Development has been released. The course will take you through the basics and help you learn and make projects along the way' 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | description: string; 19 | 20 | /** 21 | * Whether the announcement was read or not 22 | * @example true 23 | */ 24 | @IsNotEmpty() 25 | @IsBoolean() 26 | read: boolean; 27 | 28 | /** 29 | * Creator of the announcement 30 | * @example 'John Doe' 31 | */ 32 | @IsNotEmpty() 33 | @IsString() 34 | added_by: string; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/announcements/dto/update-announcement.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, IsBoolean } from 'class-validator'; 2 | 3 | export class UpdateAnnouncementDTO { 4 | /** 5 | * The title of the announcement 6 | * @example 'New course on Web Development' 7 | */ 8 | @IsNotEmpty() 9 | @IsString() 10 | title: string; 11 | 12 | /** 13 | * The description of the announcement 14 | * @example 'The wait is finally over as teh new course on Web Development has been released. The course will take you through the basics and help you learn and make projects along the way' 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | description: string; 19 | 20 | /** 21 | * Whether the announcement was read or not 22 | * @example true 23 | */ 24 | @IsNotEmpty() 25 | @IsBoolean() 26 | read: boolean; 27 | 28 | /** 29 | * Creator of the announcement 30 | * @example 'John Doe' 31 | */ 32 | @IsNotEmpty() 33 | @IsString() 34 | added_by: string; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/user/dto/create-enrolled.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsMongoId, IsNotEmpty } from 'class-validator'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { Schema } from 'mongoose'; 4 | 5 | interface video { 6 | num: number; 7 | timestamp?: Date; 8 | } 9 | 10 | export class CreateEnrolledDTO { 11 | @IsNotEmpty() 12 | @IsMongoId() 13 | @ApiProperty({ type: Schema.Types.ObjectId }) 14 | studentId: Schema.Types.ObjectId; 15 | 16 | /** 17 | * videos watched by the student 18 | * @example [false, false] 19 | */ 20 | videosWatched: boolean[]; 21 | 22 | /** 23 | * The assignments done by the student 24 | * @example [false, false] 25 | */ 26 | assignmentsDone: boolean[]; 27 | 28 | /** 29 | * The current video where student left 30 | * @example { num: 1, timestamp: Date.now() } 31 | */ 32 | currentVideo: video[]; 33 | 34 | @IsNotEmpty() 35 | @IsMongoId() 36 | @ApiProperty({ type: Schema.Types.ObjectId }) 37 | courseId: Schema.Types.ObjectId; 38 | } 39 | -------------------------------------------------------------------------------- /src/modules/mentor/docUtils/apidoc.ts: -------------------------------------------------------------------------------- 1 | import MentorResponseBody from './mentor.responsedoc'; 2 | import { ApiResponseOptions } from '@nestjs/swagger'; 3 | 4 | const addMentor: ApiResponseOptions = { 5 | description: 'Create Mentor with provided values', 6 | type: MentorResponseBody, 7 | }; 8 | 9 | const getAllMentors: ApiResponseOptions = { 10 | description: 'Get all the Mentors available in the database', 11 | type: [MentorResponseBody], 12 | }; 13 | 14 | const updateMentor: ApiResponseOptions = { 15 | description: 'Update Mentor', 16 | type: MentorResponseBody, 17 | }; 18 | 19 | const getMentor: ApiResponseOptions = { 20 | description: 'retrieve a mentor by mentorId', 21 | type: MentorResponseBody, 22 | }; 23 | 24 | const deleteMentor: ApiResponseOptions = { 25 | description: 'Delete Mentor', 26 | type: MentorResponseBody, 27 | }; 28 | 29 | const responses = { 30 | addMentor, 31 | getAllMentors, 32 | getMentor, 33 | updateMentor, 34 | deleteMentor, 35 | }; 36 | 37 | export default responses; 38 | -------------------------------------------------------------------------------- /src/modules/course/dto/create-schedule.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 | 3 | export class CreateScheduleDto { 4 | /** 5 | * The chapter name of the schedule item 6 | * @example 'Rest Apis' 7 | */ 8 | @IsNotEmpty() 9 | @IsString() 10 | chapterName: string; 11 | 12 | /** 13 | * The description of the schedule item 14 | * @example 'A session on REST API which is an application programming interface (API or web API) that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services.' 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | description: string; 19 | 20 | /** 21 | * The time required to finish the scheduled item 22 | * @example '11 hours' 23 | */ 24 | @IsNotEmpty() 25 | @IsString() 26 | time: string; 27 | 28 | /** 29 | * The lecture Number/Index among all lectures in the schedule 30 | * @example 8 31 | */ 32 | @IsNotEmpty() 33 | @IsNumber() 34 | lectureNumber: number; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/course/dto/update-schedule.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 | 3 | export class UpdateScheduleDto { 4 | /** 5 | * The chapter name of the schedule item 6 | * @example 'Rest Apis' 7 | */ 8 | @IsNotEmpty() 9 | @IsString() 10 | chapterName: string; 11 | 12 | /** 13 | * The description of the schedule item 14 | * @example 'A session on REST API which is an application programming interface (API or web API) that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services.' 15 | */ 16 | @IsNotEmpty() 17 | @IsString() 18 | description: string; 19 | 20 | /** 21 | * The time required to finish the scheduled item 22 | * @example '11 hours' 23 | */ 24 | @IsNotEmpty() 25 | @IsString() 26 | time: string; 27 | 28 | /** 29 | * The lecture Number/Index among all lectures in the schedule 30 | * @example 8 31 | */ 32 | @IsNotEmpty() 33 | @IsNumber() 34 | lectureNumber: number; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/doubt/doubt.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelToken } from '@nestjs/mongoose'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { DoubtService } from './doubt.service'; 4 | 5 | describe('DoubtService', () => { 6 | let service: any; 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ 10 | DoubtService, 11 | { 12 | provide: getModelToken('Doubt'), 13 | useValue: {}, 14 | }, 15 | { 16 | provide: getModelToken('Course'), 17 | useValue: {}, 18 | }, 19 | { 20 | provide: getModelToken('DoubtAnswer'), 21 | useValue: {}, 22 | }, 23 | { 24 | provide: getModelToken('User'), 25 | useValue: {}, 26 | }, 27 | ], 28 | }).compile(); 29 | service = module.resolve(DoubtService); 30 | }); 31 | 32 | it('should be defined', () => { 33 | expect(service).toBeDefined(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/modules/course/schema/review.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { User } from '../../user/schema/user.schema'; 3 | import { Document, Schema as SchemaTypes } from 'mongoose'; 4 | import { ReviewType } from '../review.enum'; 5 | 6 | export type ReviewDocument = Review & Document; 7 | 8 | @Schema({ timestamps: true }) 9 | export class Review { 10 | @Prop({ required: true, type: SchemaTypes.Types.ObjectId, ref: 'User' }) 11 | reviewerId: User; 12 | 13 | @Prop({ required: true }) 14 | reviewerName: string; 15 | 16 | @Prop({ required: true }) 17 | reviewDescription: string; 18 | 19 | @Prop({ required: true }) 20 | occupation: ReviewType; 21 | 22 | @Prop({}) 23 | stars: number; 24 | } 25 | 26 | export const ReviewSchema = SchemaFactory.createForClass(Review); 27 | 28 | ReviewSchema.methods.toJSON = function () { 29 | const reviewObject = this.toObject(); 30 | 31 | reviewObject.id = reviewObject._id; 32 | 33 | delete reviewObject.__v; 34 | delete reviewObject._id; 35 | 36 | return reviewObject; 37 | }; 38 | -------------------------------------------------------------------------------- /src/modules/course/schema/schedule.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | import { Lecture } from './lecture.schema'; 4 | import { Schema as SchemaTypes } from 'mongoose'; 5 | 6 | export type ScheduleDocument = Schedule & Document; 7 | 8 | @Schema({ timestamps: true }) 9 | export class Schedule { 10 | @Prop({ required: true }) 11 | chapterName: string; 12 | 13 | @Prop({ required: true }) 14 | description: string; 15 | 16 | @Prop({ required: true }) 17 | time: string; 18 | 19 | @Prop({ required: true }) 20 | lectureNumber: number; 21 | 22 | @Prop({ type: [{ type: SchemaTypes.Types.ObjectId, ref: 'Review' }] }) 23 | lecture: Lecture[]; 24 | } 25 | 26 | export const ScheduleSchema = SchemaFactory.createForClass(Schedule); 27 | 28 | ScheduleSchema.methods.toJSON = function () { 29 | const scheduleObject = this.toObject(); 30 | 31 | scheduleObject.id = scheduleObject._id; 32 | 33 | delete scheduleObject.__v; 34 | delete scheduleObject._id; 35 | 36 | return scheduleObject; 37 | }; 38 | -------------------------------------------------------------------------------- /src/modules/announcements/docUtils/apidoc.ts: -------------------------------------------------------------------------------- 1 | import AnnouncementResponseBody from './announcement.responsedoc'; 2 | import { ApiResponseOptions } from '@nestjs/swagger'; 3 | 4 | const getAllAnnouncement: ApiResponseOptions = { 5 | description: 'Get all the courses available in the database', 6 | type: [AnnouncementResponseBody], 7 | }; 8 | 9 | const getAnnouncement: ApiResponseOptions = { 10 | description: 'Get course by id from the database', 11 | type: AnnouncementResponseBody, 12 | }; 13 | 14 | const addAnnouncement: ApiResponseOptions = { 15 | description: 'Add a course', 16 | type: AnnouncementResponseBody, 17 | }; 18 | 19 | const updateAnnouncement: ApiResponseOptions = { 20 | description: 'Update a course', 21 | type: AnnouncementResponseBody, 22 | }; 23 | 24 | const deleteAnnouncement: ApiResponseOptions = { 25 | description: 'Delete a course', 26 | type: AnnouncementResponseBody, 27 | }; 28 | 29 | const responses = { 30 | getAllAnnouncement, 31 | getAnnouncement, 32 | addAnnouncement, 33 | updateAnnouncement, 34 | deleteAnnouncement, 35 | }; 36 | 37 | export default responses; 38 | -------------------------------------------------------------------------------- /src/modules/mentor/docUtils/mentor.responsedoc.ts: -------------------------------------------------------------------------------- 1 | export default class MentorResponseBody { 2 | /** 3 | * id of the mentor 4 | * @example '60ec0cd4e7de7e1940a0cc08' 5 | */ 6 | id: string; 7 | 8 | /** 9 | * name of the mentor 10 | * @example 'Anuj Garg' 11 | */ 12 | name: string; 13 | 14 | /** 15 | * Email of the mentor 16 | * @example 'anuj@codeforcause.com' 17 | */ 18 | email: string; 19 | 20 | /** 21 | * Assigned courses of the mentor 22 | * @example ["60ccf3037025096f45cb87ba", "60ccf3037025096f45cb87bq"] 23 | */ 24 | courses: string[]; 25 | 26 | /** 27 | * number of students under the mentorship of that mentor 28 | * @example 500 29 | */ 30 | number_of_students: number; 31 | 32 | /** 33 | * photo url of the mentor 34 | * @example 'https://g.gle/mypic.jpeg' 35 | */ 36 | mentorPhoto: string; 37 | 38 | /** 39 | * description of the mentor 40 | * @example 'I am a developer' 41 | */ 42 | aboutMe: string; 43 | 44 | /** 45 | * Tech Stack of the mentor 46 | * @example ['MERN', 'Python'] 47 | */ 48 | techStack: string[]; 49 | } 50 | -------------------------------------------------------------------------------- /src/modules/course/dto/create-review.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsEnum, 3 | IsMongoId, 4 | IsNotEmpty, 5 | IsNumber, 6 | IsString, 7 | Max, 8 | Min, 9 | } from 'class-validator'; 10 | import { ReviewType } from '../review.enum'; 11 | 12 | export class CreateReviewDto { 13 | /** 14 | * Reviewer's MongoId 15 | * @example "60dcbd2609c34c1e48d2d0ae" 16 | */ 17 | @IsNotEmpty() 18 | @IsMongoId() 19 | reviewerId: string; 20 | 21 | /** 22 | * name of the reviewer 23 | * @example "John Doe" 24 | */ 25 | @IsNotEmpty() 26 | @IsString() 27 | reviewerName: string; 28 | 29 | /** 30 | * The description of the Reviw 31 | * @example "The course was just fantastic" 32 | */ 33 | @IsNotEmpty() 34 | @IsString() 35 | reviewDescription: string; 36 | 37 | /** 38 | * The type of Review 39 | * @example STUDENT 40 | */ 41 | @IsNotEmpty() 42 | @IsEnum(ReviewType) 43 | occupation: ReviewType; 44 | 45 | /** 46 | * The number of stars given in the review 47 | * @example 5 48 | */ 49 | @IsNotEmpty() 50 | @IsNumber() 51 | @Max(5) 52 | @Min(0) 53 | stars: number; 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Code for Cause 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/modules/user/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateUserDTO { 4 | /** 5 | * First name of the user 6 | * @example 'John' 7 | */ 8 | @IsNotEmpty() 9 | readonly first_name: string; 10 | 11 | /** 12 | * Last name of the user 13 | * @example 'Doe' 14 | */ 15 | @IsNotEmpty() 16 | readonly last_name: string; 17 | 18 | /** 19 | * Phone number of the user 20 | * @example '9000500000' 21 | */ 22 | readonly phone: string; 23 | 24 | /** 25 | * Address of the user 26 | * @example 'Block C Amsterdam' 27 | */ 28 | readonly address?: string; 29 | 30 | /** 31 | * Description of the user 32 | * @example 'Aspiring Software Developer' 33 | */ 34 | readonly description?: string; 35 | 36 | /** 37 | * score of user 38 | * @example 100 39 | */ 40 | readonly score: number; 41 | 42 | /** 43 | * The photo url 44 | * @example 'https://g.gle/mypic.jpeg' 45 | */ 46 | photoUrl?: string; 47 | 48 | /** 49 | * The cover photo url 50 | * @example 'https://g.gle/mycover.jpeg' 51 | */ 52 | coverPhotoUrl?: string; 53 | } 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | 13 | **To Reproduce** 14 | 21 | 22 | **Expected behavior** 23 | 24 | 25 | **Screenshots (If applicable)** 26 | 27 | 28 | **Desktop (please complete the following information):** 29 | - OS: 30 | - Browser 31 | - Version 32 | 33 | **Smartphone (please complete the following information):** 34 | - Device: 35 | - OS: 36 | - Browser 37 | - Version 38 | 39 | **Additional context** 40 | 41 | -------------------------------------------------------------------------------- /src/modules/mentor/schema/mentor.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | import { Course } from '../../course/schema/course.schema'; 4 | 5 | export type MentorDocument = Mentor & Document; 6 | 7 | @Schema({ timestamps: true }) 8 | export class Mentor { 9 | @Prop({ required: true }) 10 | name: string; 11 | 12 | @Prop({ required: true }) 13 | email: string; 14 | 15 | @Prop({ default: [] }) 16 | courses: Course[]; 17 | 18 | @Prop({ default: 0 }) 19 | number_of_students: number; 20 | 21 | @Prop({ required: true }) 22 | mentorPhoto: string; 23 | 24 | @Prop({ required: true }) 25 | aboutMe: string; 26 | 27 | @Prop({ required: true }) 28 | techStack: string[]; 29 | } 30 | 31 | export const MentorSchema = SchemaFactory.createForClass(Mentor); 32 | 33 | MentorSchema.methods.toJSON = function () { 34 | const mentorObject = this.toObject(); 35 | mentorObject.id = mentorObject._id; 36 | 37 | delete mentorObject._id; 38 | delete mentorObject.__v; 39 | delete mentorObject['createdAt']; 40 | delete mentorObject['updatedAt']; 41 | 42 | return mentorObject; 43 | }; 44 | -------------------------------------------------------------------------------- /src/modules/course/dto/create-mentor.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsArray, 3 | IsEmail, 4 | IsNotEmpty, 5 | IsNumber, 6 | IsString, 7 | IsUrl, 8 | } from 'class-validator'; 9 | 10 | export class CreateMentorDTO { 11 | /** 12 | * name of the mentor 13 | * @example 'Anuj Garg' 14 | */ 15 | @IsNotEmpty() 16 | @IsString() 17 | name: string; 18 | 19 | /** 20 | * Email of the mentor 21 | * @example 'anuj@codeforcause.org' 22 | */ 23 | @IsNotEmpty() 24 | @IsEmail() 25 | email: string; 26 | 27 | /** 28 | * number of students under the mentorship of that mentor 29 | * @example 500 30 | */ 31 | @IsNotEmpty() 32 | @IsNumber() 33 | number_of_students: number; 34 | 35 | /** 36 | * photo url of the mentor 37 | * @example 'https://g.gle/mypic.jpeg' 38 | */ 39 | @IsNotEmpty() 40 | @IsUrl() 41 | mentorPhoto: string; 42 | 43 | /** 44 | * description of the mentor 45 | * @example 'I am a developer' 46 | */ 47 | @IsString() 48 | aboutMe?: string; 49 | 50 | /** 51 | * Tech Stack of the mentor 52 | * @example ['MERN', 'Python'] 53 | */ 54 | @IsNotEmpty() 55 | @IsArray() 56 | techStack: string[]; 57 | } 58 | -------------------------------------------------------------------------------- /src/modules/mentor/dto/create-mentor.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsArray, 3 | IsEmail, 4 | IsNotEmpty, 5 | IsNumber, 6 | IsString, 7 | IsUrl, 8 | } from 'class-validator'; 9 | 10 | export class CreateMentorDTO { 11 | /** 12 | * name of the mentor 13 | * @example 'Anuj Garg' 14 | */ 15 | @IsNotEmpty() 16 | @IsString() 17 | name: string; 18 | 19 | /** 20 | * Email of the mentor 21 | * @example 'anuj@codeforcause.org' 22 | */ 23 | @IsNotEmpty() 24 | @IsEmail() 25 | email: string; 26 | 27 | /** 28 | * number of students under the mentorship of that mentor 29 | * @example 500 30 | */ 31 | @IsNotEmpty() 32 | @IsNumber() 33 | number_of_students: number; 34 | 35 | /** 36 | * photo url of the mentor 37 | * @example 'https://g.gle/mypic.jpeg' 38 | */ 39 | @IsNotEmpty() 40 | @IsUrl() 41 | mentorPhoto: string; 42 | 43 | /** 44 | * description of the mentor 45 | * @example 'I am a developer' 46 | */ 47 | @IsString() 48 | aboutMe?: string; 49 | 50 | /** 51 | * Tech Stack of the mentor 52 | * @example ['MERN', 'Python'] 53 | */ 54 | @IsNotEmpty() 55 | @IsArray() 56 | techStack: string[]; 57 | } 58 | -------------------------------------------------------------------------------- /src/modules/mentor/dto/update-mentor.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsArray, 3 | IsNotEmpty, 4 | IsNumber, 5 | IsString, 6 | IsUrl, 7 | } from 'class-validator'; 8 | 9 | export class UpdateMentorDTO { 10 | /** 11 | * name of the mentor 12 | * @example 'Anuj Garg' 13 | */ 14 | @IsNotEmpty() 15 | @IsString() 16 | name: string; 17 | 18 | /** 19 | * Email of the mentor 20 | * @example 'anuj@codeforcause.org' 21 | */ 22 | @IsNotEmpty() 23 | @IsString() 24 | email: string; 25 | 26 | /** 27 | * number of students under the mentorship of that mentor 28 | * @example 500 29 | */ 30 | @IsNotEmpty() 31 | @IsNumber() 32 | number_of_students: number; 33 | 34 | /** 35 | * photo url of the mentor 36 | * @example 'https://g.gle/mypic.jpeg' 37 | */ 38 | @IsNotEmpty() 39 | @IsUrl() 40 | mentorPhoto: string; 41 | 42 | /** 43 | * description of the mentor 44 | * @example 'I am a developer' 45 | */ 46 | @IsNotEmpty() 47 | @IsString() 48 | aboutMe: string; 49 | 50 | /** 51 | * Tech Stack of the mentor 52 | * @example ['MERN', 'Python'] 53 | */ 54 | @IsNotEmpty() 55 | @IsArray() 56 | techStack: string[]; 57 | } 58 | -------------------------------------------------------------------------------- /src/config/service.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | dotenv.config(); 3 | 4 | const config = { 5 | dev: { 6 | type: process.env.TYPE, 7 | project_id: process.env.PROJECT_ID, 8 | private_key_id: process.env.PRIVATE_KEY_ID, 9 | private_key: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'), 10 | client_email: process.env.CLIENT_EMAIL, 11 | client_id: process.env.CLIENT_ID, 12 | auth_uri: process.env.AUTH_URI, 13 | token_uri: process.env.TOKEN_URI, 14 | auth_provider_x509_cert_url: process.env.AUTH_PROVIDER_X509_CERT_URL, 15 | client_x509_cert_url: process.env.CLIENT_X509_CERT_URL, 16 | }, 17 | production: { 18 | type: process.env.TYPE, 19 | project_id: process.env.PROJECT_ID, 20 | private_key_id: process.env.PRIVATE_KEY_ID, 21 | private_key: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'), 22 | client_email: process.env.CLIENT_EMAIL, 23 | client_id: process.env.CLIENT_ID, 24 | auth_uri: process.env.AUTH_URI, 25 | token_uri: process.env.TOKEN_URI, 26 | auth_provider_x509_cert_url: process.env.AUTH_PROVIDER_X509_CERT_URL, 27 | client_x509_cert_url: process.env.CLIENT_X509_CERT_URL, 28 | }, 29 | }; 30 | 31 | export default config; 32 | -------------------------------------------------------------------------------- /src/main-firebase.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { ExpressAdapter } from '@nestjs/platform-express'; 4 | import * as express from 'express'; 5 | import * as functions from 'firebase-functions'; 6 | import * as admin from 'firebase-admin'; 7 | 8 | async function createFunction(expressInstance) { 9 | const app = await NestFactory.create( 10 | AppModule, 11 | new ExpressAdapter(expressInstance), 12 | ); 13 | 14 | await app.init(); 15 | } 16 | 17 | const expressServer = express(); 18 | createFunction(expressServer); 19 | 20 | export const api = functions 21 | .region('europe-west1') 22 | .https.onRequest(async (request, response) => { 23 | expressServer(request, response); 24 | }); 25 | 26 | const usersCollection = admin.firestore().collection('users'); 27 | export const saveUser = functions.auth.user().onCreate((user) => { 28 | const userRef = usersCollection.doc(user.uid); 29 | const data = { 30 | name: user.displayName, 31 | email: user.email, 32 | emailVerified: user.emailVerified, 33 | photoURL: user.photoURL, 34 | applications: {}, 35 | }; 36 | userRef.set(data); 37 | return data; 38 | }); 39 | -------------------------------------------------------------------------------- /src/modules/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelToken } from '@nestjs/mongoose'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { UserService } from './user.service'; 4 | 5 | describe('UserService', () => { 6 | let service: any; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | providers: [ 11 | UserService, 12 | { 13 | provide: getModelToken('User'), 14 | useValue: {}, 15 | }, 16 | { 17 | provide: getModelToken('Course'), 18 | useValue: {}, 19 | }, 20 | { 21 | provide: getModelToken('Enrolled'), 22 | useValue: {}, 23 | }, 24 | ], 25 | }).compile(); 26 | 27 | service = module.resolve(UserService); 28 | }); 29 | 30 | it('should be defined', () => { 31 | expect(service).toBeDefined(); 32 | }); 33 | describe('Testing userService after mock', () => { 34 | //const id = new mongoose.Schema.Types.ObjectId('60bca010d17d463dd09baf9b'); 35 | it('testing get all method', () => { 36 | expect(typeof service.getAllUser).not.toEqual(null); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/modules/doubt/schema/doubt.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Schema as SchemaType } from 'mongoose'; 3 | import { TagType } from '../doubt-tag.enum'; 4 | import { DoubtAnswer } from './doubtAnswer.schema'; 5 | import { Document } from 'mongoose'; 6 | 7 | export type DoubtDocument = Doubt & Document; 8 | 9 | @Schema({ timestamps: true }) 10 | export class Doubt { 11 | @Prop() 12 | tags: TagType[]; 13 | 14 | @Prop({ required: true }) 15 | asked_by: SchemaType.Types.ObjectId; 16 | 17 | @Prop({ type: [{ type: SchemaType.Types.ObjectId, ref: 'DoubtAnswer' }] }) 18 | answers: DoubtAnswer[]; 19 | 20 | @Prop({ required: true }) 21 | question: string; 22 | 23 | @Prop({ default: false }) 24 | is_resolved: boolean; 25 | 26 | @Prop() 27 | request_mentor: boolean; 28 | 29 | @Prop() 30 | photoUrl: string; 31 | 32 | @Prop() 33 | askedBy_name: string; 34 | 35 | @Prop() 36 | doubtBody: string; 37 | } 38 | 39 | export const DoubtSchema = SchemaFactory.createForClass(Doubt); 40 | 41 | DoubtSchema.methods.toJSON = function () { 42 | const doubtObject = this.toObject(); 43 | doubtObject.id = doubtObject._id; 44 | 45 | delete doubtObject._id; 46 | delete doubtObject.__v; 47 | 48 | return doubtObject; 49 | }; 50 | -------------------------------------------------------------------------------- /src/modules/user/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsArray } from 'class-validator'; 3 | import { Schema } from 'mongoose'; 4 | 5 | export class UpdateUserDTO { 6 | /** 7 | * First name of the user 8 | * @example 'John' 9 | */ 10 | readonly first_name?: string; 11 | 12 | /** 13 | * Last name of the user 14 | * @example 'Doe' 15 | */ 16 | readonly last_name?: string; 17 | 18 | /** 19 | * Phone number of the user 20 | * @example '9000500000' 21 | */ 22 | readonly phone?: string; 23 | 24 | /** 25 | * Address of the user 26 | * @example 'Block C Amsterdam' 27 | */ 28 | readonly address?: string; 29 | 30 | /** 31 | * Description of the user 32 | * @example 'Aspiring Software Developer' 33 | */ 34 | readonly description?: string; 35 | 36 | /** 37 | * score of user 38 | * @example 100 39 | */ 40 | readonly score: number; 41 | 42 | @ApiProperty({ type: [Schema.Types.ObjectId] }) 43 | @IsArray() 44 | readonly wishlist?: Schema.Types.ObjectId[]; 45 | 46 | /** 47 | * The photo url 48 | * @example 'unsplash1231main.jpeg' 49 | */ 50 | photoUrl: string; 51 | 52 | /** 53 | * The cover photo url 54 | * @example 'unsplash123.jpeg' 55 | */ 56 | coverPhotoUrl?: string; 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/automatic_rebase.yml: -------------------------------------------------------------------------------- 1 | ##################################################################################### 2 | # Github Action to rebase pull request. 3 | # 4 | # Workflow starts when pr comment created or edited 5 | # 6 | # Job will not work if: 7 | # 1. Patch branch name is 'master' 8 | # 2. There was an updates in github actions in target branch (restriction by github) 9 | ##################################################################################### 10 | on: 11 | issue_comment: 12 | types: [created, edited] 13 | name: Automatic Rebase 14 | jobs: 15 | rebase: 16 | name: Rebase PR 17 | if: github.event.issue.pull_request != '' && github.event.comment.body == 'rebase' 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: khan/pull-request-comment-trigger@master 21 | name: React with rocket on run 22 | with: 23 | trigger: ',' 24 | reaction: rocket 25 | env: 26 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 27 | - run: 'echo We print it here for this action to work' 28 | if: 'true' 29 | - name: Checkout the latest code 30 | uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | - name: Automatic Rebase 34 | uses: cirrus-actions/rebase@1.3.1 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /src/modules/course/schema/enrolledCourse.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | import { Schema as SchemaType } from 'mongoose'; 4 | 5 | export type EnrolledCourseDocument = EnrolledCourse & Document; 6 | 7 | interface video { 8 | num: number; 9 | timestamp?: Date; 10 | } 11 | 12 | @Schema({ 13 | toJSON: { virtuals: true, getters: true }, 14 | toObject: { virtuals: true, getters: true }, 15 | }) 16 | export class EnrolledCourse { 17 | @Prop({ required: true }) 18 | studentId: SchemaType.Types.ObjectId; 19 | 20 | @Prop() 21 | videosWatched: boolean[]; 22 | 23 | @Prop() 24 | assignmentsDone: boolean[]; 25 | 26 | @Prop() 27 | currentVideo: video[]; 28 | 29 | @Prop({ required: true }) 30 | courseId: SchemaType.Types.ObjectId; 31 | } 32 | 33 | export const EnrolledCourseSchema = 34 | SchemaFactory.createForClass(EnrolledCourse); 35 | 36 | EnrolledCourseSchema.methods.toJSON = function () { 37 | const enrolledCourseObject = this.toObject(); 38 | enrolledCourseObject.id = enrolledCourseObject._id; 39 | 40 | delete enrolledCourseObject.__v; 41 | delete enrolledCourseObject._id; 42 | 43 | return enrolledCourseObject; 44 | }; 45 | 46 | EnrolledCourseSchema.virtual('students', { 47 | ref: 'User', 48 | localField: 'studentId', 49 | foreignField: '_id', 50 | }); 51 | -------------------------------------------------------------------------------- /src/modules/course/course.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { CourseSchema } from './schema/course.schema'; 4 | import { CourseController } from './course.controller'; 5 | import { CourseService } from './course.service'; 6 | import { ScheduleSchema } from './schema/schedule.schema'; 7 | import { ReviewSchema } from './schema/review.schema'; 8 | import { DoubtSchema } from '../doubt/schema/doubt.schema'; 9 | import { DoubtAnswerSchema } from '../doubt/schema/doubtAnswer.schema'; 10 | import { AssignmentSchema } from '../assignment/schema/assignment.schema'; 11 | import { LectureSchema } from './schema/lecture.schema'; 12 | import { MentorSchema } from 'modules/mentor/schema/mentor.schema'; 13 | 14 | @Module({ 15 | imports: [ 16 | MongooseModule.forFeature([ 17 | { name: 'Course', schema: CourseSchema }, 18 | { name: 'Schedule', schema: ScheduleSchema }, 19 | { name: 'Review', schema: ReviewSchema }, 20 | { name: 'Doubt', schema: DoubtSchema }, 21 | { name: 'DoubtAnswer', schema: DoubtAnswerSchema }, 22 | { name: 'Assignment', schema: AssignmentSchema }, 23 | { name: 'Lecture', schema: LectureSchema }, 24 | { name: 'Mentor', schema: MentorSchema }, 25 | ]), 26 | ], 27 | controllers: [CourseController], 28 | providers: [CourseService], 29 | }) 30 | export class CourseModule {} 31 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { swaggerConfig, customOptions } from './swagger-ui'; 4 | import { SwaggerModule } from '@nestjs/swagger'; 5 | import { ValidationPipe } from '@nestjs/common'; 6 | import * as helmet from 'helmet'; 7 | import { Request, Response, NextFunction } from 'express'; 8 | import admin from 'firebase-admin'; 9 | import firebaseAccountCredentials from './config/firebase'; 10 | 11 | async function bootstrap() { 12 | const app = await NestFactory.create(AppModule); 13 | 14 | // setup swagger-ui 15 | const document = SwaggerModule.createDocument(app, swaggerConfig); 16 | SwaggerModule.setup('api', app, document, customOptions); 17 | 18 | // Firebase Initialisation 19 | admin.initializeApp(firebaseAccountCredentials); 20 | 21 | app.enableCors(); 22 | app.use(helmet()); 23 | 24 | // The Cors handling middleware 25 | app.use((req: Request, res: Response, next: NextFunction) => { 26 | res.set('Access-Control-Allow-Origin', '*'); 27 | res.set('Access-Control-Allow-Headers', '*'); 28 | res.set('Access-Control-Allow-Methods', '*'); 29 | if (req.method === 'OPTIONS') { 30 | res.status(200).end(); 31 | return; 32 | } 33 | next(); 34 | }); 35 | app.useGlobalPipes(new ValidationPipe()); 36 | 37 | const PORT = process.env.PORT || 5000; 38 | await app.listen(PORT); 39 | console.log('App is listening on port:', PORT); 40 | } 41 | 42 | bootstrap(); 43 | export default admin; 44 | -------------------------------------------------------------------------------- /src/modules/user/schema/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, Schema as SchemaTypes } from 'mongoose'; 3 | import { Role } from '../../../roles/role.enum'; 4 | 5 | export type UserDocument = User & Document; 6 | 7 | @Schema({ timestamps: true }) 8 | export class User { 9 | @Prop() 10 | first_name: string; 11 | 12 | @Prop() 13 | last_name: string; 14 | 15 | @Prop({ required: true }) 16 | email: string; 17 | 18 | @Prop() 19 | phone: string; 20 | 21 | @Prop() 22 | photoUrl: string; 23 | 24 | @Prop() 25 | coverPhotoUrl: string; 26 | 27 | @Prop() 28 | address: string; 29 | 30 | @Prop() 31 | description: string; 32 | 33 | @Prop({ default: 0 }) 34 | score: number; 35 | 36 | @Prop({ default: Role.STUDENT }) 37 | role: string; 38 | 39 | @Prop({ default: [] }) 40 | wishlist: SchemaTypes.Types.ObjectId[]; 41 | 42 | @Prop({ default: [] }) 43 | cartList: SchemaTypes.Types.ObjectId[]; 44 | 45 | @Prop() 46 | fId: string; 47 | 48 | @Prop() 49 | log_in_time: string; 50 | } 51 | 52 | const UserSchema = SchemaFactory.createForClass(User); 53 | 54 | UserSchema.index({ score: 1 }); 55 | export default UserSchema; 56 | 57 | UserSchema.methods.toJSON = function () { 58 | const userObject = this.toObject(); 59 | userObject.id = userObject._id; 60 | 61 | delete userObject._id; 62 | delete userObject.__v; 63 | delete userObject['createdAt']; 64 | delete userObject['updatedAt']; 65 | 66 | return userObject; 67 | }; 68 | -------------------------------------------------------------------------------- /src/modules/doubt/dto/update-doubt.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { 3 | IsArray, 4 | IsBoolean, 5 | IsEnum, 6 | IsMongoId, 7 | IsNotEmpty, 8 | IsOptional, 9 | IsString, 10 | } from 'class-validator'; 11 | import { Schema } from 'mongoose'; 12 | import { TagType } from '../doubt-tag.enum'; 13 | 14 | export class UpdateDoubtDto { 15 | /** 16 | * The tags associated with the doubt 17 | * @example ["Web development"] 18 | */ 19 | @IsNotEmpty() 20 | @IsArray() 21 | @IsEnum(TagType, { each: true }) 22 | tags?: TagType[]; 23 | 24 | /** 25 | * The name of the person who asked the doubt 26 | * @example '60ccf3037025096f45cb87bf' 27 | */ 28 | @IsNotEmpty() 29 | @IsMongoId() 30 | @IsNotEmpty() 31 | @ApiProperty({ type: Schema.Types.ObjectId }) 32 | asked_by: Schema.Types.ObjectId; 33 | 34 | /** 35 | * The question/doubt 36 | * @example "Why do we use memoization over tabulation ?" 37 | */ 38 | @IsString() 39 | @IsNotEmpty() 40 | question: string; 41 | 42 | /** 43 | * Whether the metor's assistance is needed or not for the doubt 44 | * @example true 45 | */ 46 | @IsBoolean() 47 | @IsOptional() 48 | @IsNotEmpty() 49 | request_mentor?: boolean; 50 | 51 | /** 52 | * Whether the doubt was resolved or not 53 | * @example true 54 | */ 55 | @IsBoolean() 56 | @IsOptional() 57 | @IsNotEmpty() 58 | is_resolved: boolean; 59 | 60 | /** 61 | * The doubt body 62 | * @example "the doubt description is ...." 63 | */ 64 | @IsString() 65 | doubtBody?: string; 66 | } 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # :arrow_down: Contributing Guidelines 2 | 3 | 4 | Follow these steps:hammer: for contributing: 5 | :green_heart: Fork the repo. 6 | 7 | :pushpin: Create a separate branch for any bug/feature with branch name: branch_ 8 | 9 | :white_check_mark: Commit once a feature is updated/bug is fixed. 10 | 11 | :pencil2: Fetch for any changes in the original repo by creating an upstream using: 12 | 13 | >git remote add upstream https://github.com/codeforcauseorg/edu-server.git 14 | 15 | >git fetch upstream 16 | 17 | :tada: Merge the changes in your branch. 18 | 19 | >git merge upstream/main 20 | 21 | :alien: Push your changes to your branch. 22 | 23 | :wrench: Make a pull request from your branch. 24 | 25 | :heavy_plus_sign: Before submitting a PR, check your code well and explain your work properly in the PR description. 26 | 27 | # :bookmark: Code advice 28 | 29 | Kindly maintain a clean code with proper comments. 30 | 31 | Commit messages should be written carefully and describe the changes made. 32 | 33 | Create separate PR for a single feature update/bug fix. 34 | 35 | # Join Our Community :green_heart: 36 | 37 | Meetup Group for Events: https://www.meetup.com/codeforcause/​ 38 | 39 | Announcements: https://t.me/codeforcause​ 40 | 41 | Discussions: https://t.me/codeforcausechat​ 42 | 43 | Discord: https://discord.gg/dydQp2Q​ 44 | 45 | Twitter: https://twitter.com/codeforcauseIn​ 46 | 47 | Instagram: https://instagram.com/codeforcause​ 48 | 49 | LinkedIn: https://www.linkedin.com/company/codeforcauseorg/ 50 | 51 | Facebook: https://www.facebook.com/codeforcauseorg 52 | -------------------------------------------------------------------------------- /src/modules/doubt/docUtils/apidoc.ts: -------------------------------------------------------------------------------- 1 | import DoubtResponseBody, { 2 | DoubtAnswerResponseBody, 3 | } from './doubt.responsedoc'; 4 | import { ApiResponseOptions } from '@nestjs/swagger'; 5 | 6 | const addDoubt: ApiResponseOptions = { 7 | description: 'Add a doubt', 8 | type: DoubtResponseBody, 9 | }; 10 | 11 | const updateDoubt: ApiResponseOptions = { 12 | description: 'Update a doubt', 13 | type: DoubtResponseBody, 14 | }; 15 | 16 | const deleteDoubt: ApiResponseOptions = { 17 | description: 'Delete a doubt', 18 | type: DoubtResponseBody, 19 | }; 20 | 21 | const addDoubtAnswer: ApiResponseOptions = { 22 | description: 'Add a doubt Answer', 23 | type: DoubtAnswerResponseBody, 24 | }; 25 | 26 | const updateDoubtAnswer: ApiResponseOptions = { 27 | description: 'Update a doubt Answer', 28 | type: DoubtAnswerResponseBody, 29 | }; 30 | 31 | const deleteDoubtAnswer: ApiResponseOptions = { 32 | description: 'Delete a doubt Answer', 33 | type: DoubtAnswerResponseBody, 34 | }; 35 | 36 | const getDoubtsForSelectedCourse: ApiResponseOptions = { 37 | description: 'Get doubts for courses', 38 | type: [DoubtResponseBody], 39 | }; 40 | 41 | const getAllDoubts: ApiResponseOptions = { 42 | description: 'Retrieve doubts list', 43 | type: [DoubtResponseBody], 44 | }; 45 | 46 | const getDoubtById: ApiResponseOptions = { 47 | description: 'get doubt by Id', 48 | type: DoubtResponseBody, 49 | }; 50 | 51 | const responses = { 52 | addDoubt, 53 | updateDoubt, 54 | deleteDoubt, 55 | addDoubtAnswer, 56 | updateDoubtAnswer, 57 | deleteDoubtAnswer, 58 | getDoubtsForSelectedCourse, 59 | getAllDoubts, 60 | getDoubtById, 61 | }; 62 | 63 | export default responses; 64 | -------------------------------------------------------------------------------- /src/modules/doubt/dto/create-doubt.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { 3 | IsArray, 4 | IsBoolean, 5 | IsEnum, 6 | IsMongoId, 7 | IsNotEmpty, 8 | IsOptional, 9 | IsString, 10 | } from 'class-validator'; 11 | import { Schema } from 'mongoose'; 12 | import { TagType } from '../doubt-tag.enum'; 13 | 14 | export class CreateDoubtDto { 15 | /** 16 | * The tags associated with the doubt 17 | * @example ["Web development"] 18 | */ 19 | @IsNotEmpty() 20 | @IsArray() 21 | @IsEnum(TagType, { each: true }) 22 | tags?: TagType[]; 23 | 24 | /** 25 | * The id of the person who asked the doubt 26 | * @example '60ccf3037025096f45cb87bf' 27 | */ 28 | @IsNotEmpty() 29 | @IsMongoId() 30 | @IsNotEmpty() 31 | @ApiProperty({ type: Schema.Types.ObjectId }) 32 | asked_by: Schema.Types.ObjectId; 33 | 34 | /** 35 | * The question/doubt 36 | * @example "Why do we use memoization over tabulation ?" 37 | */ 38 | @IsString() 39 | @IsNotEmpty() 40 | question: string; 41 | 42 | /** 43 | * Whether the metor's assistance is needed or not for the doubt 44 | * @example true 45 | */ 46 | @IsBoolean() 47 | @IsOptional() 48 | @IsNotEmpty() 49 | request_mentor?: boolean; 50 | 51 | /** 52 | * Whether the doubt was resolved or not 53 | * @example true 54 | */ 55 | @IsBoolean() 56 | @IsOptional() 57 | @IsNotEmpty() 58 | is_resolved: boolean; 59 | 60 | /** 61 | * The doubt body 62 | * @example "the doubt description is ...." 63 | */ 64 | @IsString() 65 | doubtBody?: string; 66 | 67 | /** 68 | * The person who aasked the doubt 69 | * @example "John Doe" 70 | */ 71 | @IsString() 72 | @IsNotEmpty() 73 | askedBy_name: string; 74 | } 75 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | 4 | 5 | **Related Issue** 6 | - issue goes here 7 | 8 | Fixes # [ISSUE] 9 | 15 | 16 | - [ ] Code 17 | - [ ] User Interface 18 | - [ ] New Feature 19 | - [ ] Documentation 20 | - [ ] Testing 21 | 22 | 23 | **Code/Quality Assurance Only** 24 | 25 | - Bug fix (non-breaking change which fixes an issue) 26 | - This change requires a documentation update (software upgrade on readme file) 27 | 28 | **How Has This Been Tested?** 29 | 30 | 31 | 32 | 33 | **Additional Info (OPTIONAL)** 34 | 35 | 36 | 37 | 38 | **Checklist** 39 | 40 | 44 | 45 | - [ ] My code follows the code style of this project. 46 | - [ ] My UI is responsive 47 | - [ ] My change requires a change to the documentation. 48 | - [ ] I have updated the documentation accordingly. 49 | - [ ] All new and existing tests passed. 50 | - [ ] My changes generate no new warnings 51 | - [ ] The title of my pull request is a short description of the requested changes. 52 | 53 | 54 | 55 | 56 | **Screenshots** 57 | Original | Updated 58 | :------------------------:|---------------------: 59 | ** original screenshot ** | ** updated screenshot ** 60 | -------------------------------------------------------------------------------- /src/modules/chat/chat.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | Injectable, 4 | InternalServerErrorException, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { InjectModel } from '@nestjs/mongoose'; 8 | import { Model } from 'mongoose'; 9 | import { CreateChatDTO } from './dto/create-chat.dto'; 10 | import { ChatDocument as Chat } from './schema/chat.schema'; 11 | 12 | @Injectable() 13 | export class ChatService { 14 | constructor(@InjectModel('Chat') private readonly ChatModel: Model) {} 15 | 16 | // fetch all Chats 17 | async getAllChat(): Promise { 18 | const Chats = await this.ChatModel.find().exec(); 19 | return Chats; 20 | } 21 | 22 | // Get a single Chat 23 | async getChat(ChatId: string): Promise { 24 | try { 25 | const chat = await this.ChatModel.findById(ChatId).exec(); 26 | if (chat) { 27 | return chat; 28 | } 29 | } catch (e) { 30 | throw new BadRequestException(e); 31 | } 32 | 33 | throw new NotFoundException('chat not found'); 34 | } 35 | 36 | // post a single Chat 37 | async addChat(CreateChatDTO: CreateChatDTO): Promise { 38 | try { 39 | return await new this.ChatModel(CreateChatDTO).save(); 40 | } catch (e) { 41 | throw new Error(e); 42 | } 43 | } 44 | 45 | // Edit Chat details 46 | async updateChat( 47 | chatId: string, 48 | CreateChatDTO: CreateChatDTO, 49 | ): Promise { 50 | try { 51 | const chat = await this.ChatModel.findByIdAndUpdate( 52 | chatId, 53 | CreateChatDTO, 54 | { new: true }, 55 | ); 56 | 57 | if (chat) { 58 | return chat; 59 | } 60 | } catch (e) { 61 | throw new InternalServerErrorException(e); 62 | } 63 | 64 | throw new NotFoundException('chat Not Found'); 65 | } 66 | 67 | // Delete a Chat 68 | async deleteChat(ChatID): Promise { 69 | const deletedChat = await this.ChatModel.findByIdAndRemove(ChatID); 70 | return deletedChat; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/modules/chat/chat.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Post, 7 | Put, 8 | NotFoundException, 9 | Param, 10 | } from '@nestjs/common'; 11 | import { ApiCreatedResponse, ApiProperty, ApiTags } from '@nestjs/swagger'; 12 | import { ChatService } from './chat.service'; //eslint-disable-line 13 | import { CreateChatDTO } from './dto/create-chat.dto'; //eslint-disable-line 14 | 15 | class ChatResponseBody { 16 | @ApiProperty({ required: true, example: '605e3fd9acc33583fb389aec' }) 17 | id: string; 18 | 19 | @ApiProperty({ required: true, example: 'John' }) 20 | sender: string; 21 | 22 | @ApiProperty({ required: true, example: 'Johny' }) 23 | original_sender: string; 24 | 25 | @ApiProperty({ required: true, example: 'How are you!' }) 26 | message: string; 27 | } 28 | 29 | @ApiTags('Chat') 30 | @Controller('chat') 31 | export class ChatController { 32 | constructor(private chatService: ChatService) {} 33 | 34 | // add a Chat 35 | @Post() 36 | async addChat(@Body() CreateChatDTO: CreateChatDTO) { 37 | return await this.chatService.addChat(CreateChatDTO); 38 | } 39 | 40 | // Retrieve Chats list 41 | @ApiCreatedResponse({ type: [ChatResponseBody] }) 42 | @Get() 43 | async getAllChat() { 44 | return await this.chatService.getAllChat(); 45 | } 46 | 47 | // Fetch a particular Chat using ID 48 | @ApiCreatedResponse({ type: ChatResponseBody }) 49 | @Get('/:chatId') 50 | async getChat(@Param('chatId') chatId: string) { 51 | return await this.chatService.getChat(chatId); 52 | } 53 | 54 | @Put('/:chatId') 55 | async updateChat( 56 | @Param('chatId') chatId: string, 57 | @Body() createChatDTO: CreateChatDTO, 58 | ) { 59 | const chat = await this.chatService.updateChat(chatId, createChatDTO); 60 | 61 | if (!chat) throw new NotFoundException('Chat does not exist!'); 62 | 63 | return chat; 64 | } 65 | 66 | // Delete a Chat 67 | @Delete('/:chatId') 68 | async deleteChat(@Param('chatId') chatId: string) { 69 | const chat = await this.chatService.deleteChat(chatId); 70 | if (!chat) throw new NotFoundException('Chat does not exist'); 71 | return chat; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/modules/doubt/docUtils/doubt.responsedoc.ts: -------------------------------------------------------------------------------- 1 | import { TagType } from '../doubt-tag.enum'; 2 | import { DoubtAnswer } from '../schema/doubtAnswer.schema'; 3 | 4 | export default class DoubtResponseBody { 5 | /** 6 | * DoubtId 7 | * @example 60ccf3037025096f45cb87bf 8 | */ 9 | id: string; 10 | 11 | /** 12 | * The name of the person who asked the doubt 13 | * @example '60ccf3037025096f45cb87bf' 14 | */ 15 | asked_by: string; 16 | 17 | /** 18 | * The Answer of the doubt 19 | * @example [] 20 | */ 21 | answers: DoubtAnswer[]; 22 | 23 | /** 24 | * Whether the metor's assistance is needed or not for the doubt 25 | * @example true 26 | */ 27 | request_mentor: boolean; 28 | 29 | /** 30 | * Whether the doubt was resolved or not 31 | * @example true 32 | */ 33 | is_resolved: boolean; 34 | 35 | /** 36 | * The question/doubt 37 | * @example "Why do we use memoization over tabulation ?" 38 | */ 39 | question: string; 40 | 41 | /** 42 | * The tags associated with the doubt 43 | * @example ["Web development"] 44 | */ 45 | tags: TagType[]; 46 | 47 | /** 48 | * The photo url of the person who asked the doubt 49 | * @example "https://google.com/john" 50 | */ 51 | photoUrl: string; 52 | 53 | /** 54 | * The person who asked the doubt 55 | * @example "John Doe" 56 | */ 57 | askedBy_name: string; 58 | 59 | /** 60 | * The doubt body 61 | * @example "the doubt description is ...." 62 | */ 63 | doubtBody: string; 64 | } 65 | 66 | export class DoubtAnswerResponseBody { 67 | /** 68 | * DoubtAnswerId 69 | * @example '60ccf3037025096f45cb87bf' 70 | */ 71 | id: string; 72 | 73 | /** 74 | * The name of the person who answered the doubt 75 | * @example '60ccf3037025096f45cb87bf' 76 | */ 77 | answered_by: string; 78 | 79 | /** 80 | * The asnwer to the doubt 81 | * @example "We use this to conserve time by applying alogorithm of lesser time complexity" 82 | */ 83 | answer: string; 84 | 85 | /** 86 | * The photo url of the person who answered the doubt 87 | * @example "https://google.com/john" 88 | */ 89 | photoUrl: string; 90 | 91 | /** 92 | * The person who asked the doubt 93 | * @example "John Doe" 94 | */ 95 | answeredBy_name: string; 96 | } 97 | -------------------------------------------------------------------------------- /src/modules/user/docUtils/apidoc.ts: -------------------------------------------------------------------------------- 1 | import UserResponseBody, { 2 | EnrolledCourseResponseBody, 3 | getAllGamifiedResponseBody, 4 | } from './user.responsedoc'; 5 | import { ApiResponseOptions } from '@nestjs/swagger'; 6 | 7 | const addUser: ApiResponseOptions = { 8 | description: 'Create user with provided values', 9 | type: UserResponseBody, 10 | }; 11 | 12 | const getAllUser: ApiResponseOptions = { 13 | description: 'Get all the users available in the database', 14 | type: [UserResponseBody], 15 | }; 16 | 17 | const getEnrolledCourses: ApiResponseOptions = { 18 | description: 19 | 'Get all the enrolled courses, returns all the ids of enrolled courses', 20 | type: [String], 21 | }; 22 | 23 | const getEnrolledCoursesById: ApiResponseOptions = { 24 | description: 'Get details of enrolled course', 25 | type: EnrolledCourseResponseBody, 26 | }; 27 | 28 | const addEnrolledCourses: ApiResponseOptions = { 29 | description: 'Add Enrolled courses for the user', 30 | type: [String], 31 | }; 32 | 33 | const updateEnrolledCourses: ApiResponseOptions = { 34 | description: 'Update Enrolled Courses', 35 | type: EnrolledCourseResponseBody, 36 | }; 37 | 38 | const getWishlist: ApiResponseOptions = { 39 | description: 'Get all the Wishlisted Courses', 40 | type: [String], 41 | }; 42 | 43 | const addWishlist: ApiResponseOptions = { 44 | description: 'Add Wishlist Course', 45 | type: UserResponseBody, 46 | }; 47 | 48 | const deleteWishList: ApiResponseOptions = { 49 | description: 'Delete Wishlist Course', 50 | type: UserResponseBody, 51 | }; 52 | 53 | const getCartList: ApiResponseOptions = { 54 | description: 'Get all the Courses in the cartList', 55 | type: [String], 56 | }; 57 | 58 | const addCartList: ApiResponseOptions = { 59 | description: 'Add Course to cartList', 60 | type: UserResponseBody, 61 | }; 62 | 63 | const deleteCartList: ApiResponseOptions = { 64 | description: 'Delete course from cartList', 65 | type: UserResponseBody, 66 | }; 67 | 68 | const getAllGamified: ApiResponseOptions = { 69 | description: 'Delete course from cartList', 70 | type: getAllGamifiedResponseBody, 71 | }; 72 | 73 | const responses = { 74 | addUser, 75 | getAllUser, 76 | getEnrolledCourses, 77 | getEnrolledCoursesById, 78 | addEnrolledCourses, 79 | updateEnrolledCourses, 80 | getWishlist, 81 | addWishlist, 82 | deleteWishList, 83 | addCartList, 84 | deleteCartList, 85 | getCartList, 86 | getAllGamified, 87 | }; 88 | 89 | export default responses; 90 | -------------------------------------------------------------------------------- /src/modules/assignment/assignment.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Post, 4 | Body, 5 | Put, 6 | Delete, 7 | Param, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { AssignmentService } from './assignment.service'; //eslint-disable-line 11 | import { CreateAssignmentDTO } from './dto/create-assignment.dto'; //eslint-disable-line 12 | import { 13 | ApiCreatedResponse, 14 | ApiOkResponse, 15 | ApiOperation, 16 | ApiParam, 17 | ApiTags, 18 | } from '@nestjs/swagger'; 19 | import { UpdateAssignmentDTO } from './dto/update-assignment.dto'; 20 | import { Schema } from 'mongoose'; 21 | import { courseId } from './docUtils/assignment.paramdocs'; 22 | import responsedoc from './docUtils/apidoc'; 23 | import { RolesGuard } from '../../middleware/roles.guard'; 24 | import { Roles } from '../../middleware/role.decorator'; 25 | import { Role } from '../../roles/role.enum'; 26 | 27 | @ApiTags('Assignment') 28 | @Controller('assignment') 29 | @UseGuards(RolesGuard) 30 | export class AssignmentController { 31 | constructor(private assignmentService: AssignmentService) {} 32 | 33 | @Post('/:courseId') 34 | @Roles(Role.ADMIN) 35 | @ApiCreatedResponse(responsedoc.addAssignment) 36 | @ApiParam(courseId) 37 | @ApiOperation({ summary: 'add an Assignment' }) 38 | async addAssignment( 39 | @Param('courseId') courseId: Schema.Types.ObjectId, 40 | @Body() CreateAssignmentDTO: CreateAssignmentDTO, 41 | ) { 42 | return await this.assignmentService.addAssignment( 43 | courseId, 44 | CreateAssignmentDTO, 45 | ); 46 | } 47 | 48 | @Put('/:courseId/:assignmentId') 49 | @Roles(Role.ADMIN) 50 | @ApiParam(courseId) 51 | @ApiOperation({ summary: 'update an Assignment' }) 52 | @ApiOkResponse(responsedoc.updateAssignment) 53 | async updateAssignment( 54 | @Param('courseId') courseId: Schema.Types.ObjectId, 55 | @Param('assignmentId') assignmentId: Schema.Types.ObjectId, 56 | @Body() updateAssignmentDTO: UpdateAssignmentDTO, 57 | ) { 58 | return await this.assignmentService.updateAssignment( 59 | courseId, 60 | assignmentId, 61 | updateAssignmentDTO, 62 | ); 63 | } 64 | 65 | @Delete('/:courseId/:assignmentId') 66 | @Roles(Role.ADMIN) 67 | @ApiParam(courseId) 68 | @ApiOperation({ summary: 'delete an Assignment' }) 69 | @ApiOkResponse(responsedoc.deleteAssignment) 70 | async deleteAssignment( 71 | @Param('courseId') courseId: Schema.Types.ObjectId, 72 | @Param('assignmentId') assignmentId: Schema.Types.ObjectId, 73 | ) { 74 | return await this.assignmentService.deleteAssignment( 75 | courseId, 76 | assignmentId, 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; 2 | import { ConfigModule } from 'nestjs-config'; 3 | import { AppController } from './app.controller'; 4 | import { AppService } from './app.service'; 5 | import { MongooseModule } from '@nestjs/mongoose'; 6 | import { UserModule } from './modules/user/user.module'; 7 | import { AssignmentModule } from './modules/assignment/assignment.module'; 8 | import { ChatModule } from './modules/chat/chat.module'; 9 | import * as path from 'path'; 10 | import { CourseModule } from './modules/course/course.module'; 11 | import { DoubtModule } from './modules/doubt/doubt.module'; 12 | import { AnnouncementModule } from './modules/announcements/announcement.module'; 13 | import { MentorModule } from './modules/mentor/mentor.module'; 14 | import { APP_GUARD } from '@nestjs/core'; 15 | import * as dotenv from 'dotenv'; 16 | import { PreauthMiddleware } from './middleware/preAuth.middleware'; 17 | import { RolesGuard } from './middleware/roles.guard'; 18 | import UserSchema from './modules/user/schema/user.schema'; 19 | dotenv.config({ path: path.resolve(__dirname, '..', '.env') }); 20 | 21 | @Module({ 22 | imports: [ 23 | ConfigModule.load( 24 | path.resolve(__dirname, 'config', '**', '!(*.d).{ts,js}'), 25 | ), 26 | AssignmentModule, 27 | MongooseModule.forRoot(process.env.MONGOURL), 28 | UserModule, 29 | CourseModule, 30 | ChatModule, 31 | DoubtModule, 32 | AnnouncementModule, 33 | MentorModule, 34 | MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]), 35 | ], 36 | controllers: [AppController], 37 | providers: [ 38 | AppService, 39 | { 40 | provide: APP_GUARD, 41 | useClass: RolesGuard, 42 | }, 43 | ], 44 | }) 45 | export class AppModule { 46 | configure(consumer: MiddlewareConsumer) { 47 | consumer 48 | .apply(PreauthMiddleware) 49 | .exclude( 50 | { 51 | path: 'course/(.*)', 52 | method: RequestMethod.GET, 53 | }, 54 | { 55 | path: 'user/gamification', 56 | method: RequestMethod.GET, 57 | }, 58 | { 59 | path: 'Doubt/(.*)', 60 | method: RequestMethod.GET, 61 | }, 62 | { 63 | path: 'Doubt', 64 | method: RequestMethod.GET, 65 | }, 66 | { 67 | path: 'Announcement/(.*)', 68 | method: RequestMethod.GET, 69 | }, 70 | { 71 | path: 'Announcement', 72 | method: RequestMethod.GET, 73 | }, 74 | { 75 | path: 'Mentor/(.*)', 76 | method: RequestMethod.GET, 77 | }, 78 | { 79 | path: 'Mentor', 80 | method: RequestMethod.GET, 81 | }, 82 | ) 83 | .forRoutes({ 84 | path: '*', 85 | method: RequestMethod.ALL, 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/middleware/preAuth.middleware.ts: -------------------------------------------------------------------------------- 1 | import { ConflictException, Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { UserDocument as User } from '../modules/user/schema/user.schema'; 4 | import { NextFunction, Request, Response } from 'express'; 5 | import admin from '../main'; 6 | import { Model } from 'mongoose'; 7 | import { Role } from '../roles/role.enum'; 8 | 9 | @Injectable() 10 | export class PreauthMiddleware implements NestMiddleware { 11 | constructor(@InjectModel('User') private readonly userModel: Model) {} 12 | use(req: Request, res: Response, next: NextFunction) { 13 | const token = req.headers.authorization; 14 | if (token && token != null && token != '' && token.length > 0) { 15 | admin 16 | .auth() 17 | .verifyIdToken(token.replace('Bearer ', '')) 18 | .then(async (decodedToken) => { 19 | const { email, uid, picture } = decodedToken; 20 | 21 | const userExists = await this.userModel 22 | .findOne({ email: email }) 23 | .lean(); 24 | 25 | let role; 26 | 27 | if (userExists) { 28 | role = userExists.role || Role.STUDENT; 29 | await this.userModel.updateOne( 30 | { email: userExists.email }, 31 | { 32 | log_in_time: new Date().toString(), 33 | }, 34 | ); 35 | } else { 36 | const newUser = new this.userModel({ 37 | email, 38 | fid: uid, 39 | role: Role.STUDENT, 40 | photoUrl: picture, 41 | log_in_time: ' ', 42 | }); 43 | await newUser.save(); 44 | role = Role.STUDENT; 45 | console.log(role); 46 | } 47 | 48 | const user = { 49 | email: email, 50 | fId: uid, 51 | role: role, 52 | }; 53 | 54 | req['user'] = user; 55 | next(); 56 | }) 57 | .catch((error) => { 58 | if (error.errorInfo.code == 'auth/id-token-expired') { 59 | this.accessDenied(req.url, res, undefined, error.errorInfo); 60 | } else { 61 | this.accessDenied( 62 | req.url, 63 | res, 64 | error.errorInfo.message, 65 | error.errorInfo, 66 | ); 67 | } 68 | }); 69 | } else { 70 | throw new ConflictException('Access Denied as Token does not exist'); 71 | } 72 | } 73 | 74 | private accessDenied( 75 | url: string, 76 | res: Response, 77 | message: string, 78 | others?: any, 79 | ) { 80 | res.status(403).json({ 81 | statusCode: 403, 82 | timestamp: new Date().toISOString(), 83 | path: url, 84 | message: message ? message : 'Access Denied', 85 | ...others, 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/modules/announcements/announcement.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Put, 7 | Delete, 8 | Param, 9 | UseGuards, 10 | } from '@nestjs/common'; 11 | import { AnnouncementService } from './announcement.service'; //eslint-disable-line 12 | import { CreateAnnouncementDTO } from './dto/create-announcement.dto'; //eslint-disable-line 13 | import { 14 | ApiCreatedResponse, 15 | ApiOkResponse, 16 | ApiOperation, 17 | ApiParam, 18 | ApiTags, 19 | } from '@nestjs/swagger'; 20 | import { UpdateAnnouncementDTO } from './dto/update-announcement.dto'; 21 | import responsedoc from './docUtils/apidoc'; 22 | import { announcementId } from './docUtils/announcement.paramdocs'; 23 | import { RolesGuard } from '../../middleware/roles.guard'; 24 | import { Roles } from '../../middleware/role.decorator'; 25 | import { Role } from '../../roles/role.enum'; 26 | 27 | @ApiTags('Announcement') 28 | @Controller('announcement') 29 | @UseGuards(RolesGuard) 30 | export class AnnouncementController { 31 | constructor(private announcementService: AnnouncementService) {} 32 | 33 | @Post() 34 | @Roles(Role.ADMIN) 35 | @ApiOperation({ summary: 'add an Announcement' }) 36 | @ApiCreatedResponse(responsedoc.addAnnouncement) 37 | async addAnnouncement(@Body() CreateAnnouncementDTO: CreateAnnouncementDTO) { 38 | return await this.announcementService.addAnnouncement( 39 | CreateAnnouncementDTO, 40 | ); 41 | } 42 | 43 | @Get() 44 | @ApiOperation({ summary: 'Retrieve Announcements list' }) 45 | @ApiOkResponse(responsedoc.getAllAnnouncement) 46 | async getAllAnnouncement() { 47 | return await this.announcementService.getAllAnnouncement(); 48 | } 49 | 50 | @Get('/:announcementId') 51 | @ApiParam(announcementId) 52 | @ApiOperation({ 53 | summary: 'Fetch a particular Announcement using ID', 54 | }) 55 | @ApiOkResponse(responsedoc.getAnnouncement) 56 | async getAnnouncement(@Param('announcementId') announcementId: string) { 57 | return await this.announcementService.getAnnouncement(announcementId); 58 | } 59 | 60 | @Put('/:announcementId') 61 | @Roles(Role.ADMIN) 62 | @ApiParam(announcementId) 63 | @ApiOperation({ summary: 'update Announcement by Id' }) 64 | @ApiOkResponse(responsedoc.updateAnnouncement) 65 | async updateAnnouncement( 66 | @Param('announcementId') announcementId: string, 67 | @Body() updateAnnouncementDTO: UpdateAnnouncementDTO, 68 | ) { 69 | return await this.announcementService.updateAnnouncement( 70 | announcementId, 71 | updateAnnouncementDTO, 72 | ); 73 | } 74 | 75 | @Delete('/:announcementId') 76 | @Roles(Role.ADMIN) 77 | @ApiParam(announcementId) 78 | @ApiOperation({ summary: 'Delete an Announcement by Id' }) 79 | @ApiOkResponse(responsedoc.deleteAnnouncement) 80 | async deleteAnnouncement(@Param('announcementId') announcementId: string) { 81 | return await this.announcementService.deleteAnnouncement(announcementId); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/modules/announcements/announcement.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | InternalServerErrorException, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | import { Model } from 'mongoose'; 7 | import { InjectModel } from '@nestjs/mongoose'; 8 | import { AnnouncementDocument as Announcement } from './schema/announcement.schema'; 9 | import { CreateAnnouncementDTO } from './dto/create-announcement.dto'; 10 | import { UpdateAnnouncementDTO } from './dto/update-announcement.dto'; 11 | 12 | @Injectable() 13 | export class AnnouncementService { 14 | constructor( 15 | @InjectModel('Announcement') 16 | private readonly AnnouncementModel: Model, 17 | ) {} 18 | 19 | // fetch all Announcements 20 | async getAllAnnouncement(): Promise { 21 | try { 22 | const Announcements = await this.AnnouncementModel.find().exec(); 23 | return Announcements; 24 | } catch (e) { 25 | throw new InternalServerErrorException(e); 26 | } 27 | } 28 | 29 | // Get a single Announcement 30 | async getAnnouncement(announcementId): Promise { 31 | try { 32 | const Announcement = await this.AnnouncementModel.findById( 33 | announcementId, 34 | ); 35 | if (Announcement) { 36 | return Announcement; 37 | } 38 | } catch (e) { 39 | throw new InternalServerErrorException(e); 40 | } 41 | 42 | throw new NotFoundException('Announcement not found'); 43 | } 44 | 45 | // post a single Announcement 46 | async addAnnouncement( 47 | CreateAnnouncementDTO: CreateAnnouncementDTO, 48 | ): Promise { 49 | try { 50 | const newAnnouncement = await new this.AnnouncementModel( 51 | CreateAnnouncementDTO, 52 | ); 53 | await newAnnouncement.save(); 54 | return newAnnouncement; 55 | } catch (e) { 56 | throw new InternalServerErrorException(e); 57 | } 58 | } 59 | 60 | // Edit Announcement details 61 | async updateAnnouncement( 62 | announcementId, 63 | updateAnnouncementDTO: UpdateAnnouncementDTO, 64 | ): Promise { 65 | try { 66 | const updatedAnnouncement = 67 | await this.AnnouncementModel.findByIdAndUpdate( 68 | announcementId, 69 | updateAnnouncementDTO, 70 | { new: true }, 71 | ); 72 | if (updatedAnnouncement) { 73 | return updatedAnnouncement; 74 | } 75 | } catch (e) { 76 | throw new InternalServerErrorException(e); 77 | } 78 | throw new NotFoundException('Announcement not found!'); 79 | } 80 | 81 | // Delete a Announcement 82 | async deleteAnnouncement(announcementId): Promise { 83 | try { 84 | const deletedAnnouncement = 85 | await this.AnnouncementModel.findByIdAndRemove(announcementId); 86 | if (deletedAnnouncement) { 87 | return deletedAnnouncement; 88 | } 89 | throw new NotFoundException('Announcement not found!'); 90 | } catch (e) { 91 | throw new InternalServerErrorException(e); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/modules/mentor/mentor.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | UseGuards, 10 | } from '@nestjs/common'; 11 | import { 12 | ApiCreatedResponse, 13 | ApiOkResponse, 14 | ApiOperation, 15 | ApiParam, 16 | ApiTags, 17 | } from '@nestjs/swagger'; 18 | import { CreateMentorDTO } from './dto/create-mentor.dto'; //eslint-disable-line 19 | import { UpdateMentorDTO } from './dto/update-mentor.dto'; //eslint-disable-line 20 | import { MentorService } from './mentor.service'; //eslint-disable-line 21 | import { Schema } from 'mongoose'; 22 | import responsedoc from './docUtils/apidoc'; 23 | import { mentorId } from './docUtils/mentor.paramdocs'; 24 | import { RolesGuard } from '../../middleware/roles.guard'; 25 | import { Roles } from '../../middleware/role.decorator'; 26 | import { Role } from '../../roles/role.enum'; 27 | 28 | @ApiTags('Mentor') 29 | @Controller('mentor') 30 | @UseGuards(RolesGuard) 31 | export class MentorController { 32 | constructor(private mentorService: MentorService) {} 33 | 34 | @Post() 35 | @Roles(Role.ADMIN) 36 | @ApiOperation({ summary: 'add a Mentor' }) 37 | @ApiCreatedResponse(responsedoc.addMentor) 38 | async addMentor(@Body() CreateMentorDTO: CreateMentorDTO) { 39 | return await this.mentorService.addMentor(CreateMentorDTO); 40 | } 41 | 42 | @Get() 43 | @ApiOperation({ summary: 'Retrieve Mentor list' }) 44 | @ApiOkResponse(responsedoc.getAllMentors) 45 | async getAllMentor() { 46 | return await this.mentorService.getAllMentor(); 47 | } 48 | 49 | @Get('get/:mentorId') 50 | @ApiParam(mentorId) 51 | @ApiOkResponse(responsedoc.getMentor) 52 | @ApiOperation({ summary: 'Fetch a particular Mentor using ID' }) 53 | async getMentor(@Param('mentorId') mentorId: Schema.Types.ObjectId) { 54 | return await this.mentorService.findMentorById(mentorId); 55 | } 56 | 57 | @Put('/update/:mentorId') 58 | @Roles(Role.ADMIN) 59 | @ApiParam(mentorId) 60 | @ApiOperation({ summary: 'update info of a mentor using id' }) 61 | @ApiOkResponse(responsedoc.updateMentor) 62 | async updateMentor( 63 | @Param('mentorId') mentorId: Schema.Types.ObjectId, 64 | @Body() UpdateMentorDTO: UpdateMentorDTO, 65 | ) { 66 | return await this.mentorService.updateMentor(mentorId, UpdateMentorDTO); 67 | } 68 | 69 | @Delete('/delete/:mentorId') 70 | @Roles(Role.ADMIN) 71 | @ApiParam(mentorId) 72 | @ApiOperation({ summary: 'Delete a Mentor' }) 73 | @ApiOkResponse(responsedoc.deleteMentor) 74 | async deleteMentor(@Param('mentorId') mentorId: Schema.Types.ObjectId) { 75 | return await this.mentorService.deleteMentor(mentorId); 76 | } 77 | 78 | @Put('/assign/:mentorId') 79 | @Roles(Role.ADMIN) 80 | @ApiParam(mentorId) 81 | @ApiOperation({ summary: 'assign course to mentor' }) 82 | @ApiOkResponse(responsedoc.updateMentor) 83 | async assignCourseToMentor( 84 | @Param('mentorId') mentorId: Schema.Types.ObjectId, 85 | @Body() courseId: Schema.Types.ObjectId, 86 | ) { 87 | return await this.mentorService.assignCourseToMentor(mentorId, courseId); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/modules/course/docUtils/apidoc.ts: -------------------------------------------------------------------------------- 1 | import CourseResponseBody, { 2 | ReviewResponseBody, 3 | ScheduleResponseBody, 4 | LectureResponseBody, 5 | } from './course.responsedoc'; 6 | import { ApiResponseOptions } from '@nestjs/swagger'; 7 | 8 | const getAllCourses: ApiResponseOptions = { 9 | description: 'Get all the courses available in the database', 10 | type: [CourseResponseBody], 11 | }; 12 | 13 | const getAllQueryCourses: ApiResponseOptions = { 14 | description: 15 | 'Get all the courses whose name feild matches the query parameter', 16 | type: [CourseResponseBody], 17 | }; 18 | 19 | const getSelectedCourses: ApiResponseOptions = { 20 | description: 'Get course by id from the database', 21 | type: CourseResponseBody, 22 | }; 23 | 24 | const addCourse: ApiResponseOptions = { 25 | description: 'Add a course', 26 | type: CourseResponseBody, 27 | }; 28 | 29 | const updateCourse: ApiResponseOptions = { 30 | description: 'Update a course', 31 | type: CourseResponseBody, 32 | }; 33 | 34 | const deleteCourse: ApiResponseOptions = { 35 | description: 'Delete a course', 36 | type: CourseResponseBody, 37 | }; 38 | 39 | const addScheduleCourse: ApiResponseOptions = { 40 | description: 'Add a schedule for the course', 41 | type: ScheduleResponseBody, 42 | }; 43 | 44 | const updateScheduleCourse: ApiResponseOptions = { 45 | description: 'update a schedule for the course', 46 | type: ScheduleResponseBody, 47 | }; 48 | 49 | const deleteScheduleCourse: ApiResponseOptions = { 50 | description: 'Delete schedule for the course', 51 | type: ScheduleResponseBody, 52 | }; 53 | 54 | const addReview: ApiResponseOptions = { 55 | description: 'Add a review for the course', 56 | type: ReviewResponseBody, 57 | }; 58 | 59 | const updateReview: ApiResponseOptions = { 60 | description: 'update a review for the course', 61 | type: ReviewResponseBody, 62 | }; 63 | const deleteReview: ApiResponseOptions = { 64 | description: 'Delete review for the course', 65 | type: ReviewResponseBody, 66 | }; 67 | 68 | const addLecture: ApiResponseOptions = { 69 | description: 'Add a Lecture for the course', 70 | type: LectureResponseBody, 71 | }; 72 | 73 | const updateLecture: ApiResponseOptions = { 74 | description: 'update a Lecture for the course', 75 | type: LectureResponseBody, 76 | }; 77 | const deleteLecture: ApiResponseOptions = { 78 | description: 'Delete Lecture for the course', 79 | type: LectureResponseBody, 80 | }; 81 | 82 | const addMentorToCourse: ApiResponseOptions = { 83 | description: 'Add mentor to the course', 84 | type: CourseResponseBody, 85 | }; 86 | 87 | const responses = { 88 | getAllCourses, 89 | getSelectedCourses, 90 | addCourse, 91 | updateCourse, 92 | deleteCourse, 93 | addScheduleCourse, 94 | updateScheduleCourse, 95 | deleteScheduleCourse, 96 | addReview, 97 | updateReview, 98 | deleteReview, 99 | getAllQueryCourses, 100 | addLecture, 101 | updateLecture, 102 | deleteLecture, 103 | addMentorToCourse, 104 | }; 105 | 106 | export default responses; 107 | -------------------------------------------------------------------------------- /src/modules/course/schema/course.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, Schema as SchemaTypes } from 'mongoose'; 3 | import { Schedule } from './schedule.schema'; 4 | import { TagType } from '../course-tag.enum'; 5 | import { Review } from './review.schema'; 6 | import { courseLevelType } from '../courseLevel.enum'; 7 | import { Doubt } from '../../doubt/schema/doubt.schema'; 8 | import { Assignment } from '../../assignment/schema/assignment.schema'; 9 | import { Mentor } from 'modules/mentor/schema/mentor.schema'; 10 | 11 | export type CourseDocument = Course & Document; 12 | 13 | @Schema({ timestamps: true }) 14 | export class Course { 15 | @Prop({ required: true }) 16 | name: string; 17 | 18 | @Prop({ required: true }) 19 | originalPrice: number; 20 | 21 | @Prop({ default: Date.now }) 22 | start_date: Date; 23 | 24 | @Prop() 25 | end_date: Date; 26 | 27 | @Prop({}) 28 | duration: string; 29 | 30 | @Prop({ default: false }) 31 | active: boolean; 32 | 33 | @Prop() 34 | couponCode: string; 35 | 36 | @Prop({ default: 0 }) 37 | student_num: number; 38 | 39 | @Prop({ type: [{ type: SchemaTypes.Types.ObjectId, ref: 'Mentor' }] }) 40 | mentor: Mentor[]; 41 | 42 | @Prop({}) 43 | video_num: number; 44 | 45 | @Prop({ default: 0 }) 46 | no_of_enrollments: number; 47 | 48 | @Prop() 49 | sharable_link: string; 50 | 51 | @Prop({ type: [{ type: SchemaTypes.Types.ObjectId, ref: 'Schedule' }] }) 52 | schedule: Schedule[]; 53 | 54 | @Prop({ required: true }) 55 | tags: TagType[]; 56 | 57 | @Prop({ required: true }) 58 | courseDetails: string; 59 | 60 | @Prop({ default: 'Training', required: true }) 61 | courseLevel: courseLevelType; 62 | 63 | @Prop({ required: true }) 64 | courseThumbnail: string; 65 | 66 | @Prop({}) 67 | courseTrailerUrl: string; 68 | 69 | @Prop({ type: [{ type: SchemaTypes.Types.ObjectId, ref: 'Review' }] }) 70 | reviews: Review[]; 71 | 72 | @Prop({ type: [{ type: SchemaTypes.Types.ObjectId, ref: 'Doubt' }] }) 73 | doubts: Doubt[]; 74 | 75 | @Prop({ type: [{ type: SchemaTypes.Types.ObjectId, ref: 'Assignment' }] }) 76 | assignments: Assignment[]; 77 | 78 | @Prop({ required: true }) 79 | crossPrice: number; 80 | 81 | @Prop() 82 | courseShortDescription: string; 83 | 84 | @Prop() 85 | courseLongDescription: string; 86 | 87 | @Prop() 88 | rating: number; 89 | 90 | @Prop() 91 | prerequisites: string[]; 92 | 93 | @Prop() 94 | skills: string[]; 95 | 96 | @Prop() 97 | whatYouWillLearn: string[]; 98 | 99 | @Prop() 100 | certificateUrl: string; 101 | 102 | @Prop({ default: false }) 103 | isUpcoming: boolean; 104 | } 105 | 106 | export const CourseSchema = SchemaFactory.createForClass(Course); 107 | 108 | CourseSchema.methods.toJSON = function () { 109 | const courseObject = this.toObject(); 110 | courseObject.id = courseObject._id; 111 | delete courseObject.__v; 112 | delete courseObject['student_num']; 113 | delete courseObject['updatedAt']; 114 | delete courseObject['createdAt']; 115 | delete courseObject['no_of_enrollments']; 116 | delete courseObject._id; 117 | return courseObject; 118 | }; 119 | -------------------------------------------------------------------------------- /src/modules/user/docUtils/user.responsedoc.ts: -------------------------------------------------------------------------------- 1 | export default class UserResponseBody { 2 | /** 3 | * UserId 4 | * @example 60ccf3037025096f45cb87bf 5 | */ 6 | id: string; 7 | 8 | /** 9 | * First name of the user 10 | * @example 'John' 11 | */ 12 | first_name: string; 13 | 14 | /** 15 | * Last name of the user 16 | * @example 'Doe' 17 | */ 18 | last_name: string; 19 | 20 | /** 21 | * Email of the user 22 | * @example 'john@example.com' 23 | */ 24 | email: string; 25 | 26 | /** 27 | * Phone number of the user 28 | * @example '9000500000' 29 | */ 30 | phone: string; 31 | 32 | /** 33 | * Address of the user 34 | * @example 'Block C Amsterdam' 35 | */ 36 | address: string; 37 | 38 | /** 39 | * Description of the user 40 | * @example 'Aspiring Software Developer' 41 | */ 42 | description: string; 43 | 44 | /** 45 | * Score of the user 46 | * @example 100 47 | */ 48 | score: number; 49 | 50 | /** 51 | * The field to show the role 52 | * @example "student" 53 | * @default "student" 54 | */ 55 | role: string; 56 | 57 | /** 58 | * The photo url 59 | * @example 'https://g.gle/mypic.jpeg' 60 | */ 61 | photoUrl: string; 62 | 63 | /** 64 | * The cover photo url 65 | * @example 'https://g.gle/mycover.jpeg' 66 | */ 67 | coverPhotoUrl?: string; 68 | 69 | /** 70 | * The Wishlisted Courses 71 | * @example ["60ccf3037025096f45cb87ba", "60ccf3037025096f45cb87bq"] 72 | */ 73 | wishlist: string[]; 74 | 75 | /** 76 | * The Courses added to Cart 77 | * @example ["60ccf3037025096f45cb87ba", "60ccf3037025096f45cb87bq"] 78 | */ 79 | cartList: string[]; 80 | 81 | /** 82 | * The Firebase Id 83 | * @example "60ccf3037025096f45cb87ba" 84 | */ 85 | fId: string; 86 | 87 | /** 88 | * Login Time 89 | * @example "Wed Aug 18 2021 00:13:13 GMT+0530 (India Standard Time)" 90 | */ 91 | log_in_time: string; 92 | } 93 | 94 | interface video { 95 | num: number; 96 | timestamp?: Date; 97 | } 98 | 99 | export class EnrolledCourseResponseBody { 100 | /** 101 | * Student Id 102 | * @example "6079cf782f9a2181bc7aadbf" 103 | */ 104 | studentId: string; 105 | 106 | /** 107 | * videos watched by the student 108 | * @example [false, false] 109 | */ 110 | videosWatched: boolean[]; 111 | 112 | /** 113 | * The assignments done by the student 114 | * @example [false, false] 115 | */ 116 | assignmentsDone: boolean[]; 117 | 118 | /** 119 | * The current video where student left 120 | * @example { num: 1, timestamp: "12:PM" } 121 | */ 122 | currentVideo: video[]; 123 | 124 | /** 125 | * The doubts of the student 126 | * @example ['problem in BFS', 'unable to understand Dynamic Programming'] 127 | */ 128 | doubts: string[]; 129 | 130 | /** 131 | * Course Id 132 | * @example "60ccf06ad682336931f0a61b" 133 | */ 134 | courseId: string; 135 | } 136 | 137 | export class getAllGamifiedResponseBody { 138 | /** 139 | * First name of the user 140 | * @example 'John' 141 | */ 142 | first_name: string; 143 | 144 | /** 145 | * Last name of the user 146 | * @example 'Doe' 147 | */ 148 | last_name: string; 149 | 150 | /** 151 | * Score of the user 152 | * @example 100 153 | */ 154 | score: number; 155 | 156 | /** 157 | * The photo url 158 | * @example 'https://cdn.pixabay.com/photo/2015/03/04/22/35/head-659652_1280.png' 159 | */ 160 | photoUrl: string; 161 | } 162 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at team@codeforcause.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "edu-server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "Code for Cause", 6 | "email": "team@codeforcause.org", 7 | "private": true, 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "rimraf dist && tsc -p tsconfig.build.json", 11 | "serve": "npm run build && firebase serve --only functions", 12 | "shell": "npm run build && firebase functions:shell", 13 | "deploy": "firebase deploy --only functions", 14 | "logs": "firebase functions:log", 15 | "format": "prettier --write \"src/**/*.ts\"", 16 | "lint": "eslint \"{src, test}/**\"", 17 | "lint:fix": "eslint \"{src, test}/**\" --fix", 18 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 19 | "start:func": "npm run shell", 20 | "start:dev": "NODE_ENV=dev tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"", 21 | "start:debug": "tsc-watch -p tsconfig.build.json --onSuccess \"node --inspect-brk dist/main.js\"", 22 | "start:prod": "NODE_ENV=production node dist/main.js", 23 | "test": "jest", 24 | "test:watch": "jest --watch", 25 | "test:cov": "jest --coverage", 26 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 27 | "test:e2e": "jest --config ./test/jest-e2e.json" 28 | }, 29 | "dependencies": { 30 | "@golevelup/nestjs-testing": "^0.1.2", 31 | "@nestjs/common": "^7.6.17", 32 | "@nestjs/core": "^7.6.17", 33 | "@nestjs/mongoose": "^7.2.4", 34 | "@nestjs/passport": "^7.1.5", 35 | "@nestjs/platform-express": "^7.6.15", 36 | "@nestjs/swagger": "^4.7.16", 37 | "@tfarras/nestjs-firebase-admin": "^2.0.1", 38 | "@tfarras/nestjs-firebase-auth": "^2.0.0", 39 | "class-transformer": "^0.4.0", 40 | "class-validator": "^0.13.1", 41 | "dotenv": "^9.0.2", 42 | "express": "^4.17.1", 43 | "firebase-admin": "^9.6.0", 44 | "firebase-functions": "^3.3.0", 45 | "helmet": "^4.6.0", 46 | "mongoose": "^5.12.1", 47 | "nestjs-config": "^1.4.7", 48 | "passport": "^0.4.1", 49 | "passport-jwt": "^4.0.0", 50 | "reflect-metadata": "^0.1.13", 51 | "rimraf": "^3.0.2", 52 | "rxjs": "^6.5.4", 53 | "swagger-ui-express": "^4.1.6" 54 | }, 55 | "devDependencies": { 56 | "@nestjs/cli": "^7.0.0", 57 | "@nestjs/schematics": "^7.0.0", 58 | "@nestjs/testing": "^7.0.0", 59 | "@types/express": "^4.17.3", 60 | "@types/jest": "25.2.3", 61 | "@types/node": "^13.9.1", 62 | "@types/passport-jwt": "^3.0.5", 63 | "@types/supertest": "^2.0.8", 64 | "@typescript-eslint/eslint-plugin": "^4.15.2", 65 | "@typescript-eslint/parser": "^4.15.2", 66 | "eslint": "^7.28.0", 67 | "eslint-config-prettier": "^8.1.0", 68 | "eslint-plugin-prettier": "^3.3.1", 69 | "firebase-functions-test": "^0.1.6", 70 | "jest": "26.0.1", 71 | "prettier": "^2.2.1", 72 | "supertest": "^4.0.2", 73 | "ts-jest": "26.1.0", 74 | "ts-loader": "^6.2.1", 75 | "ts-node": "^8.6.2", 76 | "tsc-watch": "^4.2.9", 77 | "tsconfig-paths": "^3.9.0", 78 | "typescript": "^3.7.4" 79 | }, 80 | "jest": { 81 | "moduleFileExtensions": [ 82 | "js", 83 | "json", 84 | "ts" 85 | ], 86 | "rootDir": "src", 87 | "testRegex": ".spec.ts$", 88 | "transform": { 89 | "^.+\\.(t|j)s$": "ts-jest" 90 | }, 91 | "coverageDirectory": "../coverage", 92 | "testEnvironment": "node" 93 | }, 94 | "engines": { 95 | "node": "12" 96 | }, 97 | "main": "dist/main.js", 98 | "testEnvironment": "node" 99 | } 100 | -------------------------------------------------------------------------------- /src/modules/assignment/assignment.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | InternalServerErrorException, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | import { Model } from 'mongoose'; 7 | import { InjectModel } from '@nestjs/mongoose'; 8 | import { AssignmentDocument as Assignment } from './schema/assignment.schema'; 9 | import { CreateAssignmentDTO } from './dto/create-assignment.dto'; 10 | import { UpdateAssignmentDTO } from './dto/update-assignment.dto'; 11 | import { Course } from 'modules/course/schema/course.schema'; 12 | import { Schema } from 'mongoose'; 13 | 14 | @Injectable() 15 | export class AssignmentService { 16 | constructor( 17 | @InjectModel('Assignment') 18 | private readonly AssignmentModel: Model, 19 | @InjectModel('Course') 20 | private readonly CourseModel: Model, 21 | ) {} 22 | 23 | // post a single Assignment 24 | async addAssignment( 25 | courseId: Schema.Types.ObjectId, 26 | createAssignmentDTO: CreateAssignmentDTO, 27 | ): Promise { 28 | try { 29 | const course = await this.CourseModel.findById(courseId); 30 | if (course) { 31 | const newAssignment = await new this.AssignmentModel( 32 | createAssignmentDTO, 33 | ); 34 | await newAssignment.save(); 35 | course.assignments.push(newAssignment); 36 | await course.save(); 37 | return newAssignment; 38 | } else { 39 | throw new NotFoundException( 40 | 'The course id is invalid or the course no longer exists', 41 | ); 42 | } 43 | } catch (e) { 44 | throw new InternalServerErrorException(e); 45 | } 46 | } 47 | 48 | // Edit Assignment details 49 | async updateAssignment( 50 | courseId: Schema.Types.ObjectId, 51 | assignmentID: Schema.Types.ObjectId, 52 | updateAssignmentDTO: UpdateAssignmentDTO, 53 | ): Promise { 54 | try { 55 | const course = await this.CourseModel.findById(courseId); 56 | if (course) { 57 | let updatedAssignment = null; 58 | updatedAssignment = await this.AssignmentModel.findByIdAndUpdate( 59 | assignmentID, 60 | updateAssignmentDTO, 61 | { new: true }, 62 | ); 63 | if (updatedAssignment) { 64 | return updatedAssignment; 65 | } else { 66 | throw new NotFoundException( 67 | 'The assignment id is invalid or the assignment no longer exists', 68 | ); 69 | } 70 | } else { 71 | throw new NotFoundException( 72 | 'The course id is invalid or the course no longer exists', 73 | ); 74 | } 75 | } catch (e) { 76 | throw new InternalServerErrorException(e); 77 | } 78 | } 79 | 80 | // Delete a Assignment 81 | async deleteAssignment( 82 | courseId: Schema.Types.ObjectId, 83 | assignmentID: Schema.Types.ObjectId, 84 | ): Promise { 85 | try { 86 | const course = await this.CourseModel.findById(courseId); 87 | if (course) { 88 | let deletedAssignment = null; 89 | deletedAssignment = await this.AssignmentModel.findByIdAndRemove( 90 | assignmentID, 91 | ); 92 | if (deletedAssignment) { 93 | return deletedAssignment; 94 | } else { 95 | throw new NotFoundException( 96 | 'The assignment id is invalid or the assignment no longer exists', 97 | ); 98 | } 99 | } else { 100 | throw new NotFoundException( 101 | 'The course id is invalid or the course no longer exists', 102 | ); 103 | } 104 | } catch (e) { 105 | throw new InternalServerErrorException(e); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/modules/mentor/mentor.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConflictException, 3 | Injectable, 4 | InternalServerErrorException, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { Model, Schema } from 'mongoose'; 8 | import { InjectModel } from '@nestjs/mongoose'; 9 | import { MentorDocument as Mentor } from './schema/mentor.schema'; 10 | import { CourseDocument as Course } from '../../modules/course/schema/course.schema'; 11 | import { CreateMentorDTO } from './dto/create-mentor.dto'; 12 | import { UpdateMentorDTO } from './dto/update-mentor.dto'; 13 | 14 | @Injectable() 15 | export class MentorService { 16 | constructor( 17 | @InjectModel('Mentor') private readonly mentorModel: Model, 18 | @InjectModel('Course') private readonly courseModel: Model, 19 | ) {} 20 | 21 | // fetch all Mentors 22 | async getAllMentor(): Promise { 23 | try { 24 | const mentors = await this.mentorModel.find().exec(); 25 | return mentors; 26 | } catch (e) { 27 | throw new InternalServerErrorException(e); 28 | } 29 | } 30 | 31 | // Get a single Mentor 32 | async findMentorById(mentorId: Schema.Types.ObjectId): Promise { 33 | try { 34 | const mentor = await this.mentorModel.findById(mentorId).exec(); 35 | 36 | if (mentor) { 37 | return mentor; 38 | } 39 | } catch (e) { 40 | throw new InternalServerErrorException(e); 41 | } 42 | throw new NotFoundException('Error'); 43 | } 44 | 45 | // post a single Mentor 46 | async addMentor(CreateMentorDTO: CreateMentorDTO): Promise { 47 | try { 48 | const newMentor = await new this.mentorModel(CreateMentorDTO); 49 | return newMentor.save(); 50 | } catch (e) { 51 | throw new InternalServerErrorException(e); 52 | } 53 | } 54 | 55 | // Edit Mentor details 56 | async updateMentor( 57 | mentorId: Schema.Types.ObjectId, 58 | UpdateMentorDTO: UpdateMentorDTO, 59 | ): Promise { 60 | try { 61 | return await this.mentorModel 62 | .findByIdAndUpdate(mentorId, UpdateMentorDTO, { 63 | new: true, 64 | useFindAndModify: false, 65 | }) 66 | .exec(); 67 | } catch (e) { 68 | throw new InternalServerErrorException(e); 69 | } 70 | } 71 | 72 | // Delete a Mentor 73 | async deleteMentor(mentorId: Schema.Types.ObjectId): Promise { 74 | try { 75 | return await this.mentorModel.findByIdAndRemove(mentorId); 76 | } catch (e) { 77 | throw new InternalServerErrorException(e); 78 | } 79 | } 80 | 81 | // assign course to mentor 82 | async assignCourseToMentor( 83 | mentorId: Schema.Types.ObjectId, 84 | courseId: Schema.Types.ObjectId, 85 | ): Promise { 86 | try { 87 | const mentor = await this.mentorModel.findById(mentorId); 88 | if (mentor) { 89 | const doesCourseExists = await this.courseModel.exists({ 90 | _id: courseId['courseId'], 91 | }); 92 | if (doesCourseExists) { 93 | const isAlreadypresent = mentor.courses.includes( 94 | courseId['courseId'], 95 | ); 96 | if (!isAlreadypresent) { 97 | mentor.courses.push(courseId['courseId']); 98 | await mentor.save(); 99 | return mentor; 100 | } else { 101 | throw new ConflictException('Course Already Assigned'); 102 | } 103 | } else { 104 | throw new NotFoundException('Course not found'); 105 | } 106 | } else { 107 | throw new NotFoundException('Mentor not found'); 108 | } 109 | } catch (e) { 110 | throw new InternalServerErrorException(e); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/modules/course/dto/create-course.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsArray, 3 | IsBoolean, 4 | IsDateString, 5 | IsEnum, 6 | IsNotEmpty, 7 | IsNumber, 8 | IsOptional, 9 | IsString, 10 | IsUrl, 11 | } from 'class-validator'; 12 | import { TagType } from '../course-tag.enum'; 13 | import { courseLevelType } from '../courseLevel.enum'; 14 | 15 | export class CreateCourseDto { 16 | /** 17 | * The name of the course 18 | * @example 'Rest Apis' 19 | */ 20 | @IsString() 21 | readonly name: string; 22 | 23 | /** 24 | * The original price of the course 25 | * @example 400 26 | */ 27 | @IsNumber() 28 | readonly originalPrice: number; 29 | 30 | /** 31 | * Whether the user is active or not in the course 32 | * @example true 33 | */ 34 | @IsBoolean() 35 | readonly active: boolean; 36 | 37 | /** 38 | * The coupon code of the course 39 | * @example 'CFC424' 40 | */ 41 | @IsString() 42 | couponCode: string; 43 | 44 | /** 45 | * The number of videos of the course 46 | * @example 3 47 | */ 48 | @IsNumber() 49 | readonly video_num: number; 50 | 51 | /** 52 | * The duration of the course 53 | * @example '11 hours' 54 | */ 55 | @IsString() 56 | readonly duration: string; 57 | 58 | /** 59 | * The start date of the course 60 | * @example '2020-02-05T06:35:22.000Z' 61 | */ 62 | @IsDateString() 63 | @IsOptional() 64 | readonly start_date: Date; 65 | 66 | /** 67 | * The end date of the course 68 | * @example '2020-02-05T06:35:22.000Z' 69 | */ 70 | @IsDateString() 71 | @IsOptional() 72 | end_date: Date; 73 | 74 | /** 75 | * The sharable link of the course 76 | * @example 'https://sharable_link.com' 77 | */ 78 | @IsUrl() 79 | @IsOptional() 80 | readonly sharable_link: string; 81 | 82 | /** 83 | * The Mentor of the course 84 | * @example ['John Doe'] 85 | */ 86 | @IsArray() 87 | mentor?: string[]; 88 | 89 | /** 90 | * The Tag associated with the course 91 | * @example WEB_DEV 92 | */ 93 | @IsNotEmpty() 94 | @IsArray() 95 | @IsEnum(TagType, { each: true }) 96 | tags: TagType[]; 97 | 98 | /** 99 | * The details of the course 100 | * @example 'The course gives a hands on learning experience on Rest APIs and Javascript' 101 | */ 102 | @IsNotEmpty() 103 | courseDetails: string; 104 | 105 | /** 106 | * The level associated with the course 107 | * @example BEGINNER 108 | */ 109 | @IsNotEmpty() 110 | @IsEnum(courseLevelType) 111 | courseLevel: courseLevelType; 112 | 113 | /** 114 | * The link/URL of the course 115 | * @example 'https://codeforcause.org/courses' 116 | */ 117 | @IsUrl() 118 | @IsNotEmpty() 119 | courseThumbnail: string; 120 | 121 | /** 122 | * The link/URL of the course trailer 123 | * @example 'https://codeforcause.org/courseTrailer' 124 | */ 125 | @IsUrl() 126 | @IsNotEmpty() 127 | courseTrailerUrl: string; 128 | 129 | /** 130 | * The discounted price of the course 131 | * @example 120 132 | */ 133 | @IsNumber() 134 | crossPrice?: number; 135 | 136 | /** 137 | * The short description of the course 138 | * @example 'Short description--' 139 | */ 140 | @IsString() 141 | courseShortDescription?: string; 142 | 143 | /** 144 | * The long description of the course 145 | * @example 'Long description--' 146 | */ 147 | @IsString() 148 | courseLongDescription?: string; 149 | 150 | /** 151 | * The rating of the course 152 | * @example 5 153 | */ 154 | @IsNumber() 155 | rating: number; 156 | 157 | /** 158 | * The prerequisites of the course 159 | * @example ["HTML","CSS"] 160 | */ 161 | @IsArray() 162 | prerequisites: string[]; 163 | 164 | /** 165 | * The skills of the course 166 | * @example ["HTML","CSS"] 167 | */ 168 | @IsArray() 169 | skills: string[]; 170 | 171 | /** 172 | * what will one learn from the course 173 | * @example ["You will get to know about web technologies basics", "A good understanstanding of Html, css and JS", "You will learn about hooks and functional components"] 174 | */ 175 | @IsArray() 176 | whatYouWillLearn?: string[]; 177 | 178 | /** 179 | * the certificate Url 180 | * @example "https://codeforcause.org/certificate" 181 | */ 182 | @IsString() 183 | certificateUrl?: string; 184 | 185 | /** 186 | * The boolean value to depict whether the course is upcoming or not 187 | * @example false 188 | */ 189 | @IsBoolean() 190 | isUpcoming: boolean; 191 | } 192 | -------------------------------------------------------------------------------- /src/modules/course/dto/course-update.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IsArray, 3 | IsBoolean, 4 | IsDateString, 5 | IsEnum, 6 | IsNotEmpty, 7 | IsNumber, 8 | IsOptional, 9 | IsString, 10 | IsUrl, 11 | } from 'class-validator'; 12 | import { TagType } from '../course-tag.enum'; 13 | import { courseLevelType } from '../courseLevel.enum'; 14 | 15 | export class UpdateCourseDTO { 16 | /** 17 | * The name of the course 18 | * @example 'Rest Apis' 19 | */ 20 | @IsString() 21 | readonly name: string; 22 | 23 | /** 24 | * The original price of the course 25 | * @example 400 26 | */ 27 | @IsNumber() 28 | readonly originalPrice: number; 29 | 30 | /** 31 | * Whether the user is active or not in the course 32 | * @example true 33 | */ 34 | @IsBoolean() 35 | readonly active: boolean; 36 | 37 | /** 38 | * The coupon code of the course 39 | * @example 'CFC424' 40 | */ 41 | @IsString() 42 | couponCode: string; 43 | 44 | /** 45 | * The number of videos of the course 46 | * @example 3 47 | */ 48 | @IsNumber() 49 | readonly video_num: number; 50 | 51 | /** 52 | * The duration of the course 53 | * @example '11 hours' 54 | */ 55 | @IsString() 56 | readonly duration: string; 57 | 58 | /** 59 | * The start date of the course 60 | * @example '2020-02-05T06:35:22.000Z' 61 | */ 62 | @IsDateString() 63 | @IsOptional() 64 | readonly start_date: Date; 65 | 66 | /** 67 | * The end date of the course 68 | * @example '2020-02-05T06:35:22.000Z' 69 | */ 70 | @IsDateString() 71 | @IsOptional() 72 | end_date: Date; 73 | 74 | /** 75 | * The sharable link of the course 76 | * @example 'https://sharable_link.com' 77 | */ 78 | @IsUrl() 79 | @IsOptional() 80 | readonly sharable_link: string; 81 | 82 | /** 83 | * The number of enrollments of the course 84 | * @example 100001 85 | */ 86 | @IsOptional() 87 | no_of_enrollments: number; 88 | 89 | /** 90 | * The Tag associated with the course 91 | * @example WEB_DEV 92 | */ 93 | @IsNotEmpty() 94 | @IsArray() 95 | @IsEnum(TagType, { each: true }) 96 | tags: TagType[]; 97 | 98 | /** 99 | * The details of the course 100 | * @example 'The course gives a hands on learning experience on Rest APIs and Javascript' 101 | */ 102 | @IsNotEmpty() 103 | courseDetails: string; 104 | 105 | /** 106 | * The level associated with the course 107 | * @example BEGINNER 108 | */ 109 | @IsNotEmpty() 110 | @IsEnum(courseLevelType) 111 | courseLevel: courseLevelType; 112 | 113 | /** 114 | * The link/URL of the course 115 | * @example 'https://codeforcause.org/courses' 116 | */ 117 | @IsUrl() 118 | @IsNotEmpty() 119 | courseThumbnail: string; 120 | 121 | /** 122 | * The link/URL of the course trailer 123 | * @example 'https://codeforcause.org/courseTrailer' 124 | */ 125 | @IsUrl() 126 | @IsNotEmpty() 127 | courseTrailerUrl: string; 128 | 129 | /** 130 | * The discounted price of the course 131 | * @example 120 132 | */ 133 | @IsNumber() 134 | crossPrice?: number; 135 | 136 | /** 137 | * The short description of the course 138 | * @example 'Short description--' 139 | */ 140 | @IsString() 141 | courseShortDescription?: string; 142 | 143 | /** 144 | * The long description of the course 145 | * @example 'Long description--' 146 | */ 147 | @IsString() 148 | courseLongDescription?: string; 149 | 150 | /** 151 | * The rating of the course 152 | * @example 5 153 | */ 154 | @IsNumber() 155 | rating?: number; 156 | 157 | /** 158 | * The prerequisites of the course 159 | * @example ["HTML","CSS"] 160 | */ 161 | @IsArray() 162 | prerequisites?: string[]; 163 | 164 | /** 165 | * The skills of the course 166 | * @example ["HTML","CSS"] 167 | */ 168 | @IsArray() 169 | skills: string[]; 170 | 171 | /** 172 | * what will one learn from the course 173 | * @example ["You will get to know about web technologies basics", "A good understanstanding of Html, css and JS", "You will learn about hooks and functional components"] 174 | */ 175 | @IsArray() 176 | whatYouWillLearn?: string[]; 177 | 178 | /** 179 | * the certificate Url 180 | * @example "https://codeforcause.org/certificate" 181 | */ 182 | @IsString() 183 | certificateUrl?: string; 184 | 185 | /** 186 | * The boolean value to depict whether the course is upcoming or not 187 | * @example false 188 | */ 189 | @IsBoolean() 190 | isUpcoming: boolean; 191 | } 192 | -------------------------------------------------------------------------------- /src/modules/doubt/doubt.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | Query, 10 | } from '@nestjs/common'; 11 | import { 12 | ApiCreatedResponse, 13 | ApiOkResponse, 14 | ApiOperation, 15 | ApiParam, 16 | ApiTags, 17 | } from '@nestjs/swagger'; 18 | import { courseId, doubtAnswerId, doubtId } from './docUtils/doubt.paramdocs'; 19 | import { Schema } from 'mongoose'; 20 | import responsedoc from './docUtils/apidoc'; 21 | import { DoubtService } from './doubt.service'; 22 | import { CreateDoubtDto } from './dto/create-doubt.dto'; 23 | import { CreateDoubtAnswerDto } from './dto/create-doubtAnswer.dto'; 24 | import { UpdateDoubtDto } from './dto/update-doubt.dto'; 25 | import { UpdateDoubtAnswerDto } from './dto/update-doubtAnswer.dto'; 26 | import { DoubtDocument as Doubt } from './schema/doubt.schema'; 27 | import { DoubtAnswer } from './schema/doubtAnswer.schema'; 28 | 29 | @ApiTags('Doubt') 30 | @Controller('doubt') 31 | export class DoubtController { 32 | constructor(private doubtService: DoubtService) {} 33 | 34 | @Get() 35 | @ApiOkResponse(responsedoc.getAllDoubts) 36 | @ApiOperation({ summary: 'Retrieve doubts list' }) 37 | async getAllDoubts(@Query('skip') skip: string) { 38 | return await this.doubtService.getAllDoubts(skip); 39 | } 40 | 41 | @Get('/get/:doubtId') 42 | @ApiParam(doubtId) 43 | @ApiOkResponse(responsedoc.getDoubtById) 44 | @ApiOperation({ summary: 'Retrieve doubt by id' }) 45 | async getDoubtById(@Param('doubtId') doubtId: Schema.Types.ObjectId) { 46 | return await this.doubtService.getDoubtById(doubtId); 47 | } 48 | 49 | @Get('/get/:courseId') 50 | @ApiParam(courseId) 51 | @ApiOkResponse(responsedoc.getDoubtById) 52 | @ApiOperation({ summary: 'Retrieve doubts for courses' }) 53 | async getDoubtsForSelectedCourse( 54 | @Param('courseId') courseId: Schema.Types.ObjectId, 55 | ): Promise { 56 | return await this.doubtService.findDoubtsForSelectedCourse(courseId); 57 | } 58 | 59 | @Post('/new/:courseId') 60 | @ApiParam(courseId) 61 | @ApiCreatedResponse(responsedoc.addDoubt) 62 | @ApiOperation({ summary: 'Add a new doubt' }) 63 | async askNewDoubt( 64 | @Param('courseId') courseId: Schema.Types.ObjectId, 65 | @Body() createDoubtDto: CreateDoubtDto, 66 | ): Promise { 67 | return await this.doubtService.addNewDoubt(courseId, createDoubtDto); 68 | } 69 | 70 | @Put('/updateDoubt/:courseId/:doubtId') 71 | @ApiParam(courseId) 72 | @ApiParam(doubtId) 73 | @ApiCreatedResponse(responsedoc.updateDoubt) 74 | @ApiOperation({ summary: 'Edit doubt by id' }) 75 | async editDoubt( 76 | @Param('courseId') courseId: Schema.Types.ObjectId, 77 | @Param('doubtId') doubtId: Schema.Types.ObjectId, 78 | @Body() updateDoubtDto: UpdateDoubtDto, 79 | ): Promise { 80 | return await this.doubtService.editDoubt(courseId, doubtId, updateDoubtDto); 81 | } 82 | 83 | @Delete('/deleteDoubt/:courseId/:doubtId') 84 | @ApiParam(courseId) 85 | @ApiParam(doubtId) 86 | @ApiCreatedResponse(responsedoc.deleteDoubt) 87 | async deleteDoubt( 88 | @Param('courseId') courseId: Schema.Types.ObjectId, 89 | @Param('doubtId') doubtId: Schema.Types.ObjectId, 90 | ): Promise { 91 | return await this.doubtService.deleteDoubt(courseId, doubtId); 92 | } 93 | 94 | @Post('/newAnswer/:doubtId') 95 | @ApiParam(doubtId) 96 | @ApiCreatedResponse(responsedoc.addDoubtAnswer) 97 | @ApiOperation({ summary: 'Add a new doubt Answer' }) 98 | async askNewDoubtAnswer( 99 | @Param('doubtId') doubtId: Schema.Types.ObjectId, 100 | @Body() createDoubtAnswerDto: CreateDoubtAnswerDto, 101 | ): Promise { 102 | return await this.doubtService.addNewDoubtAnswer( 103 | doubtId, 104 | createDoubtAnswerDto, 105 | ); 106 | } 107 | 108 | @Put('/updateDoubtAnswer/:doubtId/:doubtAnswerId') 109 | @ApiParam(doubtAnswerId) 110 | @ApiParam(doubtId) 111 | @ApiCreatedResponse(responsedoc.updateDoubtAnswer) 112 | @ApiOperation({ summary: 'Edit doubt Answer by id' }) 113 | async editDoubtAnswer( 114 | @Param('doubtId') doubtId: Schema.Types.ObjectId, 115 | @Param('doubtAnswerId') doubtAnswerId: Schema.Types.ObjectId, 116 | @Body() updateDoubtAnswerDto: UpdateDoubtAnswerDto, 117 | ): Promise { 118 | return await this.doubtService.editDoubtAnswer( 119 | doubtId, 120 | doubtAnswerId, 121 | updateDoubtAnswerDto, 122 | ); 123 | } 124 | 125 | @Delete('/deleteDoubtAnswer/:doubtId/:doubtAnswerId') 126 | @ApiParam(doubtAnswerId) 127 | @ApiParam(doubtId) 128 | @ApiCreatedResponse(responsedoc.deleteDoubtAnswer) 129 | @ApiOperation({ summary: 'Delete doubt Answer by id' }) 130 | async deleteDoubtAnswer( 131 | @Param('doubtId') doubtId: Schema.Types.ObjectId, 132 | @Param('doubtAnswerId') doubtAnswerId: Schema.Types.ObjectId, 133 | ): Promise { 134 | return await this.doubtService.deleteDoubtAnswer(doubtId, doubtAnswerId); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/modules/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UpdateUserDTO } from './dto/update-user.dto'; 3 | import { UserController } from './user.controller'; 4 | import { UserService } from './user.service'; 5 | import { CreateUserDTO } from './dto/create-user.dto'; 6 | const mockuser = { 7 | wishlist: [], 8 | cartList: [], 9 | enrolled_courses: [], 10 | role: 'Student', 11 | score: 0, 12 | first_name: 'John', 13 | last_name: 'Doe', 14 | email: 'john@example.com', 15 | created_at: '2021-03-27T14:05:28.000Z', 16 | phone: '9909999099', 17 | __v: 0, 18 | photoUrl: 'https://google.com/john', 19 | log_in_time: 'Wed Aug 18 2021 00:13:13 GMT+0530 (India Standard Time)', 20 | }; 21 | 22 | describe('UserController', () => { 23 | let controller: UserController; 24 | let service: UserService; 25 | 26 | const mockUservalue = { 27 | getAllUser: jest.fn().mockResolvedValue([mockuser]), 28 | getMe: jest.fn().mockResolvedValue(mockuser), 29 | findUserByEmail: jest 30 | .fn() 31 | .mockImplementation((email) => ({ ...mockuser, email })), 32 | updateUser: jest.fn().mockImplementation((body) => ({ 33 | ...mockuser, 34 | ...body, 35 | })), 36 | addUser: jest.fn().mockImplementation((body) => ({ ...mockuser, ...body })), 37 | // deleteUser: jest.fn().mockResolvedValue([mockuser]), 38 | // getEnrolledCourses: jest.fn().mockResolvedValue([mockuser]), 39 | // addCourse: jest.fn().mockResolvedValue([mockuser]), 40 | // getWishList: jest.fn().mockResolvedValue([mockuser]), 41 | }; 42 | 43 | beforeEach(async () => { 44 | const module: TestingModule = await Test.createTestingModule({ 45 | controllers: [UserController], 46 | providers: [ 47 | { 48 | provide: UserService, 49 | useValue: mockUservalue, 50 | }, 51 | ], 52 | }).compile(); 53 | 54 | controller = module.get(UserController); 55 | service = module.get(UserService); 56 | }); 57 | 58 | it('should be defined', () => { 59 | expect(controller).toBeDefined(); 60 | }); 61 | 62 | describe('User', () => { 63 | it('should be created', async () => { 64 | const dto: CreateUserDTO = { 65 | first_name: 'John', 66 | last_name: 'Doe', 67 | phone: '9909999099', 68 | photoUrl: 'https://google.com/john', 69 | score: 0, 70 | }; 71 | await expect(controller.addUser('x', dto)).resolves.toEqual({ 72 | '0': 'x', 73 | role: 'Student', 74 | log_in_time: 'Wed Aug 18 2021 00:13:13 GMT+0530 (India Standard Time)', 75 | ...mockuser, 76 | }); 77 | }); 78 | 79 | it('should find all users', async () => { 80 | await expect(controller.getAllUser()).resolves.toEqual([mockuser]); 81 | }); 82 | 83 | it('should be found', async () => { 84 | // const _id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 85 | await expect(controller.getMe()).resolves.toEqual({ 86 | role: 'Student', 87 | ...mockuser, 88 | }); 89 | expect(service.getMe).toHaveBeenCalledWith(); 90 | }); 91 | 92 | it('should be found', async () => { 93 | // const _id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 94 | await expect(controller.getMe()).resolves.toEqual({ 95 | role: 'Student', 96 | ...mockuser, 97 | }); 98 | expect(service.getMe).toHaveBeenCalledWith(); 99 | }); 100 | 101 | it('should be found by email ID', async () => { 102 | const email = 'john@example.com'; 103 | await expect(controller.getuserByEmail(email)).resolves.toEqual({ 104 | role: 'Student', 105 | ...mockuser, 106 | }); 107 | expect(service.findUserByEmail).toHaveBeenCalledWith(email); 108 | }); 109 | 110 | it('should not be found be with another email ID', async () => { 111 | // const email = 'john@example.com'; 112 | const emailOther = 'cfc@gmail.com'; 113 | await expect(controller.getuserByEmail(emailOther)).resolves.not.toEqual({ 114 | role: 'Student', 115 | ...mockuser, 116 | }); 117 | expect(service.findUserByEmail).toHaveBeenCalledWith(emailOther); 118 | }); 119 | 120 | it('should be updated', async () => { 121 | // const _id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 122 | const dto: UpdateUserDTO = { 123 | first_name: 'New Name', 124 | last_name: 'New', 125 | phone: '', 126 | wishlist: [], 127 | address: '', 128 | description: '', 129 | score: 1, 130 | coverPhotoUrl: '', 131 | photoUrl: '', 132 | }; 133 | const email = 'john@example.com'; 134 | await expect(controller.updateUser(dto)).resolves.toEqual({ 135 | email, 136 | role: 'Student', 137 | ...dto, 138 | log_in_time: 'Wed Aug 18 2021 00:13:13 GMT+0530 (India Standard Time)', 139 | cartList: [], 140 | wishlist: [], 141 | enrolled_courses: [], 142 | created_at: '2021-03-27T14:05:28.000Z', 143 | __v: 0, 144 | }); 145 | }); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /src/modules/mentor/mentor.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelToken } from '@nestjs/mongoose'; 2 | import { Test, TestingModule } from '@nestjs/testing'; 3 | import { createMock } from '@golevelup/nestjs-testing'; 4 | import { MentorService } from './mentor.service'; 5 | import { Mentor, MentorDocument as MentorDoc } from './schema/mentor.schema'; 6 | import { Model, Query } from 'mongoose'; 7 | import * as mongoose from 'mongoose'; 8 | import { UpdateMentorDTO } from './dto/update-mentor.dto'; 9 | 10 | const mockMentor = ( 11 | name = 'Abhishek Kumar', 12 | email = 'Abhishsek@gmail.com', 13 | courses = [], 14 | number_of_students = 100000, 15 | mentorPhoto = 'https://codeforcause.org/static/images', 16 | aboutMe = 'I am a full stack developer', 17 | techStack = ['MERN', 'Java', 'Python'], 18 | ): Mentor => ({ 19 | name, 20 | email, 21 | courses, 22 | number_of_students, 23 | mentorPhoto, 24 | aboutMe, 25 | techStack, 26 | }); 27 | 28 | const mockMentorDoc = (mock?: Partial, _id?): Partial => ({ 29 | _id: _id || '6079f573062890a5e2cad207', 30 | name: mock?.name || 'Abhishek Kumar', 31 | email: mock?.email || 'Abhishsek@gmail.com', 32 | courses: mock?.courses || [], 33 | number_of_students: mock?.number_of_students || 100000, 34 | mentorPhoto: mock?.mentorPhoto || 'https://codeforcause.org/static/images', 35 | aboutMe: mock?.aboutMe || 'I am a full stack developer', 36 | techStack: mock?.techStack || ['MERN', 'Java', 'Python'], 37 | }); 38 | 39 | describe('MentorService', () => { 40 | let service: MentorService; 41 | let model: Model; 42 | 43 | beforeEach(async () => { 44 | const module: TestingModule = await Test.createTestingModule({ 45 | providers: [ 46 | MentorService, 47 | { 48 | provide: getModelToken('Mentor'), 49 | useValue: { 50 | new: jest.fn().mockResolvedValue(mockMentor), 51 | constructor: jest.fn().mockResolvedValue(mockMentor), 52 | find: jest.fn(), 53 | populate: jest.fn(), 54 | findOne: jest.fn(), 55 | findById: jest.fn(), 56 | update: jest.fn(), 57 | create: jest.fn(), 58 | remove: jest.fn(), 59 | exec: jest.fn(), 60 | findByIdAndUpdate: jest.fn(), 61 | }, 62 | }, 63 | { 64 | provide: getModelToken('Course'), 65 | useValue: {}, 66 | }, 67 | ], 68 | }).compile(); 69 | 70 | service = module.get(MentorService); 71 | model = module.get>(getModelToken('Mentor')); 72 | }); 73 | 74 | it('should be defined', () => { 75 | expect(service).toBeDefined(); 76 | }); 77 | 78 | beforeEach(() => { 79 | jest.setTimeout(10000); 80 | }); 81 | 82 | afterEach(() => { 83 | jest.clearAllMocks(); 84 | }); 85 | 86 | describe('Testing MentorService after mock', () => { 87 | const _id = '60bca010d17d463dd09baf9b'; 88 | const mentorDocArray = [mockMentorDoc({}, _id)]; 89 | 90 | // Test for testing the service for returning all mentors 91 | it('should return all mentors', async () => { 92 | jest.spyOn(model, 'find').mockReturnValue({ 93 | exec: jest.fn().mockResolvedValueOnce(mentorDocArray), 94 | } as any); 95 | // console.log(mentorDocArray); 96 | const mentors = await service.getAllMentor(); 97 | // console.log(mentors); 98 | const mentorArray = [{ ...mockMentor(), _id }]; 99 | expect(mentors).toEqual(mentorArray); 100 | }); 101 | 102 | // Test for testing the service for finding a Mentor by id 103 | it('should getOne Mentor by id', async () => { 104 | jest.spyOn(model, 'findById').mockReturnValueOnce( 105 | createMock>({ 106 | exec: jest.fn().mockResolvedValueOnce(mockMentorDoc()), 107 | }), 108 | ); 109 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 110 | const findMockMentor = { 111 | ...mockMentor(), 112 | _id: '6079f573062890a5e2cad207', 113 | }; 114 | const foundMentor = await service.findMentorById(id); 115 | expect(foundMentor).toEqual(findMockMentor); 116 | }); 117 | 118 | // Test for testing the service for updating a Mentor 119 | it('should update a Mentor successfully', async () => { 120 | jest.spyOn(model, 'findByIdAndUpdate').mockReturnValueOnce( 121 | createMock>({ 122 | exec: jest.fn().mockResolvedValueOnce({ 123 | _id: '6079f573062890a5e2cad207', 124 | name: 'Abhishek Kumar', 125 | email: 'Abhishsek@gmail.com', 126 | courses: [], 127 | number_of_students: 100000, 128 | mentorPhoto: 'https://codeforcause.org/static/images', 129 | aboutMe: 'I am a full stack developer', 130 | techStack: ['MERN', 'Java', 'Python'], 131 | }), 132 | }), 133 | ); 134 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 135 | const updatedMentordto: UpdateMentorDTO = { 136 | name: 'Abhishek Kumar', 137 | email: 'Abhishsek@gmail.com', 138 | number_of_students: 100000, 139 | mentorPhoto: 'https://codeforcause.org/static/images', 140 | aboutMe: 'I am a full stack developer', 141 | techStack: ['MERN', 'Java', 'Python'], 142 | }; 143 | const updatedMentor = await service.updateMentor(id, updatedMentordto); 144 | const _id = '6079f573062890a5e2cad207'; 145 | const updatedMentorFinal = { ...mockMentor(), ...updatedMentor, _id }; 146 | expect(updatedMentor).toEqual(updatedMentorFinal); 147 | }); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /src/modules/mentor/mentor.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MentorController } from './mentor.controller'; 3 | import { MentorService } from './mentor.service'; 4 | import { CreateMentorDTO } from './dto/create-mentor.dto'; 5 | import { UpdateMentorDTO } from './dto/update-mentor.dto'; 6 | import * as mongoose from 'mongoose'; 7 | const mockMentor = { 8 | id: '60c5eafba5940a4964d5ea96', 9 | name: ' John Doe ', 10 | email: ' johnDoe@gmail.com ', 11 | courses: [], 12 | number_of_students: 500, 13 | mentorPhoto: 'https://g.gle/mypic.jpeg', 14 | aboutMe: 'I am a developer', 15 | techStack: ['MERN', 'Python'], 16 | }; 17 | 18 | describe('MentorController', () => { 19 | let controller: MentorController; 20 | let service: MentorService; 21 | 22 | const mockMentorvalue = { 23 | getAllMentor: jest.fn().mockResolvedValue([mockMentor]), 24 | findMentorById: jest 25 | .fn() 26 | .mockImplementation((id: string) => ({ ...mockMentor, id })), 27 | editMentor: jest.fn().mockImplementation((uid, body) => ({ 28 | ...mockMentor, 29 | id: uid, 30 | ...body, 31 | })), 32 | addMentor: jest.fn().mockResolvedValue([mockMentor]), 33 | updateMentor: jest.fn().mockResolvedValue([mockMentor]), 34 | deleteMentor: jest.fn().mockResolvedValue([mockMentor]), 35 | }; 36 | 37 | beforeEach(async () => { 38 | const module: TestingModule = await Test.createTestingModule({ 39 | controllers: [MentorController], 40 | providers: [ 41 | { 42 | provide: MentorService, 43 | useValue: mockMentorvalue, 44 | }, 45 | ], 46 | }).compile(); 47 | 48 | controller = module.get(MentorController); 49 | service = module.get(MentorService); 50 | }); 51 | 52 | // check if controller is defined 53 | it('should be defined', () => { 54 | expect(controller).toBeDefined(); 55 | }); 56 | 57 | describe('Mentor', () => { 58 | // Mentor creation 59 | it('Mentor should be created', async () => { 60 | await expect(controller.getAllMentor()).resolves.toEqual([mockMentor]); 61 | }); 62 | 63 | // Mentor found by id 64 | it('Mentor should be found by ID', async () => { 65 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 66 | await expect(controller.getMentor(id)).resolves.toEqual({ 67 | ...mockMentor, 68 | id, 69 | }); 70 | expect(service.findMentorById).toHaveBeenCalledWith(id); 71 | }); 72 | 73 | // retrieved value not equal as id if different/does not exist 74 | it('Mentor should not be equal to the returned value if ID does not exist', async () => { 75 | const id = new mongoose.Schema.Types.ObjectId('18', 0, 'riep'); 76 | const idFix = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 77 | await expect(controller.getMentor(id)).resolves.not.toEqual({ 78 | ...mockMentor, 79 | idFix, 80 | }); 81 | expect(service.findMentorById).toHaveBeenCalledWith(id); 82 | }); 83 | 84 | // Mentor should be created 85 | it('Mentor should be created', async () => { 86 | const dto: CreateMentorDTO = { 87 | name: ' John Doe ', 88 | email: ' johnDoe@gmail.com ', 89 | number_of_students: 500, 90 | mentorPhoto: 'https://g.gle/mypic.jpeg', 91 | aboutMe: 'I am a developer', 92 | techStack: ['MERN', 'Python'], 93 | }; 94 | await expect(controller.addMentor(dto)).resolves.not.toBeNull(); 95 | expect(service.addMentor).toHaveBeenCalledWith(dto); 96 | }); 97 | 98 | // Mentor should be updated 99 | it('Mentor should be updated', async () => { 100 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 101 | const dto: UpdateMentorDTO = { 102 | name: ' John Doe ', 103 | email: ' johnDoe@gmail.com ', 104 | number_of_students: 500, 105 | mentorPhoto: 'https://g.gle/mypic.jpeg', 106 | aboutMe: 'I am a developer', 107 | techStack: ['MERN', 'Python'], 108 | }; 109 | await expect(controller.updateMentor(id, dto)).resolves.toEqual([ 110 | { 111 | ...mockMentor, 112 | ...dto, 113 | }, 114 | ]); 115 | expect(service.updateMentor).toHaveBeenCalledWith(id, dto); 116 | }); 117 | 118 | // retrieved value not equal as id if different/does not exist 119 | it('Mentor should not be updated and be equal to the returned value if ID does not exist', async () => { 120 | const id = new mongoose.Schema.Types.ObjectId('18', 0, 'riep'); 121 | const idFix = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 122 | const dto: UpdateMentorDTO = { 123 | name: ' John Doe ', 124 | email: ' johnDoe@gmail.com ', 125 | number_of_students: 500, 126 | mentorPhoto: 'https://g.gle/mypic.jpeg', 127 | aboutMe: 'I am a developer', 128 | techStack: ['MERN', 'Python'], 129 | }; 130 | await expect(controller.updateMentor(id, dto)).resolves.not.toEqual({ 131 | ...mockMentor, 132 | idFix, 133 | }); 134 | expect(service.updateMentor).toHaveBeenCalledWith(id, dto); 135 | }); 136 | 137 | // Mentor shpuld be deleted 138 | it('Mentor should be deleted', async () => { 139 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 140 | await expect(controller.deleteMentor(id)).resolves.toEqual([ 141 | { 142 | ...mockMentor, 143 | }, 144 | ]); 145 | expect(service.deleteMentor).toHaveBeenCalledWith(id); 146 | }); 147 | }); 148 | 149 | // retrieved value not equal as id if different/does not exist 150 | it('Mentor should not be deleted and be equal to the returned value if ID does not exist', async () => { 151 | const id = new mongoose.Schema.Types.ObjectId('18', 0, 'riep'); 152 | const idFix = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 153 | await expect(controller.deleteMentor(id)).resolves.not.toEqual({ 154 | ...mockMentor, 155 | idFix, 156 | }); 157 | expect(service.deleteMentor).toHaveBeenCalledWith(id); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /src/modules/announcements/announcement.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AnnouncementController } from './announcement.controller'; 3 | import { AnnouncementService } from './announcement.service'; 4 | import { UpdateAnnouncementDTO } from './dto/update-announcement.dto'; 5 | import { CreateAnnouncementDTO } from './dto/create-announcement.dto'; 6 | const mockAnnouncement = { 7 | id: '60c5eafba5940a4964d5ea96', 8 | title: ' New Course on react ', 9 | read: false, 10 | added_by: ' John Doe ', 11 | description: 12 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 13 | }; 14 | 15 | describe('AnnouncementController', () => { 16 | let controller: AnnouncementController; 17 | let service: AnnouncementService; 18 | 19 | const mockAnnouncementvalue = { 20 | getAllAnnouncement: jest.fn().mockResolvedValue([mockAnnouncement]), 21 | getAnnouncement: jest 22 | .fn() 23 | .mockImplementation((id: string) => ({ ...mockAnnouncement, id })), 24 | editAnnouncement: jest.fn().mockImplementation((uid, body) => ({ 25 | ...mockAnnouncement, 26 | id: uid, 27 | ...body, 28 | })), 29 | addAnnouncement: jest.fn().mockResolvedValue([mockAnnouncement]), 30 | updateAnnouncement: jest.fn().mockResolvedValue([mockAnnouncement]), 31 | deleteAnnouncement: jest.fn().mockResolvedValue([mockAnnouncement]), 32 | }; 33 | 34 | beforeEach(async () => { 35 | const module: TestingModule = await Test.createTestingModule({ 36 | controllers: [AnnouncementController], 37 | providers: [ 38 | { 39 | provide: AnnouncementService, 40 | useValue: mockAnnouncementvalue, 41 | }, 42 | ], 43 | }).compile(); 44 | 45 | controller = module.get(AnnouncementController); 46 | service = module.get(AnnouncementService); 47 | }); 48 | 49 | // check if controller is defined 50 | it('should be defined', () => { 51 | expect(controller).toBeDefined(); 52 | }); 53 | 54 | describe('Course', () => { 55 | // Announcement creation 56 | it('Announcement should be created', async () => { 57 | await expect(controller.getAllAnnouncement()).resolves.toEqual([ 58 | mockAnnouncement, 59 | ]); 60 | }); 61 | 62 | // Announcement found by id 63 | it('Announcement should be found by ID', async () => { 64 | const id = '60c5eafba5940a4964d5ea96'; 65 | await expect(controller.getAnnouncement(id)).resolves.toEqual({ 66 | ...mockAnnouncement, 67 | id, 68 | }); 69 | expect(service.getAnnouncement).toHaveBeenCalledWith(id); 70 | }); 71 | 72 | // retrieved value not equal as id if different/does not exist 73 | it('Announcement should not be equal to the returned value if ID does not exist', async () => { 74 | const id = '60c5eafba5940a4884d5fa96'; 75 | const idFix = '60c5eafba5940a4964d5ea96'; 76 | await expect(controller.getAnnouncement(id)).resolves.not.toEqual({ 77 | ...mockAnnouncement, 78 | idFix, 79 | }); 80 | expect(service.getAnnouncement).toHaveBeenCalledWith(id); 81 | }); 82 | 83 | // Announcement should be created 84 | it('Announcement should be created', async () => { 85 | const dto: CreateAnnouncementDTO = { 86 | title: ' New Course on react ', 87 | read: false, 88 | added_by: ' John Doe ', 89 | description: 90 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 91 | }; 92 | await expect(controller.addAnnouncement(dto)).resolves.not.toBeNull(); 93 | expect(service.addAnnouncement).toHaveBeenCalledWith(dto); 94 | }); 95 | 96 | // Announcement should be updated 97 | it('Announcement should be updated', async () => { 98 | const id = '60c5eafba5940a4964d5ea96'; 99 | const dto: UpdateAnnouncementDTO = { 100 | title: ' New Course on react ', 101 | read: false, 102 | added_by: ' John Doe ', 103 | description: 104 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 105 | }; 106 | await expect(controller.updateAnnouncement(id, dto)).resolves.toEqual([ 107 | { 108 | id, 109 | ...dto, 110 | }, 111 | ]); 112 | expect(service.updateAnnouncement).toHaveBeenCalledWith(id, dto); 113 | }); 114 | 115 | // retrieved value not equal as id if different/does not exist 116 | it('Announcement should not be updated and be equal to the returned value if ID does not exist', async () => { 117 | const id = '60c5eafba5940a4884d5fa96'; 118 | const idFix = '60c5eafba5940a4964d5ea96'; 119 | const dto: UpdateAnnouncementDTO = { 120 | title: ' New Course on react ', 121 | read: false, 122 | added_by: ' John Doe ', 123 | description: 124 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 125 | }; 126 | await expect(controller.updateAnnouncement(id, dto)).resolves.not.toEqual( 127 | { 128 | ...mockAnnouncement, 129 | idFix, 130 | }, 131 | ); 132 | expect(service.updateAnnouncement).toHaveBeenCalledWith(id, dto); 133 | }); 134 | 135 | // Announcement shpuld be deleted 136 | it('Announcement should be deleted', async () => { 137 | const id = '60c5eafba5940a4964d5ea96'; 138 | await expect(controller.deleteAnnouncement(id)).resolves.toEqual([ 139 | { 140 | ...mockAnnouncement, 141 | }, 142 | ]); 143 | expect(service.deleteAnnouncement).toHaveBeenCalledWith(id); 144 | }); 145 | }); 146 | 147 | // retrieved value not equal as id if different/does not exist 148 | it('Announcement should not be deleted and be equal to the returned value if ID does not exist', async () => { 149 | const id = '60c5eafba5940a4884d5fa96'; 150 | const idFix = '60c5eafba5940a4964d5ea96'; 151 | await expect(controller.deleteAnnouncement(id)).resolves.not.toEqual({ 152 | ...mockAnnouncement, 153 | idFix, 154 | }); 155 | expect(service.deleteAnnouncement).toHaveBeenCalledWith(id); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /src/modules/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Query, 8 | Post, 9 | Put, 10 | Req, 11 | UseGuards, 12 | } from '@nestjs/common'; 13 | import { 14 | ApiCreatedResponse, 15 | ApiOkResponse, 16 | ApiOperation, 17 | ApiTags, 18 | } from '@nestjs/swagger'; 19 | import UserResponseBody from './docUtils/user.responsedoc'; 20 | import { CreateUserDTO } from './dto/create-user.dto'; //eslint-disable-line 21 | import { UpdateUserDTO } from './dto/update-user.dto'; //eslint-disable-line 22 | import { UserService } from './user.service'; //eslint-disable-line 23 | import { CreateEnrolledDTO } from './dto/create-enrolled.dto'; 24 | import { UpdateEnrolledDTO } from './dto/update-enrolled.dto'; 25 | import { Schema } from 'mongoose'; 26 | import responsedoc from './docUtils/apidoc'; 27 | import { RolesGuard } from '../../middleware/roles.guard'; 28 | import { Roles } from '../../middleware/role.decorator'; 29 | import { Role } from '../../roles/role.enum'; 30 | 31 | @ApiTags('User') 32 | @Controller('user') 33 | @UseGuards(RolesGuard) 34 | export class UserController { 35 | constructor(private userService: UserService) {} 36 | 37 | @Post() 38 | @ApiOperation({ summary: 'Create a User' }) 39 | @ApiCreatedResponse(responsedoc.addUser) 40 | async addUser(@Req() request, @Body() CreateUserDTO: CreateUserDTO) { 41 | return await this.userService.addUser(request, CreateUserDTO); 42 | } 43 | 44 | @Get('/me') 45 | @ApiOperation({ summary: 'Get the Logged in user Details' }) 46 | @ApiOkResponse({ type: UserResponseBody }) 47 | async getMe() { 48 | return await this.userService.getMe(); 49 | } 50 | 51 | @Get('/enrolledCourses') 52 | @ApiOperation({ 53 | summary: 'retreiving all enrolled courses of a particular user', 54 | }) 55 | @ApiOkResponse(responsedoc.getEnrolledCourses) 56 | async getEnrolledCourses() { 57 | return await this.userService.getEnrolledCourses(); 58 | } 59 | 60 | @Get('/enrolledCourses/:courseId') 61 | @ApiOperation({ 62 | summary: 'retreiving enrolled course by id of the course and of a user', 63 | }) 64 | @ApiOkResponse(responsedoc.getEnrolledCoursesById) 65 | async getEnrolledCoursesById( 66 | @Param('courseId') courseId: Schema.Types.ObjectId, 67 | ) { 68 | return await this.userService.getEnrolledCoursesById(courseId); 69 | } 70 | 71 | @Put('/enrolledCourses') 72 | @ApiOperation({ summary: 'user enrolling courses' }) 73 | @ApiCreatedResponse(responsedoc.addEnrolledCourses) 74 | async addEnrolledCourses(@Body() createEnrolledDto: CreateEnrolledDTO) { 75 | return await this.userService.addCourse(createEnrolledDto); 76 | } 77 | 78 | @Put('/enrolledCourses/:courseId') 79 | @ApiOperation({ summary: 'user updating enrolled courses' }) 80 | @ApiCreatedResponse(responsedoc.updateEnrolledCourses) 81 | async updateEnrolledCourses( 82 | @Param('courseId') courseId: Schema.Types.ObjectId, 83 | @Body() updateEnrolledDto: UpdateEnrolledDTO, 84 | ) { 85 | return await this.userService.updateCourse(updateEnrolledDto, courseId); 86 | } 87 | 88 | @Get('/wishlist') 89 | @ApiOperation({ summary: 'Get all wishlisted courses' }) 90 | @ApiOkResponse(responsedoc.getWishlist) 91 | async getWishlist(): Promise { 92 | return await this.userService.getWishList(); 93 | } 94 | 95 | @Put('/wishlist') 96 | @ApiOperation({ summary: 'Add wishlisted courses' }) 97 | @ApiCreatedResponse(responsedoc.addWishlist) 98 | async addWishlist(@Body() cId: Schema.Types.ObjectId) { 99 | return await this.userService.addWishlist(cId); 100 | } 101 | 102 | @Get('/cartList') 103 | @ApiOperation({ summary: 'Get all cartList courses' }) 104 | @ApiOkResponse(responsedoc.getCartList) 105 | async getCartList(): Promise { 106 | return await this.userService.getCartList(); 107 | } 108 | 109 | @Put('/cartList') 110 | @ApiOperation({ summary: 'Add courses to cartList' }) 111 | @ApiCreatedResponse(responsedoc.addCartList) 112 | async addcartList(@Body() cId: Schema.Types.ObjectId) { 113 | return await this.userService.addCartList(cId); 114 | } 115 | 116 | @Get('/get') 117 | @ApiOperation({ summary: 'Fetch a particular User using Email ID' }) 118 | @ApiOkResponse({ type: UserResponseBody }) 119 | async getuserByEmail(@Query() email) { 120 | return await this.userService.findUserByEmail(email); 121 | } 122 | 123 | @Get('/users') 124 | @Roles(Role.ADMIN) 125 | @ApiOperation({ summary: 'Retreive user list' }) 126 | @ApiOkResponse(responsedoc.getAllUser) 127 | async getAllUser() { 128 | return await this.userService.getAllUser(); 129 | } 130 | 131 | @Get('/gamification') 132 | @ApiOperation({ summary: 'Gamification data retreival endpoint' }) 133 | @ApiOkResponse(responsedoc.getAllGamified) 134 | async getAllGamified(@Query('skip') skip: string) { 135 | return await this.userService.getAllGamified(skip); 136 | } 137 | 138 | @Put('/update') 139 | @ApiOperation({ summary: 'update a User ' }) 140 | @ApiOkResponse({ type: UserResponseBody }) 141 | async updateUser(@Body() UpdateUserDTO: UpdateUserDTO) { 142 | return await this.userService.updateUser(UpdateUserDTO); 143 | } 144 | 145 | @Delete('/delete') 146 | @Roles(Role.ADMIN) 147 | @ApiOperation({ summary: 'Delete a User' }) 148 | @ApiOkResponse({ type: UserResponseBody }) 149 | async deleteUser(@Query() query) { 150 | return await this.userService.deleteUser(query); 151 | } 152 | 153 | @Delete('/enrolledCourses/:courseId') 154 | @ApiOperation({ summary: 'Delete enrolled course' }) 155 | @ApiOkResponse({ type: [Schema.Types.ObjectId] }) 156 | async deleteEnrolled(@Param('courseId') courseId: Schema.Types.ObjectId) { 157 | return await this.userService.deleteEnrolledCourse(courseId); 158 | } 159 | 160 | @Delete('/wishlist/:wishId') 161 | @ApiOperation({ summary: 'Delete a wishlist' }) 162 | @ApiOkResponse({ type: [Schema.Types.ObjectId] }) 163 | async deleteWishList(@Param('wishId') wishId: Schema.Types.ObjectId) { 164 | return await this.userService.deleteWishList(wishId); 165 | } 166 | 167 | @Delete('/cartList/:cartId') 168 | @ApiOperation({ summary: 'Delete a course from cartlist' }) 169 | @ApiOkResponse({ type: [Schema.Types.ObjectId] }) 170 | async deleteCartList(@Param('cartId') cartId: Schema.Types.ObjectId) { 171 | return await this.userService.deleteCardList(cartId); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/modules/course/docUtils/course.responsedoc.ts: -------------------------------------------------------------------------------- 1 | import { Assignment } from 'modules/assignment/schema/assignment.schema'; 2 | import { TagType } from '../course-tag.enum'; 3 | import { courseLevelType } from '../courseLevel.enum'; 4 | import { ReviewType } from '../review.enum'; 5 | import { Review } from '../schema/review.schema'; 6 | import { Schedule } from '../schema/schedule.schema'; 7 | 8 | export default class CourseResponseBody { 9 | /** 10 | * CourseId 11 | * @example 60ccf3037025096f45cb87bf 12 | */ 13 | id: string; 14 | 15 | /** 16 | * The name of the course 17 | * @example 'Rest Apis' 18 | */ 19 | name: string; 20 | 21 | /** 22 | * The original price of the course 23 | * @example 400 24 | */ 25 | originalPrice: number; 26 | 27 | /** 28 | * Whether the user is active or not in the course 29 | * @example true 30 | */ 31 | active: boolean; 32 | 33 | /** 34 | * The coupon code of the course 35 | * @example 'CFC424' 36 | */ 37 | couponCode: string; 38 | 39 | /** 40 | * The number of videos of the course 41 | * @example 3 42 | */ 43 | video_num: number; 44 | 45 | /** 46 | * The duration of the course 47 | * @example '11 hours' 48 | */ 49 | duration: string; 50 | 51 | /** 52 | * The start date of the course 53 | * @example '2020-02-05T06:35:22.000Z' 54 | */ 55 | start_date: Date; 56 | 57 | /** 58 | * The end date of the course 59 | * @example '2020-02-05T06:35:22.000Z' 60 | */ 61 | end_date: Date; 62 | 63 | /** 64 | * The sharable link of the course 65 | * @example 'https://sharable_link.com' 66 | */ 67 | sharable_link: string; 68 | 69 | /** 70 | * The Mentor of the course 71 | * @example ['John Doe'] 72 | */ 73 | mentor: string[]; 74 | 75 | /** 76 | * The schedule of the course 77 | * @example [] 78 | */ 79 | schedule: Schedule[]; 80 | 81 | /** 82 | * The schedule of the course 83 | * @example [] 84 | */ 85 | review: Review[]; 86 | 87 | /** 88 | * The number of enrollments of the course 89 | * @example 100001 90 | */ 91 | no_of_enrollments: number; 92 | 93 | /** 94 | * The Tag associated with the course 95 | * @example WEB_DEV 96 | */ 97 | tags: TagType[]; 98 | 99 | /** 100 | * The details of the course 101 | * @example 'The course gives a hands on learning experience on Rest APIs and Javascript' 102 | */ 103 | courseDetails: string; 104 | 105 | /** 106 | * The level associated with the course 107 | * @example BEGINNER 108 | */ 109 | courseLevel: courseLevelType; 110 | 111 | /** 112 | * The link/URL of the course 113 | * @example 'https://codeforcause.org/courses' 114 | */ 115 | courseThumbnail: string; 116 | 117 | /** 118 | * The link/URL of the course trailer 119 | * @example 'https://codeforcause.org/courseTrailer' 120 | */ 121 | courseTrailerUrl: string; 122 | 123 | /** 124 | * The assignments of the course 125 | * @example 'https://codeforcause.org/assignments/1' 126 | */ 127 | assignments: Assignment[]; 128 | 129 | /** 130 | * The discounted price of the course 131 | * @example 120 132 | */ 133 | crossPrice: number; 134 | 135 | /** 136 | * The short description of the course 137 | * @example 'Short description--' 138 | */ 139 | courseShortDescription: string; 140 | 141 | /** 142 | * The long description of the course 143 | * @example 'Long description--' 144 | */ 145 | courseLongDescription: string; 146 | 147 | /** 148 | * The rating of the course 149 | * @example 5 150 | */ 151 | rating: number; 152 | 153 | /** 154 | * The prerequisites of the course 155 | * @example ["HTML","CSS"] 156 | */ 157 | prerequisites: string[]; 158 | 159 | /** 160 | * The skills of the course 161 | * @example ["HTML","CSS"] 162 | */ 163 | skills: string[]; 164 | 165 | /** 166 | * what will one learn from the course 167 | * @example ["You will get to know about web technologies basics", "A good understanstanding of Html, css and JS", "You will learn about hooks and functional components"] 168 | */ 169 | whatYouWillLearn: string[]; 170 | 171 | /** 172 | * the certificate Url 173 | * @example "https://codeforcause.org/certificate" 174 | */ 175 | certificateUrl: string; 176 | 177 | /** 178 | * The boolean value to depict whether the course is upcoming or not 179 | * @example false 180 | */ 181 | isUpcoming: boolean; 182 | } 183 | 184 | export class ScheduleResponseBody { 185 | /** 186 | * Schedule Id 187 | * @example "6079cf782f9a2181bc7aadbf" 188 | */ 189 | id: string; 190 | 191 | /** 192 | * The chapter name of the schedule item 193 | * @example 'Rest Apis' 194 | */ 195 | 196 | chapterName: string; 197 | 198 | /** 199 | * The description of the schedule item 200 | * @example 'A session on REST API which is an application programming interface (API or web API) that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services.' 201 | */ 202 | 203 | description: string; 204 | 205 | /** 206 | * The time required to finish the scheduled item 207 | * @example '11 hours' 208 | */ 209 | 210 | time: string; 211 | 212 | /** 213 | * The lecture Number/Index among all lectures in the schedule 214 | * @example 8 215 | */ 216 | 217 | lectureNumber: number; 218 | } 219 | 220 | export class ReviewResponseBody { 221 | /** 222 | * Review Id 223 | * @example "6079cf782f9a2181bc7aadbf" 224 | */ 225 | id: string; 226 | 227 | /** 228 | * name of the reviewer 229 | * @example "John Doe" 230 | */ 231 | reviewerName: string; 232 | 233 | /** 234 | * The description of the Reviw 235 | * @example "The course was just fantastic" 236 | */ 237 | reviewDescription: string; 238 | 239 | /** 240 | * The type of Review 241 | * @example STUDENT 242 | */ 243 | occupation: ReviewType; 244 | 245 | /** 246 | * The number of stars given in the review 247 | * @example 5 248 | */ 249 | stars: number; 250 | } 251 | 252 | export class LectureResponseBody { 253 | /** 254 | * lecture Id 255 | * @example "6079cf782f9a2181bc7aadbf" 256 | */ 257 | id: string; 258 | 259 | /** 260 | * name of the lecture 261 | * @example "Play with pandas" 262 | */ 263 | lectureName: string; 264 | 265 | /** 266 | * The description of the lecture in the schedule 267 | * @example "The pandas library" 268 | */ 269 | description: string; 270 | 271 | /** 272 | * The length of the video 273 | * @example "5 minutes" 274 | */ 275 | time: string; 276 | 277 | /** 278 | * The url video 279 | * @example "https://codeforcause.org/video/1" 280 | */ 281 | lectureVideoUrl: string; 282 | } 283 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Edu Server 3 |

4 |

Product focuses on 100% education as well as upskilling developing countries and rural areas.

5 |

6 | App Version 7 | License 8 | 9 | chat 10 | 11 |

12 | 13 | ## Description 14 | 15 | Education platform is a product which will strive to enable 100% education and will help in upskilling developing countries and rural areas. 16 | Edu server is a backend application written using Nestjs, it provides api endpoints which are used by [Edu Client](https://github.com/codeforcauseorg/edu-client) mobile application. 17 | 18 | ### Table of Contents 19 | 20 | - [Tech Stack](#tech-stack) 21 | - [Setup and Run](#setup-run) 22 | - [Setup local repo](#setup-repo) 23 | - [Setup Firebase admin SDK](#setup-firebase) 24 | - [Setup remote](#setup-remote) 25 | - [Run app](#run-app) 26 | - [Run test](#run-test) 27 | - [API Documentation](#api-docs) 28 | - [Contributing](#contributing) 29 | - [Discord](#discord) 30 | - [Potential Maintainers](#maintainers) 31 | - [License](#license) 32 | 33 | 34 | ## ⚙️ Tech Stack 35 | 36 | * JavaScript/TypeScript 37 | * [NodeJs](https://nodejs.org/en/) 38 | * [NestJS](https://nestjs.com/) 39 | * [Docker](https://www.docker.com/) 40 | 41 | 42 | ## 🔨 Setup and Run 43 | 44 | [![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/21bb218956f8c4bbd471#?env%5Bedu-server%5D=W3sia2V5IjoidXJsIiwidmFsdWUiOiJsb2NhbGhvc3Q6NTAwMCIsImVuYWJsZWQiOnRydWV9XQ==) 45 | 46 | 47 | ### Setup local repo 48 | Let's setup the backend server on your local machine. 49 | 50 | ### 0. Prerequisites 51 | * Install [Node.js](http://nodejs.org) 52 | * Install Nest cli globally 53 | ``` 54 | npm i @nestjs/cli -g 55 | ``` 56 | 57 | ### 1. Fork repo 58 | Fork this repo to your GitHub account 59 | ![](https://i.ibb.co/bmyFtCg/forkeduserver.png) 60 | 61 | ### 2. Clone repo 62 | Clone the forked repo to your local machine 63 | ```bash 64 | git clone https://github.com//edu-server.git 65 | ``` 66 | Navigate to project directory 67 | ```bash 68 | cd edu-server 69 | ``` 70 | 71 | ### 3. Install Dependencies 72 | ```bash 73 | npm install 74 | ``` 75 | 76 | 77 | ### 4. Setup firebase admin SDK for development 78 | 79 | - To use the Firebase Admin SDK in the project, create a new firebase project using [firebase console](https://console.firebase.google.com/). 80 | - After creating a project, go to [project settings](https://console.firebase.google.com/project/_/settings/general/). 81 | - In project settings of your newly created project there will be tab called as "Service Accounts". 82 | - Click on it, then it will ask to create a service account, click to create a new service account. 83 | - After creating a service account, click on generate new private key it will automatically download a json file. 84 | - Copy the content of [.env.sample](.env.sample) into your newly created file `.env`. 85 | - Copy the credentials for firebase to use it as your environment variable as suggested in [.env.sample](.env.sample). 86 | 87 | 88 | 89 | ### 5. 📡 Setup remote 90 | 91 | 0. You will have to set up remote repositories for getting latest changes from original repository 92 | 1. Specify a new remote upstream repository that will be synced with the fork using follwoing command : 93 | ```bash 94 | $ git remote add upstream https://github.com/codeforcauseorg/edu-server.git 95 | ``` 96 | 97 | 2. Verify the new upstream repository you've specified for your fork using `git remote -v` 98 | ```console 99 | 100 | origin https://github.com//edu-server.git (fetch) 101 | origin https://github.com//edu-server.git (push) 102 | upstream https://github.com/codeforcauseorg/edu-server.git (fetch) 103 | upstream https://github.com/codeforcauseorg/edu-server.git (push) 104 | 105 | ``` 106 | 107 | Your application setup is successfully completed! 108 | 109 | ### Running the app 110 | 111 | ```bash 112 | # development 113 | $ npm run start 114 | 115 | # watch mode 116 | $ npm run start:dev 117 | 118 | # production mode 119 | $ npm run start:prod 120 | 121 | # fix linting 122 | $ npm run lint:fix 123 | ``` 124 | 125 | 126 | ### Running Tests 127 | 128 | ```bash 129 | # lint tests 130 | $ npm run lint 131 | 132 | # unit tests 133 | $ npm run test 134 | 135 | # e2e tests 136 | $ npm run test:e2e 137 | 138 | # test coverage 139 | $ npm run test:cov 140 | ``` 141 | 142 | ### 6. 📡 Setup database 143 | 144 | Install mongodb and nestjs/mongoose. MongoDB would be the Database for the project 145 | MongoDB is a source-available cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with optional schemas. 146 | 147 | https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ 148 | The above Url contains the detailed steps to install it locally 149 | 150 | 1)Download the installer. 151 | 2)Run the Installer and continue with the steps 152 | 3)Intsall mongodb compass also 153 | 154 | Instead of installing MongoDB , you can also setup a mongourl for yourself and work on the project. 155 | 156 | **OR** 157 | 158 | Create a Mongodb cluster from [Mongodb Cloud](https://www.mongodb.com/cloud/atlas). 159 | 160 | 161 | ### 7. 📡 Setup Docker 162 | 163 | Docker is a set of platform as a service products that use OS-level virtualization to deliver software in packages called containers 164 | 165 | 1).Install Docker (a devops tool) in your system 166 | https://docs.docker.com/engine/install/ 167 | 168 | 169 | 2).Setup the env variables and all and check if docker desktop is working correctly or not 170 | 171 | 3).Add a mongourl to your .env file by setting a mongocluster connection through mongoDB Atlas 172 | 173 | 4).Run the command docker-compose up 174 | Thsi will build the image and container for the project and you can see it running on your console 175 | Now you can use this to run the server for development purposes as well 176 | 177 | 178 | 179 | 180 | ## 📖 API Documentation 181 | 182 | API documentation of the server endpoints are available on `/api` endpoint, which are build using Swagger-UI. 183 | 184 | - Viewing Swagger docs locally : 185 | - Start the server using command `nest start` or `npx nest start`, To generate a full documented DTOs running nest cli is important [see here](https://docs.nestjs.com/openapi/cli-plugin), once the server start running, go to `http://localhost:5000/api`. 186 | 187 | - Viewing Swagger docs if the server is deployed/hosted : 188 | - Get the deployed URL, go to `http:///api`. 189 | 190 | 191 | ## Contributing 192 | 193 | Please read our [Code of Conduct](./CODE_OF_CONDUCT.md) and Reporting Guidelines for [Bug Report](.github/ISSUE_TEMPLATE/bug_report.md) and [Feature Request](.github/ISSUE_TEMPLATE/feature-request.md) 194 | 195 | 196 | ## 💬 Discord 197 | Connect with us on [Discord](https://discord.gg/dydQp2Q). 198 | 199 | 200 | ## 💻 Potential Maintainers 201 | 202 | [Anuj Garg](https://github.com/KeenWarrior)\ 203 | [Ganga Chaturvedi](https://github.com/GangaChatrvedi)\ 204 | [Kunal Kushwaha](https://github.com/kunal-kushwaha)\ 205 | [Abhishek Kumar](https://github.com/Abhishek-kumar09) 206 | 207 | ## ✨ Contributors 208 | 209 | This project exists thanks to all the people who contribute. 210 | 211 | 212 | 213 | 214 | 215 | 216 | ## 📜 Licence 217 | This software is open source, licensed under the [MIT License](LICENSE). 218 | -------------------------------------------------------------------------------- /src/modules/course/course.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | Put, 8 | Delete, 9 | Query, 10 | UseGuards, 11 | } from '@nestjs/common'; 12 | import { UpdateCourseDTO } from './dto/course-update.dto'; 13 | import { CourseService } from './course.service'; 14 | import { CreateCourseDto } from './dto/create-course.dto'; 15 | import { 16 | ApiCreatedResponse, 17 | ApiOkResponse, 18 | ApiOperation, 19 | ApiParam, 20 | ApiTags, 21 | } from '@nestjs/swagger'; 22 | import { Schema } from 'mongoose'; 23 | import { CreateScheduleDto } from './dto/create-schedule.dto'; 24 | import { UpdateScheduleDto } from './dto/update-schedule.dto'; 25 | import { CreateReviewDto } from './dto/create-review.dto'; 26 | import { UpdateReviewDto } from './dto/update-review.dto'; 27 | import { courseId, scheduleId } from './docUtils/course.paramdocs'; 28 | import responsedoc from './docUtils/apidoc'; 29 | import { GetCourseFilterDto } from './dto/course-filter.dto'; 30 | import { CreateLectureDto } from './dto/create-lecture.dto'; 31 | import { UpdateLectureDto } from './dto/update-lecture.dto'; 32 | import { RolesGuard } from '../../middleware/roles.guard'; 33 | import { Roles } from '../../middleware/role.decorator'; 34 | import { Role } from '../../roles/role.enum'; 35 | 36 | @ApiTags('Course') 37 | @Controller('course') 38 | @UseGuards(RolesGuard) 39 | export class CourseController { 40 | constructor(private courseService: CourseService) {} 41 | 42 | @Get('/cards/all') 43 | @ApiOperation({ 44 | summary: 'get brief information on cards for all courses initially', 45 | }) 46 | @ApiOkResponse(responsedoc.getAllCourses) 47 | async getBreifAllCourses(@Query('skip') skip: string) { 48 | return await this.courseService.getBreifAllCourses(skip); 49 | } 50 | 51 | @Get('/all/query') 52 | @ApiOperation({ summary: 'fetch query results for search string' }) 53 | @ApiOkResponse(responsedoc.getAllQueryCourses) 54 | async getSearchResults(@Query() filterDto: GetCourseFilterDto) { 55 | return await this.courseService.getSearchResults(filterDto); 56 | } 57 | 58 | @Get('/all') 59 | @ApiOperation({ summary: 'get all courses' }) 60 | @ApiOkResponse(responsedoc.getAllCourses) 61 | async getAllCourses(@Query('skip') skip: string) { 62 | return await this.courseService.getAllCourses(skip); 63 | } 64 | 65 | @Get('/:courseId') 66 | @ApiParam(courseId) 67 | @ApiOperation({ summary: 'get selected course by Id' }) 68 | @ApiOkResponse(responsedoc.getSelectedCourses) 69 | async getSelectedCourse(@Param('courseId') courseId: Schema.Types.ObjectId) { 70 | return await this.courseService.findCourseById(courseId); 71 | } 72 | 73 | @Post('/create') 74 | @Roles(Role.ADMIN) 75 | @ApiOperation({ summary: 'add a Course' }) 76 | @ApiCreatedResponse(responsedoc.addCourse) 77 | async addCourse(@Body() createCourseDto: CreateCourseDto) { 78 | return await this.courseService.addCourse(createCourseDto); 79 | } 80 | 81 | @Put('/edit/:courseId') 82 | @Roles(Role.ADMIN) 83 | @ApiParam(courseId) 84 | @ApiOperation({ summary: 'update a course by Id' }) 85 | @ApiOkResponse(responsedoc.updateCourse) 86 | async updateCourse( 87 | @Param('courseId') courseId: Schema.Types.ObjectId, 88 | @Body() updateCourseDTO: UpdateCourseDTO, 89 | ) { 90 | return await this.courseService.editCourse(courseId, updateCourseDTO); 91 | } 92 | 93 | @Delete('delete/:courseId') 94 | @Roles(Role.ADMIN) 95 | @ApiParam(courseId) 96 | @ApiOperation({ summary: 'delete a course by Id' }) 97 | @ApiOkResponse(responsedoc.deleteCourse) 98 | async deleteCourse(@Param('courseId') courseId: Schema.Types.ObjectId) { 99 | return await this.courseService.deleteCourse(courseId); 100 | } 101 | 102 | @Post('/schedule/:courseId') 103 | @Roles(Role.ADMIN) 104 | @ApiOperation({ summary: 'Create a Schedule' }) 105 | @ApiCreatedResponse(responsedoc.addScheduleCourse) 106 | @ApiParam(courseId) 107 | async addScheduleCourse( 108 | @Param('courseId') courseId: Schema.Types.ObjectId, 109 | @Body() createScheduleDto: CreateScheduleDto, 110 | ) { 111 | return await this.courseService.addScheduleCourse( 112 | courseId, 113 | createScheduleDto, 114 | ); 115 | } 116 | 117 | @Put('/schedule/:courseId/:scheduleId') 118 | @Roles(Role.ADMIN) 119 | @ApiParam(courseId) 120 | @ApiOperation({ summary: 'update a Schedule by Id' }) 121 | @ApiOkResponse(responsedoc.updateScheduleCourse) 122 | async updateScheduleCourse( 123 | @Param('courseId') courseId: Schema.Types.ObjectId, 124 | @Param('scheduleId') scheduleId: Schema.Types.ObjectId, 125 | @Body() updateScheduleDto: UpdateScheduleDto, 126 | ) { 127 | return await this.courseService.updateScheduleCourse( 128 | courseId, 129 | scheduleId, 130 | updateScheduleDto, 131 | ); 132 | } 133 | 134 | @Delete('/schedule/:courseId/:scheduleId') 135 | @Roles(Role.ADMIN) 136 | @ApiParam(courseId) 137 | @ApiOperation({ summary: 'Delete a schedule by Id' }) 138 | @ApiOkResponse(responsedoc.deleteScheduleCourse) 139 | async deleteScheduleCourse( 140 | @Param('courseId') courseId: Schema.Types.ObjectId, 141 | @Param('scheduleId') scheduleId: Schema.Types.ObjectId, 142 | ) { 143 | return await this.courseService.deleteScheduleCourse(courseId, scheduleId); 144 | } 145 | 146 | @Post('/review/:courseId') 147 | @ApiParam(courseId) 148 | @ApiOperation({ summary: 'Create a Review' }) 149 | @ApiCreatedResponse(responsedoc.addReview) 150 | async addReview( 151 | @Param('courseId') courseId: Schema.Types.ObjectId, 152 | @Body() createReviewDto: CreateReviewDto, 153 | ) { 154 | return await this.courseService.addReview(courseId, createReviewDto); 155 | } 156 | 157 | @Put('/review/:courseId/:reviewId') 158 | @ApiParam(courseId) 159 | @ApiOperation({ summary: 'update a Review by Id' }) 160 | @ApiOkResponse(responsedoc.updateReview) 161 | async updateReview( 162 | @Param('courseId') courseId: Schema.Types.ObjectId, 163 | @Param('reviewId') reviewId: Schema.Types.ObjectId, 164 | @Body() updateReviewDto: UpdateReviewDto, 165 | ) { 166 | return await this.courseService.updateReview( 167 | courseId, 168 | reviewId, 169 | updateReviewDto, 170 | ); 171 | } 172 | 173 | @Delete('/review/:courseId/:reviewId') 174 | @ApiParam(courseId) 175 | @ApiOperation({ summary: 'Delete a Review by Id' }) 176 | @ApiOkResponse(responsedoc.deleteReview) 177 | async deleteReview( 178 | @Param('courseId') courseId: Schema.Types.ObjectId, 179 | @Param('reviewId') reviewId: Schema.Types.ObjectId, 180 | ) { 181 | return await this.courseService.deleteReview(courseId, reviewId); 182 | } 183 | 184 | @Post('/newLecture/:scheduleId') 185 | @Roles(Role.ADMIN) 186 | @ApiParam(scheduleId) 187 | @ApiOperation({ summary: 'Create a Lecture' }) 188 | @ApiCreatedResponse(responsedoc.addLecture) 189 | async addLecture( 190 | @Param('scheduleId') scheduleId: Schema.Types.ObjectId, 191 | @Body() createReviewDto: CreateLectureDto, 192 | ) { 193 | return await this.courseService.addLecture(scheduleId, createReviewDto); 194 | } 195 | 196 | @Put('/updateLecture/:scheduleId/:lectureId') 197 | @Roles(Role.ADMIN) 198 | @ApiParam(scheduleId) 199 | @ApiOperation({ summary: 'update a Lecture by Id' }) 200 | @ApiOkResponse(responsedoc.updateLecture) 201 | async updateLecture( 202 | @Param('scheduleId') scheduleId: Schema.Types.ObjectId, 203 | @Param('lectureId') lectureId: Schema.Types.ObjectId, 204 | @Body() updateLectureDto: UpdateLectureDto, 205 | ) { 206 | return await this.courseService.updateLecture( 207 | scheduleId, 208 | lectureId, 209 | updateLectureDto, 210 | ); 211 | } 212 | 213 | @Delete('/deleteLecture/:scheduleId/:lectureId') 214 | @Roles(Role.ADMIN) 215 | @ApiParam(scheduleId) 216 | @ApiOperation({ summary: 'Delete a Lecture by Id' }) 217 | @ApiOkResponse(responsedoc.deleteLecture) 218 | async deleteLecture( 219 | @Param('scheduleId') scheduleId: Schema.Types.ObjectId, 220 | @Param('lectureId') lectureId: Schema.Types.ObjectId, 221 | ) { 222 | return await this.courseService.deleteLecture(scheduleId, lectureId); 223 | } 224 | 225 | @Post('/mentor/:courseId/:mentorId') 226 | @Roles(Role.ADMIN) 227 | @ApiOperation({ summary: 'Add a mentor to course' }) 228 | @ApiCreatedResponse(responsedoc.addMentorToCourse) 229 | @ApiParam(courseId) 230 | async addMentorToCourse( 231 | @Param('courseId') courseId: Schema.Types.ObjectId, 232 | @Param('mentorId') mentorId: Schema.Types.ObjectId, 233 | ) { 234 | return await this.courseService.addMentorToCourse(courseId, mentorId); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/modules/doubt/doubt.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Inject, 3 | Injectable, 4 | InternalServerErrorException, 5 | NotFoundException, 6 | Scope, 7 | } from '@nestjs/common'; 8 | import { InjectModel } from '@nestjs/mongoose'; 9 | import { Model, Schema } from 'mongoose'; 10 | import { CreateDoubtDto } from './dto/create-doubt.dto'; 11 | import { DoubtDocument as Doubt } from './schema/doubt.schema'; 12 | import { Course } from '../course/schema/course.schema'; 13 | import { UpdateDoubtDto } from './dto/update-doubt.dto'; 14 | import { DoubtAnswer } from './schema/doubtAnswer.schema'; 15 | import { UpdateDoubtAnswerDto } from './dto/update-doubtAnswer.dto'; 16 | import { CreateDoubtAnswerDto } from './dto/create-doubtAnswer.dto'; 17 | import { User } from '../user/schema/user.schema'; 18 | import { REQUEST } from '@nestjs/core'; 19 | import { Request } from 'express'; 20 | 21 | @Injectable({ scope: Scope.REQUEST }) 22 | export class DoubtService { 23 | constructor( 24 | @InjectModel('Doubt') private readonly DoubtModel: Model, 25 | @InjectModel('Course') private readonly CourseModel: Model, 26 | @InjectModel('User') private readonly UserModel: Model, 27 | @InjectModel('DoubtAnswer') 28 | private readonly DoubtAnswerModel: Model, 29 | @Inject(REQUEST) private readonly request: Request, 30 | ) {} 31 | 32 | // fetch all Doubts 33 | async getAllDoubts(skip: string) { 34 | try { 35 | const skipNum = parseInt(skip, 10); 36 | const doubts = await this.DoubtModel.find( 37 | {}, 38 | {}, 39 | { skip: skipNum, limit: 10 }, 40 | ) 41 | .populate('answers') 42 | .lean(); 43 | return doubts; 44 | } catch (e) { 45 | throw new InternalServerErrorException(e); 46 | } 47 | } 48 | 49 | // Get a single Doubt 50 | async getDoubtById(doubtId: Schema.Types.ObjectId) { 51 | try { 52 | const doubt = await this.DoubtModel.findById(doubtId) 53 | .populate('answers') 54 | .lean(); 55 | 56 | if (doubt) { 57 | return doubt; 58 | } 59 | } catch (e) { 60 | throw new InternalServerErrorException(e); 61 | } 62 | throw new NotFoundException('Error, doubt not found'); 63 | } 64 | 65 | // add a new doubt 66 | async addNewDoubt( 67 | courseId: Schema.Types.ObjectId, 68 | createDoubtDto: CreateDoubtDto, 69 | ) { 70 | try { 71 | const user = await this.UserModel.findOne({ 72 | email: this.request['user']['email'], 73 | }); 74 | const course = await this.CourseModel.findById(courseId); 75 | if (course) { 76 | const doubtToBeCreated = { 77 | ...createDoubtDto, 78 | photoUrl: user.photoUrl, 79 | }; 80 | const newDoubt = await new this.DoubtModel(doubtToBeCreated).save(); 81 | course.doubts.push(newDoubt); 82 | await course.save(); 83 | return newDoubt.populate('answers'); 84 | } else { 85 | throw new NotFoundException( 86 | 'The course id is invalid or the course no longer exists', 87 | ); 88 | } 89 | } catch (e) { 90 | throw new InternalServerErrorException(e); 91 | } 92 | } 93 | 94 | // edit the doubt by Id 95 | async editDoubt( 96 | courseId: Schema.Types.ObjectId, 97 | doubtId: Schema.Types.ObjectId, 98 | updateDoubtDto: UpdateDoubtDto, 99 | ) { 100 | try { 101 | const course = await this.CourseModel.findById(courseId); 102 | if (course) { 103 | let updatedDoubt = null; 104 | updatedDoubt = await this.DoubtModel.findByIdAndUpdate( 105 | doubtId, 106 | updateDoubtDto, 107 | { new: true }, 108 | ).lean(); 109 | if (updatedDoubt) { 110 | return updatedDoubt; 111 | } else { 112 | throw new NotFoundException( 113 | 'The doubt id is invalid or the doubt no longer exists', 114 | ); 115 | } 116 | } else { 117 | throw new NotFoundException( 118 | 'The course id is invalid or the course no longer exists', 119 | ); 120 | } 121 | } catch (e) { 122 | throw new InternalServerErrorException(e); 123 | } 124 | } 125 | 126 | // Delete a doubt by Id 127 | async deleteDoubt( 128 | courseId: Schema.Types.ObjectId, 129 | doubtId: Schema.Types.ObjectId, 130 | ): Promise { 131 | try { 132 | const course = await this.CourseModel.findById(courseId); 133 | if (course) { 134 | let deletedDoubt = null; 135 | deletedDoubt = await this.DoubtModel.findByIdAndRemove(doubtId) 136 | .populate('answers') 137 | .lean(); 138 | if (deletedDoubt) { 139 | return deletedDoubt; 140 | } else { 141 | throw new NotFoundException( 142 | 'The doubt id is invalid or the doubt no longer exists', 143 | ); 144 | } 145 | } else { 146 | throw new NotFoundException( 147 | 'The course id is invalid or the course no longer exists', 148 | ); 149 | } 150 | } catch (e) { 151 | throw new InternalServerErrorException(e); 152 | } 153 | } 154 | 155 | // add a new doubtAnswer 156 | async addNewDoubtAnswer( 157 | doubtId: Schema.Types.ObjectId, 158 | createDoubtAnswerDto: CreateDoubtAnswerDto, 159 | ) { 160 | try { 161 | const doubt = await this.DoubtModel.findById(doubtId); 162 | const user = await this.UserModel.findOne({ 163 | email: this.request['user']['email'], 164 | }); 165 | if (doubt) { 166 | const doubtAnswerToBeCreated = { 167 | ...createDoubtAnswerDto, 168 | photoUrl: user.photoUrl, 169 | }; 170 | const newDoubtAnswer = await new this.DoubtAnswerModel( 171 | doubtAnswerToBeCreated, 172 | ).save(); 173 | doubt.answers.push(newDoubtAnswer); 174 | await doubt.save(); 175 | return newDoubtAnswer; 176 | } else { 177 | throw new NotFoundException( 178 | 'The doubt id is invalid or the doubt no longer exists', 179 | ); 180 | } 181 | } catch (e) { 182 | throw new InternalServerErrorException(e); 183 | } 184 | } 185 | 186 | // edit the doubtAnswer by Id 187 | async editDoubtAnswer( 188 | doubtId: Schema.Types.ObjectId, 189 | doubtAnswerId: Schema.Types.ObjectId, 190 | updateDoubtAnswerDto: UpdateDoubtAnswerDto, 191 | ) { 192 | try { 193 | const doubt = await this.DoubtModel.findById(doubtId); 194 | if (doubt) { 195 | let updatedDoubt = null; 196 | updatedDoubt = await this.DoubtAnswerModel.findByIdAndUpdate( 197 | doubtAnswerId, 198 | updateDoubtAnswerDto, 199 | { new: true }, 200 | ); 201 | if (updatedDoubt) { 202 | return updatedDoubt; 203 | } else { 204 | throw new NotFoundException( 205 | 'The doubtAnswerId id is invalid or the doubtAnswerId no longer exists', 206 | ); 207 | } 208 | } else { 209 | throw new NotFoundException( 210 | 'The doubt id is invalid or the doubt no longer exists', 211 | ); 212 | } 213 | } catch (e) { 214 | throw new InternalServerErrorException(e); 215 | } 216 | } 217 | 218 | // Delete a doubtAnswer by Id 219 | async deleteDoubtAnswer( 220 | doubtId: Schema.Types.ObjectId, 221 | doubtAnswerId: Schema.Types.ObjectId, 222 | ): Promise { 223 | try { 224 | const doubt = await this.DoubtModel.findById(doubtId); 225 | if (doubt) { 226 | let deletedDoubt = null; 227 | deletedDoubt = await this.DoubtAnswerModel.findByIdAndRemove( 228 | doubtAnswerId, 229 | ); 230 | if (deletedDoubt) { 231 | return deletedDoubt; 232 | } else { 233 | throw new NotFoundException( 234 | 'The doubtAnswerId id is invalid or the doubtAnswerId no longer exists', 235 | ); 236 | } 237 | } else { 238 | throw new NotFoundException( 239 | 'The doubt id is invalid or the doubt no longer exists', 240 | ); 241 | } 242 | } catch (e) { 243 | throw new InternalServerErrorException(e); 244 | } 245 | } 246 | 247 | // get doubts for selected courses 248 | async findDoubtsForSelectedCourse( 249 | courseId: Schema.Types.ObjectId, 250 | ): Promise { 251 | try { 252 | const Course = await this.CourseModel.findById(courseId) 253 | .populate({ 254 | path: 'doubts', 255 | model: 'Doubt', 256 | populate: { 257 | path: 'answers', 258 | model: 'DoubtAnswer', 259 | }, 260 | }) 261 | .exec(); 262 | if (Course) { 263 | const { doubts } = Course; 264 | return doubts; 265 | } else { 266 | throw new NotFoundException('course not found'); 267 | } 268 | } catch (e) { 269 | throw new InternalServerErrorException(e); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/modules/course/course.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CourseController } from './course.controller'; 3 | import { CourseService } from './course.service'; 4 | import * as mongoose from 'mongoose'; 5 | import { UpdateCourseDTO } from './dto/course-update.dto'; 6 | import { CreateCourseDto } from './dto/create-course.dto'; 7 | import { TagType } from './course-tag.enum'; 8 | import { courseLevelType } from './courseLevel.enum'; 9 | const mockCourse = { 10 | schedule: [], 11 | mentor: [], 12 | active: false, 13 | name: 'devesh K', 14 | originalPrice: 0, 15 | no_of_enrollments: 100, 16 | couponCode: 'CFC424', 17 | video_num: 0, 18 | duration: '11.5 hours', 19 | start_date: '2020-02-05T06:35:22.000Z', 20 | end_date: '2020-02-05T06:35:22.000Z', 21 | sharable_link: '88900xyz.com', 22 | id: '60c5eafba5940a4964d5ea96', 23 | tags: TagType.WEB_DEV, 24 | courseDetails: 25 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 26 | courseLevel: courseLevelType.BEGINNER, 27 | courseThumbnail: 'https://codeforcause.org/courses', 28 | courseTrailerUrl: 'https://codeforcause.org/courseTrailer', 29 | crossPrice: 120, 30 | courseShortDescription: 'Short description--', 31 | courseLongDescription: 'Long description--', 32 | rating: 5, 33 | prerequisites: ['HTML', 'CSS'], 34 | skills: ['HTML', 'CSS'], 35 | whatYouWillLearn: ['HTML', 'CSS'], 36 | certificateUrl: 'https://codeforcause.org/certificate', 37 | isUpcoming: false, 38 | }; 39 | 40 | describe('CourseController', () => { 41 | let controller: CourseController; 42 | let service: CourseService; 43 | 44 | const mockCoursevalue = { 45 | getAllCourses: jest.fn().mockResolvedValue([mockCourse]), 46 | findCourseById: jest 47 | .fn() 48 | .mockImplementation((id: string) => ({ ...mockCourse, id })), 49 | editCourse: jest.fn().mockImplementation((uid, body) => ({ 50 | ...mockCourse, 51 | id: uid, 52 | ...body, 53 | })), 54 | addCourse: jest.fn().mockResolvedValue([mockCourse]), 55 | updateCourse: jest.fn().mockResolvedValue([mockCourse]), 56 | deleteCourse: jest.fn().mockResolvedValue([mockCourse]), 57 | }; 58 | 59 | beforeEach(async () => { 60 | const module: TestingModule = await Test.createTestingModule({ 61 | controllers: [CourseController], 62 | providers: [ 63 | { 64 | provide: CourseService, 65 | useValue: mockCoursevalue, 66 | }, 67 | ], 68 | }).compile(); 69 | 70 | controller = module.get(CourseController); 71 | service = module.get(CourseService); 72 | }); 73 | 74 | // check if controller is defined 75 | it('should be defined', () => { 76 | expect(controller).toBeDefined(); 77 | }); 78 | 79 | describe('Course', () => { 80 | // course creation 81 | it('should be created', async () => { 82 | await expect(controller.getAllCourses('0')).resolves.toEqual([ 83 | mockCourse, 84 | ]); 85 | }); 86 | 87 | // course found by id 88 | it('should be found by ID', async () => { 89 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 90 | await expect(controller.getSelectedCourse(id)).resolves.toEqual({ 91 | ...mockCourse, 92 | id, 93 | }); 94 | expect(service.findCourseById).toHaveBeenCalledWith(id); 95 | }); 96 | 97 | // retrieved value not equal as id if different/does not exist 98 | it('should not be equal to the returned value if ID does not exist', async () => { 99 | const id = new mongoose.Schema.Types.ObjectId('18', 0, 'riep'); 100 | const idFix = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 101 | await expect(controller.getSelectedCourse(id)).resolves.not.toEqual({ 102 | ...mockCourse, 103 | idFix, 104 | }); 105 | expect(service.findCourseById).toHaveBeenCalledWith(id); 106 | }); 107 | 108 | it('should be created', async () => { 109 | const dto: CreateCourseDto = { 110 | mentor: [], 111 | active: false, 112 | name: 'devesh K', 113 | originalPrice: 0, 114 | couponCode: 'CFC424', 115 | video_num: 0, 116 | duration: '11.5 hours', 117 | sharable_link: '88900xyz.com', 118 | start_date: new Date(), 119 | end_date: new Date(), 120 | tags: [], 121 | courseDetails: 122 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 123 | courseLevel: courseLevelType.BEGINNER, 124 | courseThumbnail: 'https://codeforcause.org/courses', 125 | courseTrailerUrl: 'https://codeforcause.org/courseTrailer', 126 | crossPrice: 120, 127 | courseShortDescription: 'Short description--', 128 | courseLongDescription: 'Long description--', 129 | rating: 5, 130 | prerequisites: ['HTML', 'CSS'], 131 | skills: ['HTML', 'CSS'], 132 | whatYouWillLearn: ['HTML', 'CSS'], 133 | certificateUrl: 'https://codeforcause.org/certificate', 134 | isUpcoming: false, 135 | }; 136 | await expect(controller.addCourse(dto)).resolves.not.toBeNull(); 137 | expect(service.addCourse).toHaveBeenCalledWith(dto); 138 | }); 139 | 140 | // should be updated 141 | it('should be updated', async () => { 142 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 143 | const dto: UpdateCourseDTO = { 144 | active: false, 145 | name: 'devesh K', 146 | originalPrice: 0, 147 | couponCode: 'CFC424', 148 | video_num: 0, 149 | duration: '11.5 hours', 150 | sharable_link: '88900xyz.com', 151 | no_of_enrollments: 100, 152 | start_date: new Date(), 153 | end_date: new Date(), 154 | tags: [], 155 | courseDetails: 156 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 157 | courseLevel: courseLevelType.BEGINNER, 158 | courseThumbnail: 'https://codeforcause.org/courses', 159 | courseTrailerUrl: 'https://codeforcause.org/courseTrailer', 160 | crossPrice: 120, 161 | courseShortDescription: 'Short description--', 162 | courseLongDescription: 'Long description--', 163 | rating: 5, 164 | prerequisites: ['HTML', 'CSS'], 165 | skills: ['HTML', 'CSS'], 166 | whatYouWillLearn: ['HTML', 'CSS'], 167 | certificateUrl: 'https://codeforcause.org/certificate', 168 | isUpcoming: false, 169 | }; 170 | await expect(controller.updateCourse(id, dto)).resolves.toEqual({ 171 | id, 172 | mentor: [], 173 | ...dto, 174 | schedule: [], 175 | }); 176 | expect(service.editCourse).toHaveBeenCalledWith(id, dto); 177 | }); 178 | 179 | // retrieved value not equal as id if different/does not exist 180 | it('should not be updated and be equal to the returned value if ID does not exist', async () => { 181 | const id = new mongoose.Schema.Types.ObjectId('18', 0, 'riep'); 182 | const idFix = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 183 | const dto: UpdateCourseDTO = { 184 | active: false, 185 | name: 'devesh K', 186 | originalPrice: 0, 187 | couponCode: 'CFC424', 188 | video_num: 0, 189 | duration: '11.5 hours', 190 | sharable_link: '88900xyz.com', 191 | no_of_enrollments: 100, 192 | start_date: new Date(), 193 | end_date: new Date(), 194 | tags: [], 195 | courseDetails: 196 | 'The course gives a hands on learning experience on Rest APIs and Javascript', 197 | courseLevel: courseLevelType.BEGINNER, 198 | courseThumbnail: 'https://codeforcause.org/courses', 199 | courseTrailerUrl: 'https://codeforcause.org/courseTrailer', 200 | crossPrice: 120, 201 | courseShortDescription: 'Short description--', 202 | courseLongDescription: 'Long description--', 203 | rating: 5, 204 | prerequisites: ['HTML', 'CSS'], 205 | skills: ['HTML', 'CSS'], 206 | whatYouWillLearn: ['HTML', 'CSS'], 207 | certificateUrl: 'https://codeforcause.org/certificate', 208 | isUpcoming: false, 209 | }; 210 | await expect(controller.updateCourse(id, dto)).resolves.not.toEqual({ 211 | ...mockCourse, 212 | idFix, 213 | }); 214 | expect(service.editCourse).toHaveBeenCalledWith(id, dto); 215 | }); 216 | 217 | // shpuld be deleted 218 | it('should be deleted', async () => { 219 | const id = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 220 | await expect(controller.deleteCourse(id)).resolves.toEqual([ 221 | { 222 | ...mockCourse, 223 | }, 224 | ]); 225 | expect(service.deleteCourse).toHaveBeenCalledWith(id); 226 | }); 227 | }); 228 | 229 | // retrieved value not equal as id if different/does not exist 230 | it('should not be deleted and be equal to the returned value if ID does not exist', async () => { 231 | const id = new mongoose.Schema.Types.ObjectId('18', 0, 'riep'); 232 | const idFix = new mongoose.Schema.Types.ObjectId('22', 0, 'rtex'); 233 | await expect(controller.deleteCourse(id)).resolves.not.toEqual({ 234 | ...mockCourse, 235 | idFix, 236 | }); 237 | expect(service.deleteCourse).toHaveBeenCalledWith(id); 238 | }); 239 | }); 240 | --------------------------------------------------------------------------------