├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── backend ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Procfile ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── auth │ │ ├── auth.controller.spec.ts │ │ ├── auth.controller.ts │ │ ├── auth.module.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── dto │ │ │ ├── create-user.dto.ts │ │ │ └── login-user.dto.ts │ │ ├── get-user.decorator.ts │ │ ├── jwt-payload.interface.ts │ │ ├── jwt.strategy.ts │ │ ├── user.repository.ts │ │ ├── user.schema.ts │ │ └── utility-methods.ts │ ├── config │ │ └── configuration.ts │ ├── main.ts │ ├── migration │ │ ├── data.ts │ │ ├── schema │ │ │ ├── alltopicquestions.schema.ts │ │ │ └── user.schema.ts │ │ └── services │ │ │ └── migration.service.ts │ ├── progress │ │ ├── progress.controller.spec.ts │ │ ├── progress.controller.ts │ │ ├── progress.module.ts │ │ ├── progress.schema.ts │ │ ├── progress.service.spec.ts │ │ └── progress.service.ts │ ├── questions │ │ ├── dto │ │ │ ├── base-question.dto.ts │ │ │ ├── create-question.dto.ts │ │ │ └── update-question.dto.ts │ │ ├── problems │ │ │ ├── dto │ │ │ │ ├── base-problem.dto.ts │ │ │ │ ├── create-problem.dto.ts │ │ │ │ └── update-problem.dto.ts │ │ │ ├── problem.schema.ts │ │ │ ├── problems.controller.spec.ts │ │ │ ├── problems.controller.ts │ │ │ ├── problems.repository.ts │ │ │ ├── problems.service.spec.ts │ │ │ ├── problems.service.ts │ │ │ └── voting.schema.ts │ │ ├── question.repository.ts │ │ ├── question.schema.ts │ │ ├── questions.controller.spec.ts │ │ ├── questions.controller.ts │ │ ├── questions.module.ts │ │ ├── questions.service.spec.ts │ │ └── questions.service.ts │ ├── shared │ │ ├── base.schema.ts │ │ └── utility.methods.ts │ └── solution │ │ ├── dto │ │ ├── base-solution.dto.ts │ │ ├── create-solution.dto.ts │ │ └── update-solution.dto.ts │ │ ├── solution.controller.spec.ts │ │ ├── solution.controller.ts │ │ ├── solution.module.ts │ │ ├── solution.repository.ts │ │ ├── solution.schema.ts │ │ ├── solution.service.spec.ts │ │ └── solution.service.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json ├── client ├── .eslintrc.json ├── .gitignore ├── index.html ├── netlify.toml ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── App.css │ ├── App.tsx │ ├── Backend │ │ ├── db-store │ │ │ └── data.ts │ │ └── model │ │ │ └── Question-model.ts │ ├── actions │ │ └── index.ts │ ├── assets │ │ ├── design-style.css │ │ ├── icons.tsx │ │ ├── svg.tsx │ │ └── tailwind.css │ ├── components │ │ ├── Alert │ │ │ ├── Error.tsx │ │ │ ├── Success.tsx │ │ │ └── index.ts │ │ ├── Auth │ │ │ ├── Account-Reset.tsx │ │ │ ├── Login.tsx │ │ │ ├── index.tsx │ │ │ └── indexedDB.ts │ │ ├── Breadcums │ │ │ └── index.tsx │ │ ├── Category │ │ │ ├── CategoryNavbar.tsx │ │ │ ├── index.tsx │ │ │ └── useDragDrop.hook.ts │ │ ├── Container │ │ │ └── index.tsx │ │ ├── Footer │ │ │ └── index.tsx │ │ ├── Header │ │ │ └── index.tsx │ │ ├── Home │ │ │ └── index.tsx │ │ ├── HomeRoot │ │ │ └── index.tsx │ │ ├── Loader │ │ │ ├── index.tsx │ │ │ └── style.css │ │ ├── Pill │ │ │ └── index.tsx │ │ ├── QuestionStatCard │ │ │ ├── hook.ts │ │ │ └── index.tsx │ │ ├── QuestionTopicCard │ │ │ ├── index.tsx │ │ │ └── topic-card.tsx │ │ ├── Table │ │ │ ├── Tbody.tsx │ │ │ ├── Thead.tsx │ │ │ └── index.tsx │ │ └── UploadCode │ │ │ ├── hook.ts │ │ │ └── index.tsx │ ├── context │ │ ├── AuthContext │ │ │ ├── hooks.ts │ │ │ └── index.tsx │ │ ├── CategoryContext │ │ │ ├── hooks.ts │ │ │ └── index.tsx │ │ └── QuestionContext │ │ │ ├── hooks.ts │ │ │ └── index.tsx │ ├── env │ │ └── index.ts │ ├── favicon.svg │ ├── index.css │ ├── interfaces │ │ └── index.ts │ ├── logo.svg │ ├── main.tsx │ ├── reducer │ │ ├── action-type.ts │ │ ├── auth.reducer.ts │ │ ├── category-filter.reducer.ts │ │ └── question-data.context.reducer.ts │ ├── routes │ │ └── index.ts │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json └── vite.config.ts ├── frontend ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── README.md ├── angular.json ├── karma.conf.js ├── netlify.toml ├── ng-tailwind.js ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth-routing.module.ts │ │ │ ├── auth.guard.spec.ts │ │ │ ├── auth.guard.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── login │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.spec.ts │ │ │ │ └── login.component.ts │ │ │ ├── reset-password │ │ │ │ ├── reset-password.component.css │ │ │ │ ├── reset-password.component.html │ │ │ │ ├── reset-password.component.spec.ts │ │ │ │ └── reset-password.component.ts │ │ │ └── signup │ │ │ │ ├── signup.component.css │ │ │ │ ├── signup.component.html │ │ │ │ ├── signup.component.spec.ts │ │ │ │ └── signup.component.ts │ │ ├── cms │ │ │ ├── auth │ │ │ │ ├── auth.component.css │ │ │ │ ├── auth.component.html │ │ │ │ ├── auth.component.spec.ts │ │ │ │ └── auth.component.ts │ │ │ ├── cms-routing.module.ts │ │ │ ├── cms.component.css │ │ │ ├── cms.component.html │ │ │ ├── cms.component.spec.ts │ │ │ ├── cms.component.ts │ │ │ ├── cms.module.ts │ │ │ ├── dashboard │ │ │ │ ├── dashboard.component.css │ │ │ │ ├── dashboard.component.html │ │ │ │ ├── dashboard.component.spec.ts │ │ │ │ ├── dashboard.component.ts │ │ │ │ └── problem-info │ │ │ │ │ ├── modify-dialog │ │ │ │ │ ├── modify-dialog.component.css │ │ │ │ │ ├── modify-dialog.component.html │ │ │ │ │ ├── modify-dialog.component.spec.ts │ │ │ │ │ └── modify-dialog.component.ts │ │ │ │ │ ├── problem-info.component.css │ │ │ │ │ ├── problem-info.component.html │ │ │ │ │ ├── problem-info.component.spec.ts │ │ │ │ │ └── problem-info.component.ts │ │ │ ├── services │ │ │ │ ├── all-problems-interface.ts │ │ │ │ ├── cms.service.spec.ts │ │ │ │ └── cms.service.ts │ │ │ └── shared │ │ │ │ ├── cms-auth.guard.spec.ts │ │ │ │ ├── cms-auth.guard.ts │ │ │ │ ├── loader.interceptor.ts │ │ │ │ └── markdown-to-html.pipe.ts │ │ ├── dashboard │ │ │ ├── dashboard-routing.module.ts │ │ │ ├── dashboard.component.css │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.spec.ts │ │ │ ├── dashboard.component.ts │ │ │ ├── dashboard.module.ts │ │ │ ├── home │ │ │ │ ├── home.component.css │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.spec.ts │ │ │ │ ├── home.component.ts │ │ │ │ └── progress-card │ │ │ │ │ ├── progress-card.component.css │ │ │ │ │ ├── progress-card.component.html │ │ │ │ │ ├── progress-card.component.spec.ts │ │ │ │ │ └── progress-card.component.ts │ │ │ ├── interfaces │ │ │ │ ├── problem-information.interface.ts │ │ │ │ ├── progress-history.interface.ts │ │ │ │ └── questionsbytopic.interface.ts │ │ │ ├── problem-info │ │ │ │ ├── problem-info.component.css │ │ │ │ ├── problem-info.component.html │ │ │ │ ├── problem-info.component.spec.ts │ │ │ │ └── problem-info.component.ts │ │ │ ├── services │ │ │ │ ├── dashboard.service.spec.ts │ │ │ │ └── dashboard.service.ts │ │ │ ├── shared │ │ │ │ ├── back-button.directive.spec.ts │ │ │ │ ├── back-button.directive.ts │ │ │ │ ├── markdown-to-html.pipe.spec.ts │ │ │ │ └── markdown-to-html.pipe.ts │ │ │ └── topic-board │ │ │ │ ├── topic-board.component.css │ │ │ │ ├── topic-board.component.html │ │ │ │ ├── topic-board.component.spec.ts │ │ │ │ └── topic-board.component.ts │ │ ├── material │ │ │ └── material.module.ts │ │ └── shared │ │ │ ├── jwt-token.interceptor.spec.ts │ │ │ ├── jwt-token.interceptor.ts │ │ │ ├── loader.interceptor.spec.ts │ │ │ └── loader.interceptor.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── globals.css │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── tailwind.css │ ├── test.ts │ └── theme.less ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── git-assets ├── 450-dsa.png ├── about.png └── upload-code-feature.png ├── package-lock.json ├── package.json └── server ├── .env.example ├── .gitignore ├── Procfile ├── database ├── index.ts └── schema │ ├── alltopicquestions.schema.ts │ ├── categories.schema.ts │ └── users.schema.ts ├── package-lock.json ├── package.json ├── server ├── controllers │ ├── AuthController │ │ ├── account-reset.controller.ts │ │ ├── data │ │ │ └── 450DSA-questions.ts │ │ ├── index.ts │ │ ├── login.controller.ts │ │ └── signup.controller.ts │ ├── CaterogyController │ │ └── index.ts │ ├── QuestionsController │ │ └── index.ts │ ├── api.controller.ts │ ├── index.ts │ └── middlerwares │ │ └── requires-auth.middleware.ts ├── index.ts ├── interfaces │ └── index.ts ├── server.ts └── utils │ └── index.ts ├── src └── index.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-detectable=false 2 | *.js linguist-detectable=true 3 | *.ts linguist-detectable=true 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | build 15 | 16 | *.txt 17 | 18 | # misc 19 | .env 20 | .DS_Store 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | .*.swp 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # Local Netlify folder 32 | .netlify 33 | 34 | 35 | server/.env -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Edge", 9 | "request": "launch", 10 | "type": "pwa-msedge", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": true 3 | } 4 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=dev 2 | JWT_REFRESH_TOKEN_SECRET= 3 | JWT_ACCESS_TOKEN_SECRET= 4 | 5 | DATABASE_URI_PROD= 6 | DATABASE_URI_DEV_ORIGINAL= 7 | DATABASE_URI_MIGRATION_PROD= -------------------------------------------------------------------------------- /backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /backend/Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start:prod -------------------------------------------------------------------------------- /backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let app: TestingModule; 7 | 8 | beforeAll(async () => { 9 | app = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | }); 14 | 15 | describe('getHello', () => { 16 | it('should return "Hello World!"', () => { 17 | const appController = app.get(AppController); 18 | expect(appController.getHello()).toBe('Hello World!'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /backend/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Ip } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(@Ip() ip: string) { 10 | return this.appService.description(ip); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { MongooseModule } from '@nestjs/mongoose'; 4 | import { AppController } from './app.controller'; 5 | import { AppService } from './app.service'; 6 | import { AuthModule } from './auth/auth.module'; 7 | import configuration from './config/configuration'; 8 | import { 9 | BAllTopicQuestion, 10 | BAllTopicQuestionSchema, 11 | } from './migration/schema/alltopicquestions.schema'; 12 | import { BUser, BUserSchema } from './migration/schema/user.schema'; 13 | import { MigrateService } from './migration/services/migration.service'; 14 | import { ProgressModule } from './progress/progress.module'; 15 | import { QuestionsModule } from './questions/questions.module'; 16 | import { SolutionModule } from './solution/solution.module'; 17 | 18 | @Module({ 19 | imports: [ 20 | ConfigModule.forRoot({ 21 | load: [configuration], 22 | encoding: ' utf-8', 23 | }), 24 | // MongooseModule.forRootAsync({ 25 | // imports: [ConfigModule], 26 | // inject: [ConfigService], 27 | // useFactory: (configService: ConfigService) => { 28 | // console.log({ configService }); 29 | // return { 30 | // uri: configService.get('mongodbProd'), 31 | // connectionName: 'MIGRATION_PROD', 32 | // }; 33 | // }, 34 | // }), 35 | // MongooseModule.forRootAsync({ 36 | // inject: [ConfigService], 37 | // imports: [ConfigModule], 38 | // useFactory: (configService: ConfigService) => { 39 | // return { uri: configService.get('mongodbDev') }; 40 | // }, 41 | // }), 42 | MongooseModule.forRoot(configuration().mongodbProd, { 43 | connectionName: 'PROD', 44 | }), 45 | MongooseModule.forRoot(configuration().mongodbMigrationProd, { 46 | connectionName: 'MIGRATION_PROD', 47 | }), 48 | AuthModule, 49 | QuestionsModule, 50 | SolutionModule, 51 | ProgressModule, 52 | MongooseModule.forFeature( 53 | [ 54 | { name: BUser.name, schema: BUserSchema, collection: 'users' }, 55 | { 56 | name: BAllTopicQuestion.name, 57 | schema: BAllTopicQuestionSchema, 58 | collection: 'alltopicquestions', 59 | }, 60 | ], 61 | 'MIGRATION_PROD', 62 | ), 63 | ], 64 | controllers: [AppController], 65 | providers: [AppService, MigrateService], 66 | }) 67 | export class AppModule {} 68 | -------------------------------------------------------------------------------- /backend/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | description(ip: string) { 6 | return { 7 | ip, 8 | apiname: '450-dsa-api', 9 | version: 'v2.0.2', 10 | description: 'A redesigned and complete rewrite of existing API', 11 | timestamp: Date.now(), 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthController } from './auth.controller'; 3 | 4 | describe('UsersController', () => { 5 | let controller: AuthController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [AuthController], 10 | }).compile(); 11 | 12 | controller = module.get(AuthController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { AuthService } from './auth.service'; 4 | import { AuthDto } from './dto/login-user.dto'; 5 | import { GetUser } from './get-user.decorator'; 6 | import { User } from './user.schema'; 7 | 8 | @Controller('auth') 9 | export class AuthController { 10 | constructor(private readonly authService: AuthService) {} 11 | 12 | @Get('/all') 13 | async getUser() { 14 | return await this.authService.getAllUsers(); 15 | } 16 | 17 | @Post('signup') 18 | async signin(@Body() userDto: AuthDto) { 19 | return await this.authService.signupWithUsernamePassword(userDto); 20 | } 21 | 22 | @Post('login') 23 | async login(@Body() userDto: AuthDto) { 24 | return await this.authService.loginWithUsernamePassword(userDto); 25 | } 26 | 27 | @Post('cms-login') 28 | async cmslogin(@Body() userDto: AuthDto) { 29 | return await this.authService.loginWithUsernamePassword(userDto, true); 30 | } 31 | 32 | @Get('is-loggedIn') 33 | @UseGuards(AuthGuard('jwt')) 34 | async isLoggedIn(@GetUser() user: User) { 35 | return {}; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /backend/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule, ConfigService } from '@nestjs/config'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { PassportModule } from '@nestjs/passport'; 6 | import { AuthController } from './auth.controller'; 7 | import { AuthService } from './auth.service'; 8 | import { JwtStrategy } from './jwt.strategy'; 9 | import { UserRepository } from './user.repository'; 10 | import { User, UserSchema } from './user.schema'; 11 | 12 | @Module({ 13 | imports: [ 14 | MongooseModule.forFeature( 15 | [{ name: User.name, schema: UserSchema, collection: 'users' }], 16 | 'PROD', 17 | ), 18 | PassportModule.register({ defaultStrategy: 'jwt', session: true }), 19 | JwtModule.registerAsync({ 20 | imports: [ConfigModule], 21 | inject: [ConfigService], 22 | useFactory: (configService: ConfigService) => { 23 | return { 24 | secret: configService.get('JWT_ACCESS_TOKEN_SECRET'), 25 | signOptions: { encoding: 'binary', expiresIn: '2h' }, 26 | }; 27 | }, 28 | }), 29 | ], 30 | controllers: [AuthController], 31 | providers: [AuthService, UserRepository, JwtStrategy, ConfigService], 32 | exports: [JwtStrategy, PassportModule, UserRepository], 33 | }) 34 | export class AuthModule {} 35 | -------------------------------------------------------------------------------- /backend/src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthService } from './auth.service'; 3 | 4 | describe('UsersService', () => { 5 | let service: AuthService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService], 10 | }).compile(); 11 | 12 | service = module.get(AuthService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/auth/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString, MinLength } from 'class-validator'; 2 | 3 | export class CreateUserDto { 4 | @IsNotEmpty() 5 | @IsString() 6 | @MinLength(5) 7 | username: string; 8 | 9 | @IsNotEmpty() 10 | @IsString() 11 | @MinLength(6) 12 | password: string; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/auth/dto/login-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | 3 | export class AuthDto { 4 | @IsNotEmpty() 5 | @IsString() 6 | username: string; 7 | 8 | @IsNotEmpty() 9 | @IsString() 10 | password: string; 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/auth/get-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { User } from './user.schema'; 3 | 4 | export const GetUser = createParamDecorator( 5 | (_data, ctx: ExecutionContext): User => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user; 8 | }, 9 | ); 10 | -------------------------------------------------------------------------------- /backend/src/auth/jwt-payload.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JwtPayloadInterface { 2 | sessionId: number; 3 | username: string; 4 | } 5 | -------------------------------------------------------------------------------- /backend/src/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { InjectModel } from '@nestjs/mongoose'; 4 | import { PassportStrategy } from '@nestjs/passport'; 5 | import { Model } from 'mongoose'; 6 | import { ExtractJwt, Strategy } from 'passport-jwt'; 7 | import { JwtPayloadInterface } from './jwt-payload.interface'; 8 | import { UserRepository } from './user.repository'; 9 | import { User, UserDocument } from './user.schema'; 10 | 11 | @Injectable() 12 | export class JwtStrategy extends PassportStrategy(Strategy) { 13 | constructor( 14 | @InjectModel(User.name) private readonly userModel: Model, 15 | private configService: ConfigService, 16 | private readonly userRepo: UserRepository, 17 | ) { 18 | super({ 19 | secretOrKey: configService.get('JWT_ACCESS_TOKEN_SECRET'), 20 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 21 | }); 22 | } 23 | 24 | async validate(payload: JwtPayloadInterface) { 25 | const { username, sessionId } = payload; 26 | const user = await this.userRepo.findOne({ username }); 27 | if (!user) throw new UnauthorizedException(); 28 | return { 29 | _id: user._id, 30 | id: user.id, 31 | username: user.username, 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/auth/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { FilterQuery, Model } from 'mongoose'; 4 | import { UUIDV4 } from 'src/shared/utility.methods'; 5 | import { User, UserDocument } from './user.schema'; 6 | 7 | @Injectable() 8 | export class UserRepository { 9 | constructor( 10 | @InjectModel(User.name) private userSchema: Model, 11 | ) {} 12 | 13 | async getAllUsers() { 14 | return await this.userSchema.find(); 15 | } 16 | 17 | async createUser(userInfo: { username: string; password: string }) { 18 | return new this.userSchema({ 19 | ...userInfo, 20 | id: UUIDV4(), 21 | createdAt: new Date(), 22 | updatedAt: new Date(), 23 | }); 24 | } 25 | 26 | async findOne(userFilter: FilterQuery) { 27 | return await this.userSchema.findOne({ ...userFilter }); 28 | } 29 | 30 | async insertBulk( 31 | userInformations: Array<{ username: string; password: string }>, 32 | ) { 33 | const updatedUserInformations = userInformations.map((userInfo) => { 34 | return { 35 | ...userInfo, 36 | id: UUIDV4(), 37 | createdAt: new Date(), 38 | updatedAt: new Date(), 39 | }; 40 | }); 41 | const startTime = Date.now(); 42 | await this.userSchema.insertMany(updatedUserInformations); 43 | return { 44 | status: 'Successfully Done!', 45 | completedIn: `${Date.now() - startTime} ms`, 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backend/src/auth/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | import { Base } from 'src/shared/base.schema'; 4 | 5 | @Schema({ collection: 'users' }) 6 | export class User extends Base { 7 | @Prop({ 8 | type: SchemaTypes.String, 9 | unique: true, 10 | minlength: 1, 11 | required: true, 12 | index: 'hashed', 13 | lowercase: true, 14 | }) 15 | username: string; 16 | 17 | @Prop({ 18 | unique: true, 19 | minlength: 6, 20 | required: true, 21 | type: SchemaTypes.String, 22 | }) 23 | password: string; 24 | } 25 | 26 | export type UserDocument = User & Document; 27 | export const UserSchema = SchemaFactory.createForClass(User); 28 | -------------------------------------------------------------------------------- /backend/src/auth/utility-methods.ts: -------------------------------------------------------------------------------- 1 | import { compareSync, genSaltSync, hashSync } from 'bcrypt'; 2 | 3 | export async function comparePasswords( 4 | plainTextPasskey: string, 5 | encryptedPasskey: string, 6 | ): Promise<{ data: boolean | null; error: boolean | null }> { 7 | const status = compareSync(plainTextPasskey, encryptedPasskey); 8 | return { data: status ?? false, error: !status ?? true }; 9 | } 10 | 11 | export function encryptPlainPassword(password: string) { 12 | return hashSync(password, genSaltSync(10)); 13 | } -------------------------------------------------------------------------------- /backend/src/config/configuration.ts: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | port: parseInt(process.env.PORT, 10) || 1337, 3 | mongodbProd: process.env.DATABASE_URI_PROD || '', 4 | mongodbMigrationProd: process.env.DATABASE_URI_MIGRATION_PROD || '', 5 | nodeenv: process.env.NODE_ENV, 6 | JWT_ACCESS_TOKEN_SECRET: process.env.JWT_ACCESS_TOKEN_SECRET || 'secret1234', 7 | }); 8 | -------------------------------------------------------------------------------- /backend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import * as compression from 'compression'; 4 | import { AppModule } from './app.module'; 5 | import configuration from './config/configuration'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(AppModule); 9 | app.setGlobalPrefix('api'); 10 | app.enableCors({ 11 | origin: ['http://127.0.0.1:4200', 'https://450-dsa-tracker.netlify.app'], 12 | }); 13 | app.use(compression()); 14 | app.useGlobalPipes(new ValidationPipe({ transform: true, always: true })); 15 | app.useGlobalPipes(new ValidationPipe()); 16 | await app.listen(configuration().port || 1337); 17 | await app.init(); 18 | } 19 | bootstrap(); 20 | -------------------------------------------------------------------------------- /backend/src/migration/schema/alltopicquestions.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | import { QuestionInterface } from '../data'; 4 | 5 | @Schema({ collection: 'alltopicquestions' }) 6 | export class BAllTopicQuestion { 7 | @Prop({ 8 | type: SchemaTypes.ObjectId, 9 | ref: 'users', 10 | }) 11 | userId: string; 12 | 13 | @Prop({ 14 | type: SchemaTypes.Array, 15 | }) 16 | questions: Array; 17 | } 18 | 19 | export type BAllTopicQuestionDocument = BAllTopicQuestion & Document; 20 | export const BAllTopicQuestionSchema = 21 | SchemaFactory.createForClass(BAllTopicQuestion); 22 | -------------------------------------------------------------------------------- /backend/src/migration/schema/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | 4 | @Schema({ collection: 'users' }) 5 | export class BUser { 6 | @Prop({ 7 | type: SchemaTypes.String, 8 | }) 9 | username: string; 10 | 11 | @Prop({ 12 | type: SchemaTypes.String, 13 | }) 14 | password: string; 15 | } 16 | 17 | export type BUserDocument = BUser & Document; 18 | export const BUserSchema = SchemaFactory.createForClass(BUser); 19 | -------------------------------------------------------------------------------- /backend/src/progress/progress.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProgressController } from './progress.controller'; 3 | 4 | describe('ProgressController', () => { 5 | let controller: ProgressController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ProgressController], 10 | }).compile(); 11 | 12 | controller = module.get(ProgressController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/progress/progress.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { GetUser } from 'src/auth/get-user.decorator'; 4 | import { ProgressService } from './progress.service'; 5 | 6 | @UseGuards(AuthGuard('jwt')) 7 | @Controller('progress') 8 | export class ProgressController { 9 | constructor(private readonly progressService: ProgressService) {} 10 | @Get('track') 11 | async track(@GetUser() user: any) { 12 | return await this.progressService.progress(user.id); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/progress/progress.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { Question } from 'src/questions/question.schema'; 4 | import { Solution, SolutionSchema } from 'src/solution/solution.schema'; 5 | import { ProgressController } from './progress.controller'; 6 | import { ProgressService } from './progress.service'; 7 | 8 | @Module({ 9 | imports: [ 10 | MongooseModule.forFeature( 11 | [ 12 | { 13 | name: Solution.name, 14 | schema: SolutionSchema, 15 | collection: 'solutions', 16 | }, 17 | { name: Question.name, schema: Question, collection: 'questions' }, 18 | ], 19 | 'PROD', 20 | ), 21 | ], 22 | controllers: [ProgressController], 23 | providers: [ProgressService], 24 | }) 25 | export class ProgressModule {} 26 | -------------------------------------------------------------------------------- /backend/src/progress/progress.schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | import { Base } from 'src/shared/base.schema'; 4 | 5 | @Schema({ collection: 'progress' }) 6 | export class Progress extends Base {} 7 | 8 | export type ProgressDocument = Progress & Document; 9 | export const ProgressSchema = SchemaFactory.createForClass(Progress); 10 | -------------------------------------------------------------------------------- /backend/src/progress/progress.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProgressService } from './progress.service'; 3 | 4 | describe('ProgressService', () => { 5 | let service: ProgressService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ProgressService], 10 | }).compile(); 11 | 12 | service = module.get(ProgressService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/progress/progress.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { Model } from 'mongoose'; 4 | import { Question, QuestionDocument } from 'src/questions/question.schema'; 5 | import { Solution, SolutionDocument } from 'src/solution/solution.schema'; 6 | 7 | @Injectable() 8 | export class ProgressService { 9 | constructor( 10 | @InjectModel(Solution.name) private solutionSchema: Model, 11 | @InjectModel(Question.name) private questionSchema: Model, 12 | ) {} 13 | 14 | async progress(userId: any) { 15 | const solvedQuestions = ( 16 | await this.solutionSchema.aggregate([ 17 | { $match: { userId } }, 18 | { 19 | $group: { 20 | _id: { questionId: '$questionId' }, 21 | count: { $sum: 1 }, 22 | }, 23 | }, 24 | ]) 25 | ).map((solve) => { 26 | return { 27 | questionId: solve._id.questionId, 28 | solveCount: solve.count, 29 | }; 30 | }); 31 | 32 | const questions = await this.questionSchema.find( 33 | {}, 34 | { _id: 0, __v: 0, problems: 0, topicInformation: 0 }, 35 | ); 36 | let questionsMap = new Map(); 37 | solvedQuestions.map((solq) => 38 | questionsMap.set(solq.questionId, solq.solveCount), 39 | ); 40 | 41 | const subscriberCountMap = await this.totalSubscribersToAllQuestionsMap(); 42 | 43 | const progressHistory = questions.map((question) => { 44 | const solveCount = questionsMap.get(question.id) ?? 0; 45 | return { 46 | ...question.toJSON(), 47 | totalSubscribers: subscriberCountMap.get(question.id) ?? 0, 48 | solveCount, 49 | started: solveCount === 0 ? false : true, 50 | completionPercentage: ( 51 | (solveCount / question.totalProblems) * 52 | 100 53 | ).toFixed(2), 54 | }; 55 | }); 56 | 57 | return { data: { progressHistory } }; 58 | } 59 | 60 | private async totalSubscribersToAllQuestionsMap() { 61 | // How many user has solved each question 62 | const data = ( 63 | await this.solutionSchema.aggregate([ 64 | { $group: { _id: { questionId: '$questionId' }, count: { $sum: 1 } } }, 65 | ]) 66 | ).map((solve) => { 67 | return { 68 | questionId: solve._id.questionId, 69 | solveCount: solve.count, 70 | }; 71 | }); 72 | const map = new Map(); 73 | data.map((solq) => map.set(solq.questionId, solq.solveCount)); 74 | return map; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /backend/src/questions/dto/base-question.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Base } from 'src/shared/base.schema'; 3 | 4 | export class BaseQuestionDto extends Base { 5 | @IsNotEmpty() 6 | @IsString() 7 | topicname: string; 8 | 9 | topicInformation: string; 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/questions/dto/create-question.dto.ts: -------------------------------------------------------------------------------- 1 | import { BaseQuestionDto } from './base-question.dto'; 2 | 3 | export class CreateQuestionDto extends BaseQuestionDto {} 4 | -------------------------------------------------------------------------------- /backend/src/questions/dto/update-question.dto.ts: -------------------------------------------------------------------------------- 1 | import { BaseQuestionDto } from './base-question.dto'; 2 | 3 | export class UpdateQuestionDto extends BaseQuestionDto {} 4 | -------------------------------------------------------------------------------- /backend/src/questions/problems/dto/base-problem.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { Base } from 'src/shared/base.schema'; 3 | 4 | export class BaseProblemDto extends Base { 5 | @IsNotEmpty() 6 | @IsString() 7 | problemTitle: string; 8 | 9 | @IsNotEmpty() 10 | @IsString() 11 | problemURL: string; 12 | 13 | @IsNotEmpty() 14 | @IsString() 15 | topicname: string; 16 | 17 | @IsString() 18 | questionInformation: string = ''; 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/questions/problems/dto/create-problem.dto.ts: -------------------------------------------------------------------------------- 1 | import { BaseProblemDto } from './base-problem.dto'; 2 | 3 | export class CreateProblemDto extends BaseProblemDto {} 4 | -------------------------------------------------------------------------------- /backend/src/questions/problems/dto/update-problem.dto.ts: -------------------------------------------------------------------------------- 1 | import { BaseProblemDto } from './base-problem.dto'; 2 | 3 | export class updateProblemDto extends BaseProblemDto { 4 | difficultyLevel: number; 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/questions/problems/problem.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | import { Base } from 'src/shared/base.schema'; 4 | 5 | export enum DifficultyTypeEnum { 6 | 'Easy', 7 | 'Medium', 8 | 'Hard', 9 | } 10 | 11 | @Schema({ collection: 'problems' }) 12 | export class Problem extends Base { 13 | @Prop({ type: SchemaTypes.String, required: true }) 14 | problemTitle: string; 15 | 16 | @Prop({ type: SchemaTypes.String, required: true }) 17 | problemURL: string; 18 | 19 | @Prop({ type: SchemaTypes.String, index: true, ref: 'Question' }) 20 | topicname: string; 21 | 22 | @Prop({ type: SchemaTypes.String, default: DifficultyTypeEnum.Medium }) 23 | difficultyLevel: DifficultyTypeEnum; 24 | 25 | @Prop({ type: SchemaTypes.String, default: '' }) 26 | questionInformation: string; 27 | 28 | @Prop({ type: SchemaTypes.Number, default: 0 }) 29 | upvoted: number; 30 | 31 | @Prop({ type: SchemaTypes.Number, default: 0 }) 32 | downvoted: number; 33 | } 34 | 35 | export type ProblemDocument = Problem & Document; 36 | export const ProblemSchema = SchemaFactory.createForClass(Problem); 37 | -------------------------------------------------------------------------------- /backend/src/questions/problems/problems.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProblemsController } from './problems.controller'; 3 | 4 | describe('ProblemsController', () => { 5 | let controller: ProblemsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ProblemsController], 10 | }).compile(); 11 | 12 | controller = module.get(ProblemsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/questions/problems/problems.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Patch, 6 | Post, 7 | Query, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { AuthGuard } from '@nestjs/passport'; 11 | import { GetUser } from 'src/auth/get-user.decorator'; 12 | import { CreateProblemDto } from './dto/create-problem.dto'; 13 | import { updateProblemDto } from './dto/update-problem.dto'; 14 | import { ProblemsService } from './problems.service'; 15 | import { VOTETYPE } from './voting.schema'; 16 | 17 | @UseGuards(AuthGuard('jwt')) 18 | @Controller('questions/problems') 19 | export class ProblemsController { 20 | constructor(private problemService: ProblemsService) {} 21 | 22 | @Post('create') 23 | async createProblem(@Body() createProblemDto: CreateProblemDto) { 24 | return await this.problemService.createProblem(createProblemDto); 25 | } 26 | 27 | @Patch('update') 28 | async updateProblem( 29 | @GetUser() user: any, 30 | @Query('problemId') problemId: string, 31 | @Body() updateParams: updateProblemDto, 32 | ) { 33 | return await this.problemService.updateProblem( 34 | user.id, 35 | problemId, 36 | updateParams, 37 | ); 38 | } 39 | 40 | @Get('details') 41 | async getCompleteInformationForTheProblemId( 42 | @GetUser() user: any, 43 | @Query('problemId') problemId: string, 44 | ) { 45 | return await this.problemService.getInformation(user.id, problemId); 46 | } 47 | 48 | @Post('vote-for') 49 | async updateVote( 50 | @GetUser() user: any, 51 | @Query('problemId') problemId: string, 52 | @Query('voteType') voteType: VOTETYPE, 53 | ) { 54 | return await this.problemService.updateVote(user, problemId, voteType); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /backend/src/questions/problems/problems.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProblemsService } from './problems.service'; 3 | 4 | describe('ProblemsService', () => { 5 | let service: ProblemsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ProblemsService], 10 | }).compile(); 11 | 12 | service = module.get(ProblemsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/questions/problems/voting.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | import { Base } from 'src/shared/base.schema'; 4 | 5 | export type VOTETYPE = 'UP' | 'DOWN'; 6 | 7 | @Schema({ collection: 'votes' }) 8 | export class Vote extends Base { 9 | @Prop({ type: SchemaTypes.String, ref: 'problems' }) 10 | problemId: string; 11 | 12 | @Prop({ type: SchemaTypes.String, ref: 'users' }) 13 | userId: string; 14 | 15 | @Prop({ type: SchemaTypes.String }) 16 | voteType: VOTETYPE; 17 | } 18 | 19 | export type VoteDocument = Document & Vote; 20 | export const VoteSchema = SchemaFactory.createForClass(Vote); 21 | -------------------------------------------------------------------------------- /backend/src/questions/question.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | import { Base } from 'src/shared/base.schema'; 4 | 5 | @Schema({ collection: 'questions' }) 6 | export class Question extends Base { 7 | @Prop({ type: SchemaTypes.String, required: true, unique: true }) 8 | topicname: string; 9 | 10 | @Prop({ 11 | type: SchemaTypes.String, 12 | default: 'Curated lists of popular questions', 13 | maxlength: 700, 14 | }) 15 | topicInformation: string; 16 | 17 | @Prop({ type: [SchemaTypes.ObjectId], default: [], ref: 'Problem' }) 18 | problems: Array; 19 | 20 | @Prop({ type: SchemaTypes.Number, default: 0 }) 21 | totalProblems: number; 22 | } 23 | 24 | export type QuestionDocument = Question & Document; 25 | export const QuestionSchema = SchemaFactory.createForClass(Question); 26 | -------------------------------------------------------------------------------- /backend/src/questions/questions.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { QuestionsController } from './questions.controller'; 3 | 4 | describe('QuestionsController', () => { 5 | let controller: QuestionsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [QuestionsController], 10 | }).compile(); 11 | 12 | controller = module.get(QuestionsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/questions/questions.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Patch, 7 | Post, 8 | Query, 9 | UseGuards, 10 | } from '@nestjs/common'; 11 | import { AuthGuard } from '@nestjs/passport'; 12 | import { GetUser } from 'src/auth/get-user.decorator'; 13 | import { CreateQuestionDto } from './dto/create-question.dto'; 14 | import { UpdateQuestionDto } from './dto/update-question.dto'; 15 | import { QuestionsService } from './questions.service'; 16 | 17 | @Controller('questions') 18 | @UseGuards(AuthGuard('jwt')) 19 | export class QuestionsController { 20 | constructor(private readonly questionService: QuestionsService) {} 21 | 22 | @Get('') 23 | async getQuestions( 24 | @GetUser() user: any, 25 | @Query('topicname') topicname: string = '', 26 | ) { 27 | // this method will return user's questionlist combined with solution tbl 28 | return await this.questionService.getAll(user.id, topicname); 29 | } 30 | 31 | @Post('create') 32 | async createQuestion(@Body() createQuestionDto: CreateQuestionDto) { 33 | return await this.questionService.createQuestion(createQuestionDto); 34 | } 35 | 36 | @Patch('update') 37 | async updateQuestion( 38 | @Query('id') id: string, 39 | @Body() updateQuestionDto: UpdateQuestionDto, 40 | ) { 41 | return await this.questionService.updateQuestion(id, updateQuestionDto); 42 | } 43 | 44 | @Delete('delete') 45 | async deleteQuestion(@Query('id') id: string) { 46 | return await this.questionService.deleteQuestion(id); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backend/src/questions/questions.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { AuthModule } from 'src/auth/auth.module'; 4 | import { User, UserSchema } from 'src/auth/user.schema'; 5 | import { SolutionModule } from 'src/solution/solution.module'; 6 | import { Solution, SolutionSchema } from 'src/solution/solution.schema'; 7 | import { Problem, ProblemSchema } from './problems/problem.schema'; 8 | import { ProblemsController } from './problems/problems.controller'; 9 | import { ProblemsRepository } from './problems/problems.repository'; 10 | import { ProblemsService } from './problems/problems.service'; 11 | import { Vote, VoteSchema } from './problems/voting.schema'; 12 | import { QuestionRepository } from './question.repository'; 13 | import { Question, QuestionSchema } from './question.schema'; 14 | import { QuestionsController } from './questions.controller'; 15 | import { QuestionsService } from './questions.service'; 16 | 17 | @Module({ 18 | imports: [ 19 | AuthModule, 20 | MongooseModule.forFeature( 21 | [ 22 | { 23 | name: Question.name, 24 | schema: QuestionSchema, 25 | collection: 'questions', 26 | }, 27 | { name: Problem.name, schema: ProblemSchema, collection: 'problems' }, 28 | { name: Vote.name, schema: VoteSchema, collection: 'votes' }, 29 | { 30 | name: Solution.name, 31 | schema: SolutionSchema, 32 | collection: 'solutions', 33 | }, 34 | { name: User.name, schema: UserSchema, collection: 'users' }, 35 | ], 36 | 'PROD', 37 | ), 38 | SolutionModule, 39 | ], 40 | controllers: [QuestionsController, ProblemsController], 41 | providers: [ 42 | QuestionsService, 43 | QuestionRepository, 44 | ProblemsService, 45 | ProblemsRepository, 46 | ], 47 | exports: [QuestionRepository, ProblemsRepository], 48 | }) 49 | export class QuestionsModule {} 50 | -------------------------------------------------------------------------------- /backend/src/questions/questions.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { QuestionsService } from './questions.service'; 3 | 4 | describe('QuestionsService', () => { 5 | let service: QuestionsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [QuestionsService], 10 | }).compile(); 11 | 12 | service = module.get(QuestionsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/questions/questions.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotAcceptableException } from '@nestjs/common'; 2 | import { CreateQuestionDto } from './dto/create-question.dto'; 3 | import { UpdateQuestionDto } from './dto/update-question.dto'; 4 | import { QuestionRepository } from './question.repository'; 5 | 6 | @Injectable() 7 | export class QuestionsService { 8 | constructor(private readonly questionRepo: QuestionRepository) {} 9 | 10 | async getAll(userId: string, topicname: string) { 11 | try { 12 | if (!topicname) { 13 | // return all 14 | const questions = await this.questionRepo.findAll(); 15 | return { data: { questions } }; 16 | } else { 17 | // return by topicname only 18 | const questions = await this.questionRepo.findByTopicname( 19 | userId, 20 | topicname, 21 | ); 22 | return { data: { questions } }; 23 | } 24 | } catch (error) { 25 | return error; 26 | } 27 | } 28 | 29 | async createQuestion(createQuestionDto: CreateQuestionDto) { 30 | try { 31 | const { topicname, topicInformation } = createQuestionDto; 32 | const question = this.questionRepo.create({ 33 | topicname, 34 | topicInformation, 35 | }); 36 | await question.save(); 37 | 38 | return { 39 | data: `New Topic ${topicname} Has Added In The Questions Category!`, 40 | }; 41 | } catch (error) { 42 | if (error.code === 11000) 43 | return new NotAcceptableException('Topic has been already created!'); 44 | return error; 45 | } 46 | } 47 | 48 | async updateQuestion(id: string, updateQuestion: UpdateQuestionDto) { 49 | try { 50 | const { topicname, topicInformation } = updateQuestion; 51 | await this.questionRepo.findByIdAndUpdate( 52 | id, 53 | topicname, 54 | topicInformation, 55 | ); 56 | 57 | return { data: `Topic Has Been Updated!.` }; 58 | } catch (error) { 59 | if (error.code === 11000) 60 | return new NotAcceptableException('Topic has been already created!'); 61 | return error; 62 | } 63 | } 64 | 65 | async deleteQuestion(id: string) { 66 | try { 67 | await this.questionRepo.delete(id); 68 | return { data: `Topic ${id} has been deleted!.` }; 69 | } catch (error) { 70 | return error; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /backend/src/shared/base.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | 4 | @Schema() 5 | export class Base { 6 | @Prop({ 7 | type: SchemaTypes.String, 8 | index: 'hashed', 9 | required: true, 10 | unique: true, 11 | }) 12 | id: string; 13 | 14 | @Prop({ type: SchemaTypes.Date }) 15 | createdAt: Date; 16 | 17 | @Prop({ type: SchemaTypes.Date }) 18 | updatedAt: Date; 19 | } 20 | 21 | export type BaseDocument = Base & Document; 22 | export const BaseSchema = SchemaFactory.createForClass(Base); 23 | -------------------------------------------------------------------------------- /backend/src/shared/utility.methods.ts: -------------------------------------------------------------------------------- 1 | export function UUIDV4() { 2 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 3 | var r = (Math.random() * 16) | 0, 4 | v = c == 'x' ? r : (r & 0x3) | 0x8; 5 | return v.toString(16); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/solution/dto/base-solution.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString } from 'class-validator'; 2 | 3 | export class BaseSolutionDto { 4 | @IsString() 5 | code: string = '// Upload your working solution!'; 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/solution/dto/create-solution.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsUUID } from 'class-validator'; 2 | import { BaseSolutionDto } from './base-solution.dto'; 3 | 4 | export class CreateSolutionDto extends BaseSolutionDto { 5 | @IsNotEmpty() 6 | @IsUUID() 7 | questionId: string; 8 | 9 | @IsNotEmpty() 10 | @IsUUID() 11 | problemId: string; 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/solution/dto/update-solution.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsString } from 'class-validator'; 2 | import { BaseSolutionDto } from './base-solution.dto'; 3 | 4 | export class UpdateSolutionDto extends BaseSolutionDto { 5 | @IsNotEmpty() 6 | @IsString() 7 | code: string; 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/solution/solution.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { SolutionController } from './solution.controller'; 3 | 4 | describe('SolutionController', () => { 5 | let controller: SolutionController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [SolutionController], 10 | }).compile(); 11 | 12 | controller = module.get(SolutionController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/solution/solution.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Patch, 6 | Post, 7 | Query, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { AuthGuard } from '@nestjs/passport'; 11 | import { GetUser } from 'src/auth/get-user.decorator'; 12 | import { CreateSolutionDto } from './dto/create-solution.dto'; 13 | import { UpdateSolutionDto } from './dto/update-solution.dto'; 14 | import { SolutionService } from './solution.service'; 15 | 16 | @UseGuards(AuthGuard('jwt')) 17 | @Controller('solutions') 18 | export class SolutionController { 19 | constructor(private readonly solutionService: SolutionService) {} 20 | 21 | @Post('submit') 22 | async submit( 23 | @GetUser() user: any, 24 | @Body() createSolutionDto: CreateSolutionDto, 25 | ) { 26 | // return await this.solutionService.findAll(); 27 | return await this.solutionService.submit(user.id, createSolutionDto); 28 | } 29 | 30 | @Patch('update') 31 | async update( 32 | @GetUser() user: any, 33 | @Query('solutionId') solutionId: string, 34 | @Body() updateSolutionDto: UpdateSolutionDto, 35 | ) { 36 | return await this.solutionService.update( 37 | user.id, 38 | solutionId, 39 | updateSolutionDto, 40 | ); 41 | } 42 | 43 | @Delete('reset-progress') 44 | async resetProgressForQuestionTopic(@GetUser() user: any, @Query('questionId') questionId: string) { 45 | return await this.solutionService.resetProgressByQuestionTopicId(user.id, questionId); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /backend/src/solution/solution.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { Question, QuestionSchema } from 'src/questions/question.schema'; 4 | import { SolutionController } from './solution.controller'; 5 | import { SolutionRepository } from './solution.repository'; 6 | import { Solution, SolutionSchema } from './solution.schema'; 7 | import { SolutionService } from './solution.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | MongooseModule.forFeature( 12 | [ 13 | { 14 | name: Solution.name, 15 | schema: SolutionSchema, 16 | collection: 'solutions', 17 | }, 18 | { 19 | name: Question.name, 20 | schema: QuestionSchema, 21 | collection: 'questions', 22 | }, 23 | ], 24 | 'PROD', 25 | ), 26 | ], 27 | controllers: [SolutionController], 28 | providers: [SolutionService, SolutionRepository], 29 | exports: [SolutionService, SolutionRepository], 30 | }) 31 | export class SolutionModule {} 32 | -------------------------------------------------------------------------------- /backend/src/solution/solution.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document, SchemaTypes } from 'mongoose'; 3 | import { Base } from 'src/shared/base.schema'; 4 | 5 | @Schema({ collection: 'solutions' }) 6 | export class Solution extends Base { 7 | @Prop({ type: SchemaTypes.String, required: true, index: true }) 8 | userId: string; 9 | 10 | @Prop({ type: SchemaTypes.String, required: true }) 11 | questionId: string; 12 | 13 | @Prop({ type: SchemaTypes.String, required: true, unique: false }) 14 | problemId: string; 15 | 16 | @Prop({ 17 | type: SchemaTypes.String, 18 | // required: true, 19 | default: '// Upload your working solution!', 20 | }) 21 | code: string; 22 | } 23 | 24 | export type SolutionDocument = Solution & Document; 25 | export const SolutionSchema = SchemaFactory.createForClass(Solution); 26 | -------------------------------------------------------------------------------- /backend/src/solution/solution.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { SolutionService } from './solution.service'; 3 | 4 | describe('SolutionService', () => { 5 | let service: SolutionService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [SolutionService], 10 | }).compile(); 11 | 12 | service = module.get(SolutionService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend/src/solution/solution.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotAcceptableException } from '@nestjs/common'; 2 | import { CreateSolutionDto } from './dto/create-solution.dto'; 3 | import { UpdateSolutionDto } from './dto/update-solution.dto'; 4 | import { SolutionRepository } from './solution.repository'; 5 | 6 | @Injectable() 7 | export class SolutionService { 8 | constructor(private readonly solutionRepository: SolutionRepository) {} 9 | 10 | async submit(userId: any, createSolutionDto: CreateSolutionDto) { 11 | try { 12 | await this.solutionRepository.create(userId, createSolutionDto); 13 | return { data: `Your Solution Has Been Saved!.` }; 14 | } catch (error) { 15 | if (error.code === 11000) 16 | throw new NotAcceptableException( 17 | `Solution is already saved. You can only modify your Code Submission!.`, 18 | ); 19 | return error; 20 | } 21 | } 22 | 23 | async findAll() { 24 | return await this.solutionRepository.findAll(); 25 | } 26 | 27 | async update( 28 | userid: any, 29 | solutionId: string, 30 | updateSolutionDto: UpdateSolutionDto, 31 | ) { 32 | if (updateSolutionDto.code === '// Upload your working solution!') 33 | throw new NotAcceptableException( 34 | `Seems your solution is fishy!. Please Upload your working code!.`, 35 | ); 36 | 37 | try { 38 | await this.solutionRepository.update( 39 | userid, 40 | solutionId, 41 | updateSolutionDto, 42 | ); 43 | 44 | return { data: `We have updated your Updated Solution!` }; 45 | } catch (error) { 46 | return error; 47 | } 48 | } 49 | 50 | async resetProgressByQuestionTopicId(userId: string, questionId: string) { 51 | try { 52 | const data = await this.solutionRepository.resetProgressByQuestionTopicId( 53 | userId, 54 | questionId, 55 | ); 56 | return { 57 | data: { ...data }, 58 | message: 'We have reseted your progress on this topic!', 59 | }; 60 | } catch (error) { 61 | return error; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /backend/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'supertest'; 2 | import { Test } from '@nestjs/testing'; 3 | import { AppModule } from './../src/app.module'; 4 | import { INestApplication } from '@nestjs/common'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture = 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 | -------------------------------------------------------------------------------- /backend/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 | -------------------------------------------------------------------------------- /backend/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /backend/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": "./", 13 | "incremental": true, 14 | "skipLibCheck": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "settings": { 8 | "import/resolver": { 9 | "node": { 10 | "extensions": [ 11 | ".js", 12 | ".jsx", 13 | ".ts", 14 | ".tsx" 15 | ] 16 | } 17 | } 18 | }, 19 | "extends": [ 20 | "eslint:recommended", 21 | "plugin:react/recommended", 22 | "plugin:@typescript-eslint/recommended" 23 | ], 24 | "parser": "@typescript-eslint/parser", 25 | "parserOptions": { 26 | "ecmaFeatures": { 27 | "jsx": true 28 | }, 29 | "ecmaVersion": 12, 30 | "sourceType": "module" 31 | }, 32 | "plugins": [ 33 | "react", 34 | "@typescript-eslint" 35 | ], 36 | "rules": { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /client/netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/proxy/*" 3 | to = "https://dsa-450-backend.herokuapp.com/proxy/:splat" 4 | status = 200 5 | force = true 6 | 7 | 8 | [[redirects]] 9 | from = "/*" 10 | to = "/" 11 | status = 200 12 | 13 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "tsc && vite build", 6 | "serve": "vite preview" 7 | }, 8 | "dependencies": { 9 | "@monaco-editor/react": "^4.2.1", 10 | "@types/js-cookie": "^2.2.6", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "@types/react-router-dom": "^5.1.7", 14 | "js-cookie": "^2.2.1", 15 | "monaco-editor": "^0.25.2", 16 | "react": "^17.0.0", 17 | "react-dom": "^17.0.0", 18 | "react-router-dom": "^5.2.0" 19 | }, 20 | "devDependencies": { 21 | "@typescript-eslint/eslint-plugin": "^4.28.0", 22 | "@typescript-eslint/parser": "^4.28.0", 23 | "@vitejs/plugin-react-refresh": "^1.3.1", 24 | "autoprefixer": "^10.2.6", 25 | "eslint": "^7.29.0", 26 | "eslint-import-resolver-typescript": "^2.4.0", 27 | "eslint-plugin-react": "^7.24.0", 28 | "netlify-cli": "^4.0.5", 29 | "postcss": "^8.3.3", 30 | "tailwindcss": "^2.1.4", 31 | "typescript": "^4.3.2", 32 | "vite": "^2.3.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sounishnath003/450-DSA-Tracker/cd8ee8a86ba80fc8584b8ef94873a6815fb32b73/client/src/App.css -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./App.css"; 3 | import { AuthHome } from "./components/Auth"; 4 | import Footer from "./components/Footer"; 5 | import Home from "./components/Home"; 6 | import { useAuth } from "./context/AuthContext"; 7 | 8 | function App() { 9 | const { authState } = useAuth(); 10 | 11 | React.useEffect(() => { 12 | console.log("App initialized"); 13 | // window.addEventListener('click', () => window.navigator.onLine ? null : window.alert('You are offline! Check your internet connection!')); 14 | }, []); 15 | 16 | return ( 17 | 18 | {!authState.isLoggedIn ? : } 19 |