[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 amitavdevzone\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "readme.md",
    "content": "# Quiz Manager app\n\nThis is a sample application built for my Youtube series on Quiz manager application. The backend API is built using Nest JS - a popular Node JS framework. \n\nThe concept of this app is to allow Users to register themselves and take different Quizes that have been created inside the application. There will be Admins who will be moderating the quizes and also reviewing the responses.\n"
  },
  {
    "path": "server/.editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_size = 2\nindent_style = space\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "server/.eslintrc.js",
    "content": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: 'tsconfig.json',\n    sourceType: 'module',\n  },\n  plugins: ['@typescript-eslint/eslint-plugin'],\n  extends: [\n    'plugin:@typescript-eslint/recommended',\n    'prettier/@typescript-eslint',\n    'plugin:prettier/recommended',\n  ],\n  root: true,\n  env: {\n    node: true,\n    jest: true,\n  },\n  ignorePatterns: ['.eslintrc.js'],\n  rules: {\n    '@typescript-eslint/interface-name-prefix': 'off',\n    '@typescript-eslint/explicit-function-return-type': 'off',\n    '@typescript-eslint/explicit-module-boundary-types': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n  },\n};\n"
  },
  {
    "path": "server/.gitignore",
    "content": "# compiled output\n/dist\n/node_modules\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# OS\n.DS_Store\n\n# Tests\n/coverage\n/.nyc_output\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# Important secrets\n.env\n.env.prod\n\n# Uploads\n!uploads\nuploads/*\n"
  },
  {
    "path": "server/.prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}"
  },
  {
    "path": "server/README.md",
    "content": "<p align=\"center\">\n  <a href=\"http://nestjs.com/\" target=\"blank\"><img src=\"https://nestjs.com/img/logo_text.svg\" width=\"320\" alt=\"Nest Logo\" /></a>\n</p>\n\n[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456\n[circleci-url]: https://circleci.com/gh/nestjs/nest\n\n  <p align=\"center\">A progressive <a href=\"http://nodejs.org\" target=\"_blank\">Node.js</a> framework for building efficient and scalable server-side applications.</p>\n    <p align=\"center\">\n<a href=\"https://www.npmjs.com/~nestjscore\" target=\"_blank\"><img src=\"https://img.shields.io/npm/v/@nestjs/core.svg\" alt=\"NPM Version\" /></a>\n<a href=\"https://www.npmjs.com/~nestjscore\" target=\"_blank\"><img src=\"https://img.shields.io/npm/l/@nestjs/core.svg\" alt=\"Package License\" /></a>\n<a href=\"https://www.npmjs.com/~nestjscore\" target=\"_blank\"><img src=\"https://img.shields.io/npm/dm/@nestjs/common.svg\" alt=\"NPM Downloads\" /></a>\n<a href=\"https://circleci.com/gh/nestjs/nest\" target=\"_blank\"><img src=\"https://img.shields.io/circleci/build/github/nestjs/nest/master\" alt=\"CircleCI\" /></a>\n<a href=\"https://coveralls.io/github/nestjs/nest?branch=master\" target=\"_blank\"><img src=\"https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9\" alt=\"Coverage\" /></a>\n<a href=\"https://discord.gg/G7Qnnhy\" target=\"_blank\"><img src=\"https://img.shields.io/badge/discord-online-brightgreen.svg\" alt=\"Discord\"/></a>\n<a href=\"https://opencollective.com/nest#backer\" target=\"_blank\"><img src=\"https://opencollective.com/nest/backers/badge.svg\" alt=\"Backers on Open Collective\" /></a>\n<a href=\"https://opencollective.com/nest#sponsor\" target=\"_blank\"><img src=\"https://opencollective.com/nest/sponsors/badge.svg\" alt=\"Sponsors on Open Collective\" /></a>\n  <a href=\"https://paypal.me/kamilmysliwiec\" target=\"_blank\"><img src=\"https://img.shields.io/badge/Donate-PayPal-ff3f59.svg\"/></a>\n    <a href=\"https://opencollective.com/nest#sponsor\"  target=\"_blank\"><img src=\"https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg\" alt=\"Support us\"></a>\n  <a href=\"https://twitter.com/nestframework\" target=\"_blank\"><img src=\"https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow\"></a>\n</p>\n  <!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)\n  [![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->\n\n## Description\n\n[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.\n\n## Installation\n\n```bash\n$ npm install\n```\n\n## Running the app\n\n```bash\n# development\n$ npm run start\n\n# watch mode\n$ npm run start:dev\n\n# production mode\n$ npm run start:prod\n```\n\n## Test\n\n```bash\n# unit tests\n$ npm run test\n\n# e2e tests\n$ npm run test:e2e\n\n# test coverage\n$ npm run test:cov\n```\n\n## Support\n\nNest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).\n\n## Stay in touch\n\n- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)\n- Website - [https://nestjs.com](https://nestjs.com/)\n- Twitter - [@nestframework](https://twitter.com/nestframework)\n\n## License\n\nNest is [MIT licensed](LICENSE).\n"
  },
  {
    "path": "server/nest-cli.json",
    "content": "{\n  \"collection\": \"@nestjs/schematics\",\n  \"sourceRoot\": \"src\"\n}\n"
  },
  {
    "path": "server/package.json",
    "content": "{\n  \"name\": \"server\",\n  \"version\": \"0.0.1\",\n  \"description\": \"\",\n  \"author\": \"\",\n  \"private\": true,\n  \"license\": \"UNLICENSED\",\n  \"scripts\": {\n    \"prebuild\": \"rimraf dist\",\n    \"build\": \"nest build\",\n    \"format\": \"prettier --write \\\"src/**/*.ts\\\" \\\"test/**/*.ts\\\"\",\n    \"start\": \"nest start\",\n    \"start:dev\": \"nest start --watch\",\n    \"start:debug\": \"nest start --debug --watch\",\n    \"start:prod\": \"node dist/main\",\n    \"lint\": \"eslint \\\"{src,apps,libs,test}/**/*.ts\\\" --fix\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"test:cov\": \"jest --coverage\",\n    \"test:debug\": \"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand\",\n    \"test:e2e\": \"jest --config ./test/jest-e2e.json\",\n    \"typeorm:cli\": \"ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli -f src/config/typeorm.config-migrations.ts\",\n    \"migration:generate\": \"npm run typeorm:cli -- migration:generate -d src/database/migrations -n\",\n    \"migration:create\": \"npm run typeorm:cli -- migration:create -d src/database/migrations -n\",\n    \"migration:run\": \"npm run typeorm:cli -- migration:run\",\n    \"migration:revert\": \"npm run typeorm:cli -- migration:revert\",\n    \"seed:config\": \"ts-node ./node_modules/typeorm-seeding/dist/cli.js config -n src/config/typeorm.config-migrations.ts\",\n    \"seed:run\": \"ts-node ./node_modules/typeorm-seeding/dist/cli.js seed -n src/config/typeorm.config-migrations.ts\",\n    \"db:refresh\": \"npm run typeorm:cli schema:drop && npm run migration:run && npm run seed:run\"\n  },\n  \"dependencies\": {\n    \"@nestjs/common\": \"^8.4.4\",\n    \"@nestjs/config\": \"^2.0.0\",\n    \"@nestjs/core\": \"^8.4.4\",\n    \"@nestjs/event-emitter\": \"^1.1.0\",\n    \"@nestjs/jwt\": \"^8.0.0\",\n    \"@nestjs/passport\": \"^8.2.1\",\n    \"@nestjs/platform-express\": \"^8.4.4\",\n    \"@nestjs/swagger\": \"^5.2.1\",\n    \"@nestjs/typeorm\": \"^8.0.3\",\n    \"@ngneat/falso\": \"^5.0.0\",\n    \"@types/bcrypt\": \"^5.0.0\",\n    \"bcrypt\": \"^5.0.1\",\n    \"class-transformer\": \"^0.5.1\",\n    \"class-validator\": \"^0.13.2\",\n    \"meilisearch\": \"^0.25.1\",\n    \"mysql2\": \"^2.3.3\",\n    \"nestjs-typeorm-paginate\": \"^3.2.0\",\n    \"passport\": \"^0.5.2\",\n    \"passport-jwt\": \"^4.0.0\",\n    \"passport-local\": \"^1.0.0\",\n    \"reflect-metadata\": \"^0.1.13\",\n    \"rimraf\": \"^3.0.2\",\n    \"rxjs\": \"^7.5.5\",\n    \"swagger-ui-express\": \"^4.3.0\",\n    \"typeorm\": \"^0.2.45\",\n    \"typeorm-seeding\": \"^1.6.1\"\n  },\n  \"devDependencies\": {\n    \"@nestjs/cli\": \"^8.2.5\",\n    \"@nestjs/schematics\": \"^8.0.10\",\n    \"@nestjs/testing\": \"^8.4.4\",\n    \"@types/express\": \"^4.17.13\",\n    \"@types/jest\": \"^27.4.1\",\n    \"@types/multer\": \"^1.4.7\",\n    \"@types/node\": \"^17.0.25\",\n    \"@types/passport\": \"^1.0.7\",\n    \"@types/passport-jwt\": \"^3.0.6\",\n    \"@types/passport-local\": \"^1.0.34\",\n    \"@types/supertest\": \"^2.0.12\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.20.0\",\n    \"@typescript-eslint/parser\": \"^5.20.0\",\n    \"eslint\": \"^8.13.0\",\n    \"eslint-config-prettier\": \"8.5.0\",\n    \"eslint-plugin-prettier\": \"^4.0.0\",\n    \"jest\": \"^27.5.1\",\n    \"prettier\": \"^2.6.2\",\n    \"supertest\": \"^6.2.2\",\n    \"ts-jest\": \"^27.1.4\",\n    \"ts-loader\": \"^9.2.8\",\n    \"ts-node\": \"^10.7.0\",\n    \"tsconfig-paths\": \"^3.14.1\",\n    \"typescript\": \"^4.6.3\"\n  },\n  \"jest\": {\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"json\",\n      \"ts\"\n    ],\n    \"rootDir\": \"src\",\n    \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n    \"transform\": {\n      \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n    },\n    \"collectCoverageFrom\": [\n      \"**/*.(t|j)s\"\n    ],\n    \"coverageDirectory\": \"../coverage\",\n    \"testEnvironment\": \"node\"\n  }\n}\n"
  },
  {
    "path": "server/src/app.controller.spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { AppController } from './app.controller';\nimport { AppService } from './app.service';\n\ndescribe('AppController', () => {\n  let appController: AppController;\n\n  beforeEach(async () => {\n    const app: TestingModule = await Test.createTestingModule({\n      controllers: [AppController],\n      providers: [AppService],\n    }).compile();\n\n    appController = app.get<AppController>(AppController);\n  });\n\n  describe('root', () => {\n    it('should return \"Hello World!\"', () => {\n      expect(appController.getHello()).toBe('Hello World!');\n    });\n  });\n});\n"
  },
  {
    "path": "server/src/app.controller.ts",
    "content": "import {\n  Controller,\n  Get,\n  Post,\n  UploadedFile,\n  UseInterceptors,\n} from '@nestjs/common';\nimport { FileInterceptor } from '@nestjs/platform-express';\nimport { diskStorage } from 'multer';\nimport { extname } from 'path';\nimport { AppService } from './app.service';\n\n@Controller()\nexport class AppController {\n  constructor(private readonly appService: AppService) {}\n\n  @Get()\n  getHello(): string {\n    return this.appService.getHello();\n  }\n\n  @Post('/file')\n  @UseInterceptors(\n    FileInterceptor('file', {\n      storage: diskStorage({\n        destination: './uploads',\n        filename: (req, file, callback) => {\n          const uniqueSuffix =\n            Date.now() + '-' + Math.round(Math.random() * 1e9);\n          const ext = extname(file.originalname);\n          const filename = `${uniqueSuffix}${ext}`;\n          callback(null, filename);\n        },\n      }),\n    }),\n  )\n  handleUpload(@UploadedFile() file: Express.Multer.File) {\n    console.log('file', file);\n    return 'File upload API';\n  }\n}\n"
  },
  {
    "path": "server/src/app.module.ts",
    "content": "import {\n  MiddlewareConsumer,\n  Module,\n  NestModule,\n  RequestMethod,\n} from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { AppController } from './app.controller';\nimport { AppService } from './app.service';\nimport { typeOrmAsyncConfig } from './config/typeorm.config';\nimport { QuizModule } from './modules/quiz/quiz.module';\nimport { UserModule } from './modules/user/user.module';\nimport { AuthModule } from './modules/auth/auth.module';\nimport { ApiTokenCheckMiddleware } from './common/middleware/api-token-check.middleware';\nimport { EventEmitterModule } from '@nestjs/event-emitter';\nimport { SearchModule } from './modules/search/search.module';\nimport { MulterModule } from '@nestjs/platform-express';\n@Module({\n  imports: [\n    ConfigModule.forRoot({ isGlobal: true }),\n    TypeOrmModule.forRootAsync(typeOrmAsyncConfig),\n    EventEmitterModule.forRoot(),\n    MulterModule.register({ dest: './uploads' }),\n    QuizModule,\n    UserModule,\n    AuthModule,\n    SearchModule,\n  ],\n  controllers: [AppController],\n  providers: [AppService],\n})\nexport class AppModule implements NestModule {\n  configure(consumer: MiddlewareConsumer) {\n    consumer\n      .apply(ApiTokenCheckMiddleware)\n      .forRoutes({ path: '/', method: RequestMethod.ALL });\n  }\n}\n"
  },
  {
    "path": "server/src/app.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class AppService {\n  getHello(): string {\n    return 'Hello World!';\n  }\n}\n"
  },
  {
    "path": "server/src/app.utils.ts",
    "content": "import { HttpStatus, ValidationPipe } from '@nestjs/common';\n\nconst PASSWORD_RULE = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;\n\nconst PASSWORD_RULE_MESSAGE =\n  'Password should have 1 upper case, lowcase letter along with a number and special character.';\n\nconst VALIDATION_PIPE = new ValidationPipe({\n  errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,\n});\n\nexport const REGEX = {\n  PASSWORD_RULE,\n};\n\nexport const MESSAGES = {\n  PASSWORD_RULE_MESSAGE,\n};\n\nexport const SETTINGS = {\n  VALIDATION_PIPE,\n};\n"
  },
  {
    "path": "server/src/common/constants/event.constants.ts",
    "content": "export const events = {\n  RESPONSE_SUBMITTED: 'response.submitted',\n};\n"
  },
  {
    "path": "server/src/common/decorator/api-pagination.response.ts",
    "content": "import { applyDecorators, Type } from '@nestjs/common';\nimport { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';\nimport { PaginatedDto } from '../dto/paginated.dto';\n\ninterface IPaginatedDecoratorApiResponse {\n  model: Type<any>;\n  description?: string;\n}\n\nexport const ApiPaginatedResponse = <TModel extends Type<any>>(\n  options: IPaginatedDecoratorApiResponse,\n) => {\n  return applyDecorators(\n    ApiExtraModels(PaginatedDto),\n    ApiOkResponse({\n      description: options.description || 'Successfully received model list',\n      schema: {\n        allOf: [\n          { $ref: getSchemaPath(PaginatedDto) },\n          {\n            properties: {\n              items: {\n                type: 'array',\n                items: { $ref: getSchemaPath(options.model) },\n              },\n              meta: {\n                type: 'any',\n                default: {\n                  totalItems: 2,\n                  itemCount: 2,\n                  itemsPerPage: 2,\n                  totalPages: 1,\n                  currentPage: 1,\n                },\n              },\n            },\n          },\n        ],\n      },\n    }),\n  );\n};\n"
  },
  {
    "path": "server/src/common/dto/paginated.dto.ts",
    "content": "interface PaginationMeta {\n  totalItems: number;\n  itemCount: number;\n  itemsPerPage: number;\n  totalPages: number;\n  currentPage: number;\n}\n\nclass PaginatedDto<TData> {\n  items: TData[];\n  meta: PaginationMeta;\n}\n\nexport { PaginationMeta, PaginatedDto };\n"
  },
  {
    "path": "server/src/common/exceptions/api-token-payement.exception.ts",
    "content": "import { HttpException, HttpStatus } from '@nestjs/common';\n\nexport class ApiTokenPaymentException extends HttpException {\n  constructor() {\n    super('Token suggest payment is required', HttpStatus.PAYMENT_REQUIRED);\n  }\n}\n"
  },
  {
    "path": "server/src/common/middleware/api-token-check.middleware.ts",
    "content": "import { NestMiddleware } from '@nestjs/common';\nimport { NextFunction, Request, Response } from 'express';\nimport { ApiTokenPaymentException } from '../exceptions/api-token-payement.exception';\n\nexport class ApiTokenCheckMiddleware implements NestMiddleware {\n  use(req: Request, res: Response, next: NextFunction) {\n    if (req.headers['api-token'] !== 'my-token') {\n      throw new ApiTokenPaymentException();\n    }\n    next();\n  }\n}\n"
  },
  {
    "path": "server/src/config/app.config.ts",
    "content": "export default () => ({\n  appSecret: process.env.APP_SECRET,\n});\n"
  },
  {
    "path": "server/src/config/jwt.config.ts",
    "content": "import { JwtModuleAsyncOptions } from '@nestjs/jwt';\n\nimport appConfig from './app.config';\n\nexport const jwtConfig: JwtModuleAsyncOptions = {\n  useFactory: () => {\n    return {\n      secret: appConfig().appSecret,\n      signOptions: { expiresIn: '1d' },\n    };\n  },\n};\n"
  },
  {
    "path": "server/src/config/typeorm.config-migrations.ts",
    "content": "import { typeOrmConfig } from './typeorm.config';\nexport = typeOrmConfig;\n"
  },
  {
    "path": "server/src/config/typeorm.config.ts",
    "content": "import { ConfigModule, ConfigService } from '@nestjs/config';\nimport {\n  TypeOrmModuleAsyncOptions,\n  TypeOrmModuleOptions,\n} from '@nestjs/typeorm';\n\nexport const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = {\n  imports: [ConfigModule],\n  inject: [ConfigService],\n  useFactory: async (): Promise<TypeOrmModuleOptions> => {\n    return {\n      type: 'mysql',\n      host: process.env.DB_HOST,\n      port: parseInt(process.env.DB_PORT, 10),\n      username: process.env.DB_USERNAME,\n      database: process.env.DB_NAME,\n      password: process.env.DB_PASSWORD,\n      entities: [__dirname + '/../**/*.entity.{js,ts}'],\n      migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],\n      cli: {\n        migrationsDir: __dirname + '/../database/migrations',\n      },\n      extra: {\n        charset: 'utf8mb4_unicode_ci',\n      },\n      synchronize: false,\n      logging: true,\n    };\n  },\n};\n\nexport const typeOrmConfig: TypeOrmModuleOptions = {\n  type: 'mysql',\n  host: process.env.DB_HOST,\n  port: parseInt(process.env.DB_PORT, 10),\n  username: process.env.DB_USERNAME,\n  database: process.env.DB_NAME,\n  password: process.env.DB_PASSWORD,\n  entities: [__dirname + '/../**/*.entity.{js,ts}'],\n  migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],\n  cli: {\n    migrationsDir: __dirname + '/../database/migrations',\n  },\n  extra: {\n    charset: 'utf8mb4_unicode_ci',\n  },\n  synchronize: false,\n  logging: true,\n};\n"
  },
  {
    "path": "server/src/database/data/quiz.data.ts",
    "content": "interface ISampleData {\n  quizTitle: string;\n  quizDescription: string;\n  questions: Array<IQuestionData>;\n}\n\ninterface IQuestionData {\n  question: string;\n  options: Array<{ text: string; isCorrect: boolean }>;\n}\n\nexport const quizSampleData: Array<ISampleData> = [\n  {\n    quizTitle: 'Laravel beginner level quiz',\n    quizDescription:\n      'In this quiz, you are going to be asked some basic questions which will target your knowledge of Laravel',\n    questions: [\n      {\n        question: 'How to put Laravel applications in maintenance mode?',\n        options: [\n          { text: 'php artisan maintenance:on', isCorrect: false },\n          { text: 'php artisan down', isCorrect: true },\n          { text: 'php artisan maintenance:up', isCorrect: false },\n          { text: 'php artisan maintenance:down', isCorrect: false },\n        ],\n      },\n      {\n        question: 'What is the role of Service provider?',\n        options: [\n          {\n            text: 'They allow Laravel to know about the packages which are present and how to bootstrap them',\n            isCorrect: true,\n          },\n          {\n            text: 'They allow Laravel to provide services for individual modules',\n            isCorrect: false,\n          },\n        ],\n      },\n    ],\n  },\n  {\n    quizTitle: 'React Js beginner level quiz',\n    quizDescription:\n      'In this quiz, you are going to be asked some basic questions which will target your knowledge of React Js',\n    questions: [\n      {\n        question: 'How to put Laravel applications in maintenance mode?',\n        options: [\n          { text: 'php artisan maintenance:on', isCorrect: false },\n          { text: 'php artisan down', isCorrect: true },\n          { text: 'php artisan maintenance:up', isCorrect: false },\n          { text: 'php artisan maintenance:down', isCorrect: false },\n        ],\n      },\n    ],\n  },\n];\n"
  },
  {
    "path": "server/src/database/factories/quiz.factory.ts",
    "content": "import { randDatabaseColumn, randParagraph, randSentence } from '@ngneat/falso';\nimport { define } from 'typeorm-seeding';\nimport { Quiz } from '../../modules/quiz/entities/quiz.entity';\n\ndefine(Quiz, () => {\n  const quiz = new Quiz();\n  quiz.title = randSentence();\n  quiz.description = randParagraph();\n  return quiz;\n});\n"
  },
  {
    "path": "server/src/database/factories/user.factory.ts",
    "content": "import { randEmail, randFullName, randPassword } from '@ngneat/falso';\nimport { define } from 'typeorm-seeding';\nimport { User } from '../../modules/user/user.entity';\n\ndefine(User, () => {\n  const user = new User();\n  user.name = randFullName();\n  user.email = randEmail();\n  user.password = randPassword();\n  return user;\n});\n"
  },
  {
    "path": "server/src/database/migrations/1649938237326-BaseMigrations.ts",
    "content": "import {MigrationInterface, QueryRunner} from \"typeorm\";\n\nexport class BaseMigrations1649938237326 implements MigrationInterface {\n    name = 'BaseMigrations1649938237326'\n\n    public async up(queryRunner: QueryRunner): Promise<void> {\n        await queryRunner.query(`CREATE TABLE \\`users\\` (\\`id\\` int NOT NULL AUTO_INCREMENT, \\`name\\` varchar(255) NOT NULL, \\`email\\` varchar(255) NOT NULL, \\`password\\` varchar(255) NOT NULL, \\`createdAt\\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \\`updatedAt\\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), UNIQUE INDEX \\`IDX_97672ac88f789774dd47f7c8be\\` (\\`email\\`), PRIMARY KEY (\\`id\\`)) ENGINE=InnoDB`);\n        await queryRunner.query(`CREATE TABLE \\`quizes\\` (\\`id\\` int NOT NULL AUTO_INCREMENT COMMENT 'The quiz unique identifier', \\`title\\` varchar(255) NOT NULL, \\`description\\` text NOT NULL, \\`isActive\\` tinyint NOT NULL DEFAULT '1', PRIMARY KEY (\\`id\\`)) ENGINE=InnoDB`);\n        await queryRunner.query(`CREATE TABLE \\`questions\\` (\\`id\\` int NOT NULL AUTO_INCREMENT, \\`question\\` varchar(255) NOT NULL, \\`quizId\\` int NULL COMMENT 'The quiz unique identifier', PRIMARY KEY (\\`id\\`)) ENGINE=InnoDB`);\n        await queryRunner.query(`CREATE TABLE \\`options\\` (\\`id\\` int NOT NULL AUTO_INCREMENT, \\`text\\` varchar(255) NOT NULL, \\`isCorrect\\` tinyint NOT NULL, \\`questionId\\` int NULL, PRIMARY KEY (\\`id\\`)) ENGINE=InnoDB`);\n        await queryRunner.query(`ALTER TABLE \\`questions\\` ADD CONSTRAINT \\`FK_35d54f06d12ea78d4842aed6b6d\\` FOREIGN KEY (\\`quizId\\`) REFERENCES \\`quizes\\`(\\`id\\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);\n        await queryRunner.query(`ALTER TABLE \\`options\\` ADD CONSTRAINT \\`FK_46b668c49a6c4154d4643d875a5\\` FOREIGN KEY (\\`questionId\\`) REFERENCES \\`questions\\`(\\`id\\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);\n    }\n\n    public async down(queryRunner: QueryRunner): Promise<void> {\n        await queryRunner.query(`ALTER TABLE \\`options\\` DROP FOREIGN KEY \\`FK_46b668c49a6c4154d4643d875a5\\``);\n        await queryRunner.query(`ALTER TABLE \\`questions\\` DROP FOREIGN KEY \\`FK_35d54f06d12ea78d4842aed6b6d\\``);\n        await queryRunner.query(`DROP TABLE \\`options\\``);\n        await queryRunner.query(`DROP TABLE \\`questions\\``);\n        await queryRunner.query(`DROP TABLE \\`quizes\\``);\n        await queryRunner.query(`DROP INDEX \\`IDX_97672ac88f789774dd47f7c8be\\` ON \\`users\\``);\n        await queryRunner.query(`DROP TABLE \\`users\\``);\n    }\n\n}\n"
  },
  {
    "path": "server/src/database/migrations/1651075479367-add_user_role_column.ts",
    "content": "import {MigrationInterface, QueryRunner} from \"typeorm\";\n\nexport class addUserRoleColumn1651075479367 implements MigrationInterface {\n    name = 'addUserRoleColumn1651075479367'\n\n    public async up(queryRunner: QueryRunner): Promise<void> {\n        await queryRunner.query(`ALTER TABLE \\`users\\` ADD \\`role\\` enum ('admin', 'member') NOT NULL DEFAULT 'member'`);\n    }\n\n    public async down(queryRunner: QueryRunner): Promise<void> {\n        await queryRunner.query(`ALTER TABLE \\`users\\` DROP COLUMN \\`role\\``);\n    }\n\n}\n"
  },
  {
    "path": "server/src/database/seeds/setup-data.seed.ts",
    "content": "import { Connection, getManager } from 'typeorm';\nimport { Factory, Seeder } from 'typeorm-seeding';\nimport { Option } from '../../modules/quiz/entities/option.entity';\nimport { Question } from '../../modules/quiz/entities/question.entity';\nimport { Quiz } from '../../modules/quiz/entities/quiz.entity';\nimport { quizSampleData } from '../data/quiz.data';\n\nexport class SetupData implements Seeder {\n  public async run(factory: Factory, connection: Connection): Promise<void> {\n    console.log('quizSampleData', quizSampleData);\n    await getManager().query('SET foreign_key_checks = 0');\n    await getManager().query('TRUNCATE quizes');\n    await getManager().query('TRUNCATE questions');\n    await getManager().query('TRUNCATE options');\n    await getManager().query('SET foreign_key_checks = 1');\n\n    for (let i = 0; i < quizSampleData.length; i++) {\n      const { quizTitle, quizDescription, questions } = quizSampleData[i];\n\n      const quiz = new Quiz();\n      quiz.title = quizTitle;\n      quiz.description = quizDescription;\n      await quiz.save();\n\n      for (let j = 0; j < questions.length; j++) {\n        const { question, options } = questions[j];\n\n        const que = new Question();\n        que.question = question;\n        que.quiz = quiz;\n        await que.save();\n\n        for (let k = 0; k < options.length; k++) {\n          const { isCorrect, text } = options[k];\n          const opt = new Option();\n          opt.isCorrect = isCorrect;\n          opt.text = text;\n          opt.question = que;\n          await opt.save();\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "server/src/database/seeds/user-create.seed.ts",
    "content": "import { Connection, getManager } from 'typeorm';\nimport { Factory, Seeder } from 'typeorm-seeding';\nimport { UserRoles } from '../../modules/user/enums/user.enum';\nimport { User } from '../../modules/user/user.entity';\n\nexport class UserCreateSeed implements Seeder {\n  public async run(factory: Factory, connection: Connection): Promise<void> {\n    await getManager().query('TRUNCATE users');\n    await factory(User)().create({\n      name: 'Amitav Roy',\n      email: 'reachme@amitavroy.com',\n      password: 'Password@123',\n      role: UserRoles.ADMIN,\n    });\n    // await factory(User)().createMany(20);\n  }\n}\n"
  },
  {
    "path": "server/src/main.ts",
    "content": "import { NestFactory } from '@nestjs/core';\nimport { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';\nimport { AppModule } from './app.module';\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n  app.enableCors();\n\n  const config = new DocumentBuilder()\n    .addBearerAuth()\n    .setTitle('Quiz manager API')\n    .setDescription('Quiz manager API description')\n    .setVersion('1.0')\n    .build();\n  const document = SwaggerModule.createDocument(app, config);\n  SwaggerModule.setup('api', app, document);\n\n  await app.listen(process.env.PORT || 3000);\n}\nbootstrap();\n"
  },
  {
    "path": "server/src/modules/auth/admin-role.guard.ts",
    "content": "import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { UserRoles } from '../user/enums/user.enum';\nimport { UserService } from '../user/user.service';\n\n@Injectable()\nexport class AdminRoleGuard implements CanActivate {\n  constructor(private userService: UserService) {}\n\n  async canActivate(context: ExecutionContext) {\n    const request = context.switchToHttp().getRequest();\n\n    if (request?.user) {\n      const { id } = request.user;\n      const user = await this.userService.getUserById(id);\n      return user.role === UserRoles.ADMIN;\n    }\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "server/src/modules/auth/auth.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  Post,\n  Request,\n  UseGuards,\n} from '@nestjs/common';\nimport { ApiBearerAuth, ApiTags } from '@nestjs/swagger';\nimport { AuthService } from './auth.service';\nimport { LoginDto } from './dto/login.dto';\nimport { JwtAuthGuard } from './jwt-auth.guard';\nimport { LocalAuthGuard } from './local-auth.guard';\n\n@ApiTags('Auth')\n@Controller('auth')\nexport class AuthController {\n  constructor(private authService: AuthService) {}\n\n  @UseGuards(LocalAuthGuard)\n  @Post('login')\n  async login(@Request() req, @Body() loginDto: LoginDto): Promise<any> {\n    return this.authService.generateToken(req.user);\n  }\n\n  @ApiBearerAuth()\n  @UseGuards(JwtAuthGuard)\n  @Get('user')\n  async user(@Request() req): Promise<any> {\n    return req.user;\n  }\n}\n"
  },
  {
    "path": "server/src/modules/auth/auth.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { AuthService } from './auth.service';\nimport { AuthController } from './auth.controller';\nimport { LocalStrategy } from './local.strategy';\nimport { UserModule } from '../user/user.module';\nimport { PassportModule } from '@nestjs/passport';\nimport { JwtModule } from '@nestjs/jwt';\nimport { JwtStrategy } from './jwt.strategy';\nimport { jwtConfig } from '../../config/jwt.config';\n\n@Module({\n  imports: [UserModule, PassportModule, JwtModule.registerAsync(jwtConfig)],\n  providers: [AuthService, LocalStrategy, JwtStrategy],\n  controllers: [AuthController],\n})\nexport class AuthModule {}\n"
  },
  {
    "path": "server/src/modules/auth/auth.service.ts",
    "content": "import {\n  BadRequestException,\n  Injectable,\n  UnauthorizedException,\n} from '@nestjs/common';\nimport { UserService } from '../user/user.service';\nimport * as bcrypt from 'bcrypt';\nimport { JwtService } from '@nestjs/jwt';\n\n@Injectable()\nexport class AuthService {\n  constructor(\n    private userService: UserService,\n    private jwtService: JwtService,\n  ) {}\n\n  async validateUserCreds(email: string, password: string): Promise<any> {\n    const user = await this.userService.getUserByEmail(email);\n\n    if (!user) throw new BadRequestException();\n\n    if (!(await bcrypt.compare(password, user.password)))\n      throw new UnauthorizedException();\n\n    return user;\n  }\n\n  generateToken(user: any) {\n    return {\n      access_token: this.jwtService.sign({\n        name: user.name,\n        sub: user.id,\n      }),\n    };\n  }\n}\n"
  },
  {
    "path": "server/src/modules/auth/dto/login.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsEmail, IsNotEmpty } from 'class-validator';\n\nexport class LoginDto {\n  @ApiProperty({\n    description: 'Email address of the user',\n    example: 'reachme@amitavroy.com',\n  })\n  @IsNotEmpty()\n  @IsEmail()\n  username: string;\n\n  @ApiProperty({\n    description: 'Password in plain text',\n    example: 'Password@123',\n  })\n  @IsNotEmpty()\n  password: string;\n}\n"
  },
  {
    "path": "server/src/modules/auth/jwt-auth.guard.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { AuthGuard } from '@nestjs/passport';\n\n@Injectable()\nexport class JwtAuthGuard extends AuthGuard('jwt') {}\n"
  },
  {
    "path": "server/src/modules/auth/jwt.strategy.ts",
    "content": "import { PassportStrategy } from '@nestjs/passport';\nimport { ExtractJwt, Strategy } from 'passport-jwt';\nimport appConfig from '../../config/app.config';\n\nexport class JwtStrategy extends PassportStrategy(Strategy) {\n  constructor() {\n    super({\n      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),\n      secretOrKey: appConfig().appSecret,\n    });\n  }\n\n  async validate(payload: any) {\n    return {\n      id: payload.sub,\n      name: payload.name,\n      tenant: 'amitav',\n    };\n  }\n}\n"
  },
  {
    "path": "server/src/modules/auth/local-auth.guard.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { AuthGuard } from '@nestjs/passport';\n\n@Injectable()\nexport class LocalAuthGuard extends AuthGuard('local') {}\n"
  },
  {
    "path": "server/src/modules/auth/local.strategy.ts",
    "content": "import { Injectable, UnauthorizedException } from '@nestjs/common';\nimport { PassportStrategy } from '@nestjs/passport';\nimport { Strategy } from 'passport-local';\nimport { AuthService } from './auth.service';\n\n@Injectable()\nexport class LocalStrategy extends PassportStrategy(Strategy) {\n  constructor(private authService: AuthService) {\n    super();\n  }\n\n  async validate(email: string, password: string) {\n    const user = await this.authService.validateUserCreds(email, password);\n    if (!user) throw new UnauthorizedException();\n    return user;\n  }\n}\n"
  },
  {
    "path": "server/src/modules/auth/roles.decorator.ts",
    "content": "import { SetMetadata } from '@nestjs/common';\n\nexport const Roles = (...roles: string[]) => SetMetadata('roles', roles);\n"
  },
  {
    "path": "server/src/modules/auth/roles.guard.ts",
    "content": "import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { UserService } from '../user/user.service';\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n  constructor(private reflector: Reflector, private userService: UserService) {}\n\n  async canActivate(context: ExecutionContext): Promise<boolean> {\n    const roles = this.reflector.get<string[]>('roles', context.getHandler());\n    const request = context.switchToHttp().getRequest();\n\n    if (request?.user) {\n      const { id } = request.user;\n      const user = await this.userService.getUserById(id);\n      return roles.includes(user.role);\n    }\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/controllers/option.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Post,\n  UsePipes,\n  ValidationPipe,\n} from '@nestjs/common';\nimport { ApiBearerAuth, ApiCreatedResponse, ApiTags } from '@nestjs/swagger';\nimport { CreateOptionDto } from '../dto/create-option.dto';\nimport { Option } from '../entities/option.entity';\nimport { OptionService } from '../services/option.service';\nimport { QuestionService } from '../services/question.service';\n\n@ApiTags('Questions')\n@ApiBearerAuth()\n@Controller('question/option')\nexport class OptionController {\n  constructor(\n    private optionService: OptionService,\n    private questionService: QuestionService,\n  ) {}\n\n  @Post('')\n  @UsePipes(ValidationPipe)\n  @ApiCreatedResponse({\n    description: 'The option that got created',\n    type: Option,\n  })\n  async saveOptionToQuestion(@Body() createOption: CreateOptionDto) {\n    const question = await this.questionService.findQuestionById(\n      createOption.questionId,\n    );\n    const option = await this.optionService.creatOption(createOption, question);\n    return { question, createOption, option };\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/controllers/question.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  Get,\n  Post,\n  UsePipes,\n  ValidationPipe,\n} from '@nestjs/common';\nimport { ApiBearerAuth, ApiCreatedResponse, ApiTags } from '@nestjs/swagger';\nimport { CreateQuestionDto } from '../dto/create-question.dto';\nimport { Question } from '../entities/question.entity';\nimport { QuestionService } from '../services/question.service';\nimport { QuizService } from '../services/quiz.service';\n\n@ApiTags('Questions')\n@ApiBearerAuth()\n@Controller('question')\nexport class QuestionController {\n  constructor(\n    private questionService: QuestionService,\n    private quizService: QuizService,\n  ) {}\n\n  @Post('')\n  @UsePipes(ValidationPipe)\n  @ApiCreatedResponse({\n    description: 'Question added to a quiz',\n    type: Question,\n  })\n  async saveQuestion(@Body() question: CreateQuestionDto): Promise<Question> {\n    const quiz = await this.quizService.getQuizById(question.quizId);\n    return await this.questionService.createQuestion(question, quiz);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/controllers/quiz.controller.ts",
    "content": "import {\n  Body,\n  Controller,\n  DefaultValuePipe,\n  Get,\n  Param,\n  ParseIntPipe,\n  Post,\n  Query,\n  UseGuards,\n  UsePipes,\n  ValidationPipe,\n} from '@nestjs/common';\nimport {\n  ApiCreatedResponse,\n  ApiOkResponse,\n  ApiSecurity,\n  ApiTags,\n} from '@nestjs/swagger';\nimport { IPaginationOptions, Pagination } from 'nestjs-typeorm-paginate';\nimport { ApiPaginatedResponse } from '../../../common/decorator/api-pagination.response';\nimport { AdminRoleGuard } from '../../auth/admin-role.guard';\nimport { JwtAuthGuard } from '../../auth/jwt-auth.guard';\nimport { Roles } from '../../auth/roles.decorator';\nimport { RolesGuard } from '../../auth/roles.guard';\n\nimport { CreateQuizDto } from '../dto/create-quiz.dto';\nimport { Quiz } from '../entities/quiz.entity';\nimport { QuizService } from '../services/quiz.service';\n\n@ApiTags('Quiz')\n@Controller('quiz')\n@ApiSecurity('bearer')\n@UseGuards(JwtAuthGuard)\nexport class QuizController {\n  constructor(private quizService: QuizService) {}\n\n  @Get('/')\n  @ApiPaginatedResponse({ model: Quiz, description: 'List of quizzes' })\n  async getAllQuiz(\n    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,\n    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 1,\n  ): Promise<Pagination<Quiz>> {\n    const options: IPaginationOptions = {\n      limit,\n      page,\n    };\n    return await this.quizService.paginate(options);\n  }\n\n  @Get('/:id')\n  @ApiOkResponse({ description: 'Get a quiz by id', type: Quiz })\n  async getQuizById(@Param('id', ParseIntPipe) id: number): Promise<Quiz> {\n    return await this.quizService.getQuizById(id);\n  }\n\n  @ApiCreatedResponse({ description: 'The quiz that got created', type: Quiz })\n  @Post('/create')\n  @UsePipes(ValidationPipe)\n  @UseGuards(RolesGuard)\n  @Roles('admin')\n  async createQuiz(@Body() quizData: CreateQuizDto): Promise<Quiz> {\n    return await this.quizService.createNewQuiz(quizData);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/controllers/response.controller.ts",
    "content": "import { Controller, Post } from '@nestjs/common';\nimport { EventEmitter2 } from '@nestjs/event-emitter';\nimport { ApiTags } from '@nestjs/swagger';\nimport { events } from '../../../common/constants/event.constants';\nimport { ResponseAddEvent } from '../events/response-add.event';\n\n@Controller('/response')\n@ApiTags('Response')\nexport class ResponseController {\n  constructor(private eventEmitter: EventEmitter2) {}\n\n  @Post('')\n  async handleQuestionResponse() {\n    // insert data into the response table\n    console.log('This is inside the controller');\n\n    const payload = new ResponseAddEvent();\n    payload.userId = 1;\n    payload.optionId = 33;\n\n    this.eventEmitter.emit(events.RESPONSE_SUBMITTED, payload);\n\n    return { message: 'Response taken' };\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/dto/create-option.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class CreateOptionDto {\n  @ApiProperty({\n    description: 'The option for a question',\n    example: 'Owl',\n  })\n  @IsNotEmpty()\n  @Length(2, 255)\n  text: string;\n\n  @ApiProperty({\n    description: 'The ID of the question',\n    example: 1,\n  })\n  @IsNotEmpty()\n  questionId: number;\n\n  @ApiProperty({\n    description: 'Whether the option is correct or not',\n    example: true,\n  })\n  @IsNotEmpty()\n  isCorrect: boolean;\n}\n"
  },
  {
    "path": "server/src/modules/quiz/dto/create-question.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class CreateQuestionDto {\n  @ApiProperty({\n    description: 'The actual question',\n    example: 'A sample question',\n  })\n  @IsNotEmpty()\n  @Length(3, 255)\n  question: string;\n\n  @ApiProperty({\n    description: 'The quiz id to which the question is associated.',\n    example: 1,\n  })\n  @IsNotEmpty()\n  quizId: number;\n}\n"
  },
  {
    "path": "server/src/modules/quiz/dto/create-quiz.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class CreateQuizDto {\n  @ApiProperty({\n    description: 'The title of the quiz',\n    example: 'How good are your with Laravel?',\n  })\n  @IsNotEmpty({ message: 'The quiz should have a title' })\n  @Length(3, 255)\n  title: string;\n\n  @ApiProperty({\n    description: 'A small description for the user',\n    example:\n      'This quiz will ask your questions on Laravel and test your knowledge.',\n  })\n  @IsNotEmpty()\n  @Length(3)\n  description: string;\n}\n"
  },
  {
    "path": "server/src/modules/quiz/entities/option.entity.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  BaseEntity,\n  Column,\n  Entity,\n  ManyToOne,\n  PrimaryGeneratedColumn,\n} from 'typeorm';\nimport { Question } from './question.entity';\n\n@Entity('options')\nexport class Option extends BaseEntity {\n  @ApiProperty({ description: 'Primary key as Option ID', example: 1 })\n  @PrimaryGeneratedColumn()\n  id: number;\n\n  @ApiProperty({ description: 'The actual option', example: 'Owl' })\n  @Column({\n    type: 'varchar',\n  })\n  text: string;\n\n  @ApiProperty({ description: 'Whether option is correct', example: true })\n  @Column({\n    type: 'boolean',\n  })\n  isCorrect: boolean;\n\n  @ManyToOne(() => Question, (question) => question.options)\n  question: Question;\n}\n"
  },
  {
    "path": "server/src/modules/quiz/entities/question.entity.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  BaseEntity,\n  Column,\n  Entity,\n  ManyToOne,\n  OneToMany,\n  PrimaryGeneratedColumn,\n} from 'typeorm';\nimport { Option } from './option.entity';\nimport { Quiz } from './quiz.entity';\n\n@Entity('questions')\nexport class Question extends BaseEntity {\n  @ApiProperty({\n    description: 'The primary ID of question.',\n    example: 1,\n  })\n  @PrimaryGeneratedColumn()\n  id: number;\n\n  @ApiProperty({\n    description: 'The actual question',\n    example: 'What is the question?',\n  })\n  @Column({\n    type: 'varchar',\n  })\n  question: string;\n\n  @ApiProperty({\n    description: 'Quiz of the question',\n  })\n  @ManyToOne(() => Quiz, (quiz) => quiz.questions)\n  quiz: Quiz;\n\n  @ApiProperty({\n    description: 'Options of the question',\n  })\n  @OneToMany(() => Option, (option) => option.question)\n  options: Option[];\n}\n"
  },
  {
    "path": "server/src/modules/quiz/entities/quiz.entity.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n  BaseEntity,\n  Column,\n  Entity,\n  OneToMany,\n  PrimaryGeneratedColumn,\n} from 'typeorm';\nimport { Question } from './question.entity';\n\n@Entity('quizes')\nexport class Quiz extends BaseEntity {\n  @ApiProperty({ description: 'Primary key as Quiz ID', example: 1 })\n  @PrimaryGeneratedColumn({\n    comment: 'The quiz unique identifier',\n  })\n  id: number;\n\n  @ApiProperty({\n    description: 'Title of the quiz',\n    example: 'Sample Laravel quiz',\n  })\n  @Column({\n    type: 'varchar',\n  })\n  title: string;\n\n  @ApiProperty({\n    description: 'Description of the quiz',\n    example: 'Lorem ipsum',\n  })\n  @Column({\n    type: 'text',\n  })\n  description: string;\n\n  @ApiProperty({\n    description: 'Quiz active or inactive state',\n    example: true,\n  })\n  @Column({\n    type: 'boolean',\n    default: 1,\n  })\n  isActive: boolean;\n\n  @ApiProperty({\n    description: 'List of questions',\n  })\n  @OneToMany(() => Question, (question) => question.quiz)\n  questions: Question[];\n}\n"
  },
  {
    "path": "server/src/modules/quiz/events/response-add.event.ts",
    "content": "export class ResponseAddEvent {\n  userId: number;\n  optionId: number;\n}\n"
  },
  {
    "path": "server/src/modules/quiz/quiz.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { QuestionController } from './controllers/question.controller';\nimport { QuestionRepository } from './repositories/question.repository';\nimport { QuestionService } from './services/question.service';\nimport { QuizController } from './controllers/quiz.controller';\nimport { QuizRepository } from './repositories/quiz.repository';\nimport { QuizService } from './services/quiz.service';\nimport { OptionRepository } from './repositories/option.repository';\nimport { OptionController } from './controllers/option.controller';\nimport { OptionService } from './services/option.service';\nimport { UserModule } from '../user/user.module';\nimport { ResponseController } from './controllers/response.controller';\nimport { ResponseService } from './services/response.service';\n\n@Module({\n  controllers: [\n    QuizController,\n    QuestionController,\n    OptionController,\n    ResponseController,\n  ],\n  imports: [\n    TypeOrmModule.forFeature([\n      QuizRepository,\n      QuestionRepository,\n      OptionRepository,\n    ]),\n    UserModule,\n  ],\n  providers: [QuizService, QuestionService, OptionService, ResponseService],\n})\nexport class QuizModule {}\n"
  },
  {
    "path": "server/src/modules/quiz/repositories/option.repository.ts",
    "content": "import { EntityRepository, Repository } from 'typeorm';\nimport { Option } from '../entities/option.entity';\n\n@EntityRepository(Option)\nexport class OptionRepository extends Repository<Option> {}\n"
  },
  {
    "path": "server/src/modules/quiz/repositories/question.repository.ts",
    "content": "import { EntityRepository, Repository } from 'typeorm';\nimport { Question } from '../entities/question.entity';\n\n@EntityRepository(Question)\nexport class QuestionRepository extends Repository<Question> {}\n"
  },
  {
    "path": "server/src/modules/quiz/repositories/quiz.repository.ts",
    "content": "import { EntityRepository, Repository } from 'typeorm';\nimport { Quiz } from '../entities/quiz.entity';\n\n@EntityRepository(Quiz)\nexport class QuizRepository extends Repository<Quiz> {}\n"
  },
  {
    "path": "server/src/modules/quiz/services/option.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { CreateOptionDto } from '../dto/create-option.dto';\nimport { Question } from '../entities/question.entity';\nimport { OptionRepository } from '../repositories/option.repository';\n\n@Injectable()\nexport class OptionService {\n  constructor(\n    @InjectRepository(OptionRepository)\n    private optionRepository: OptionRepository,\n  ) {}\n\n  async creatOption(option: CreateOptionDto, question: Question) {\n    const newOption = await this.optionRepository.save({\n      text: option.text,\n      isCorrect: option.isCorrect,\n    });\n\n    question.options = [...question.options, newOption];\n    await question.save();\n\n    return newOption;\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/services/question.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { CreateQuestionDto } from '../dto/create-question.dto';\nimport { Question } from '../entities/question.entity';\nimport { QuestionRepository } from '../repositories/question.repository';\nimport { Quiz } from '../entities/quiz.entity';\n\n@Injectable()\nexport class QuestionService {\n  constructor(\n    @InjectRepository(QuestionRepository)\n    private questionRepository: QuestionRepository,\n  ) {}\n\n  async findQuestionById(id: number): Promise<Question> {\n    return await this.questionRepository.findOne(id, {\n      relations: ['quiz', 'options'],\n    });\n  }\n\n  async createQuestion(\n    question: CreateQuestionDto,\n    quiz: Quiz,\n  ): Promise<Question> {\n    const newQuestion = await this.questionRepository.save({\n      question: question.question,\n    });\n\n    quiz.questions = [...quiz.questions, newQuestion];\n    await quiz.save();\n\n    return newQuestion;\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/services/quiz.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { OnEvent } from '@nestjs/event-emitter';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport {\n  IPaginationOptions,\n  paginate,\n  Pagination,\n} from 'nestjs-typeorm-paginate';\nimport { events } from '../../../common/constants/event.constants';\n\nimport { CreateQuizDto } from '../dto/create-quiz.dto';\nimport { Quiz } from '../entities/quiz.entity';\nimport { ResponseAddEvent } from '../events/response-add.event';\nimport { QuizRepository } from '../repositories/quiz.repository';\n\n@Injectable()\nexport class QuizService {\n  constructor(\n    @InjectRepository(QuizRepository) private quizRepository: QuizRepository,\n  ) {}\n\n  async getAllQuiz(): Promise<Quiz[]> {\n    return await this.quizRepository\n      .createQueryBuilder('q')\n      .leftJoinAndSelect('q.questions', 'qt')\n      .getMany();\n  }\n\n  async paginate(options: IPaginationOptions): Promise<Pagination<Quiz>> {\n    const qb = this.quizRepository.createQueryBuilder('q');\n    qb.orderBy('q.id', 'DESC');\n\n    return paginate<Quiz>(qb, options);\n  }\n\n  async getQuizById(id: number): Promise<Quiz> {\n    return await this.quizRepository.findOne(id, {\n      relations: ['questions', 'questions.options'],\n    });\n  }\n\n  async createNewQuiz(quiz: CreateQuizDto) {\n    return await this.quizRepository.save(quiz);\n  }\n\n  @OnEvent(events.RESPONSE_SUBMITTED)\n  checkQuizCompeleted(payload: ResponseAddEvent) {\n    console.log('checkQuizCompeleted', payload);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/quiz/services/response.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { OnEvent } from '@nestjs/event-emitter';\nimport { events } from '../../../common/constants/event.constants';\nimport { ResponseAddEvent } from '../events/response-add.event';\n\n@Injectable()\nexport class ResponseService {\n  @OnEvent(events.RESPONSE_SUBMITTED)\n  handleIfResponseIsCorrect(payload: ResponseAddEvent) {\n    console.log('handleIfResponseIsCorrect', payload);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/search/controllers/search.controller.ts",
    "content": "import { Body, Controller, Get, Post } from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { SearchMovieDto } from '../dto/search-movie.dto';\nimport { SearchService } from '../search.service';\n\n@ApiTags('Search')\n@Controller('search')\nexport class SearchController {\n  constructor(private searchService: SearchService) {}\n\n  @Get('/')\n  public async getSearch() {\n    const resp = await this.searchService.addDocuments([\n      { id: 1, title: 'Carol', genres: ['Romance', 'Drama'] },\n      { id: 2, title: 'Wonder Woman', genres: ['Action', 'Adventure'] },\n      { id: 3, title: 'Life of Pi', genres: ['Adventure', 'Drama'] },\n      {\n        id: 4,\n        title: 'Mad Max: Fury Road',\n        genres: ['Adventure', 'Science Fiction'],\n      },\n      { id: 5, title: 'Moana', genres: ['Fantasy', 'Action'] },\n      { id: 6, title: 'Philadelphia', genres: ['Drama'] },\n      { id: 7, title: 'Kung Fu Panda', genres: ['Cartoon', 'Drama'] },\n    ]);\n\n    console.log(resp);\n  }\n\n  @Post('/')\n  public async searchMovie(@Body() search: SearchMovieDto) {\n    return await this.searchService.search(search.text, {\n      attributesToHighlight: ['*'],\n    });\n  }\n}\n"
  },
  {
    "path": "server/src/modules/search/dto/search-movie.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class SearchMovieDto {\n  @ApiProperty({\n    description: 'The name of the movie that you want to search',\n    example: 'wo',\n  })\n  @IsNotEmpty()\n  @Length(2, 255)\n  text: string;\n}\n"
  },
  {
    "path": "server/src/modules/search/search.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { SearchController } from './controllers/search.controller';\nimport { SearchService } from './search.service';\n\n@Module({\n  controllers: [SearchController],\n  providers: [SearchService],\n})\nexport class SearchModule {}\n"
  },
  {
    "path": "server/src/modules/search/search.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport MeiliSearch, { Index, SearchParams } from 'meilisearch';\n\n@Injectable()\nexport class SearchService {\n  private _client: MeiliSearch;\n  constructor() {\n    this._client = new MeiliSearch({\n      host: 'http://192.168.1.141:7700/',\n    });\n  }\n\n  private getMovieIndex(): Index {\n    return this._client.index('movies');\n  }\n\n  public async addDocuments(documents) {\n    const index = this.getMovieIndex();\n    return await index.addDocuments(documents);\n  }\n\n  public async search(text: string, searchParams?: SearchParams) {\n    console.log(searchParams);\n    const index = this.getMovieIndex();\n    return await index.search(text, searchParams);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/user/dto/user-register.req.dto.ts",
    "content": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsEmail, IsNotEmpty, Length, Matches } from 'class-validator';\nimport { MESSAGES, REGEX } from 'src/app.utils';\n\nexport class UserRegisterRequestDto {\n  @ApiProperty({\n    description: 'The name of the User',\n    example: 'Jhon Doe',\n  })\n  @IsNotEmpty()\n  name: string;\n\n  @ApiProperty({\n    description: 'The email address of the User',\n    example: 'jhon.doe@gmail.com',\n  })\n  @IsNotEmpty()\n  @IsEmail()\n  email: string;\n\n  @ApiProperty({\n    description: 'The password of the User',\n    example: 'Password@123',\n  })\n  @IsNotEmpty()\n  @Length(8, 24)\n  @Matches(REGEX.PASSWORD_RULE, { message: MESSAGES.PASSWORD_RULE_MESSAGE })\n  password: string;\n\n  @ApiProperty({\n    description: 'Confirm the password',\n    example: 'Password@123',\n  })\n  @IsNotEmpty()\n  @Length(8, 24)\n  @Matches(REGEX.PASSWORD_RULE, { message: MESSAGES.PASSWORD_RULE_MESSAGE })\n  confirm: string;\n}\n"
  },
  {
    "path": "server/src/modules/user/enums/user.enum.ts",
    "content": "enum UserRoles {\n  ADMIN = 'admin',\n  MEMBER = 'member',\n}\n\nexport { UserRoles };\n"
  },
  {
    "path": "server/src/modules/user/user.controller.ts",
    "content": "import { Body, Controller, Post } from '@nestjs/common';\nimport {\n  ApiBadRequestResponse,\n  ApiCreatedResponse,\n  ApiTags,\n} from '@nestjs/swagger';\nimport { SETTINGS } from 'src/app.utils';\n\nimport { UserRegisterRequestDto } from './dto/user-register.req.dto';\nimport { User } from './user.entity';\nimport { UserService } from './user.service';\n\n@ApiTags('User')\n@Controller('user')\nexport class UserController {\n  constructor(private userService: UserService) {}\n\n  @Post('/register')\n  @ApiCreatedResponse({\n    description: 'Created user object as response',\n    type: User,\n  })\n  @ApiBadRequestResponse({ description: 'User cannot register. Try again!' })\n  async doUserRegistration(\n    @Body(SETTINGS.VALIDATION_PIPE)\n    userRegister: UserRegisterRequestDto,\n  ): Promise<User> {\n    return await this.userService.doUserRegistration(userRegister);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/user/user.entity.ts",
    "content": "import {\n  BaseEntity,\n  BeforeInsert,\n  Column,\n  CreateDateColumn,\n  Entity,\n  PrimaryGeneratedColumn,\n  UpdateDateColumn,\n} from 'typeorm';\nimport * as bcrypt from 'bcrypt';\nimport { ApiProperty } from '@nestjs/swagger';\nimport { UserRoles } from './enums/user.enum';\n\n@Entity({ name: 'users' })\nexport class User extends BaseEntity {\n  @ApiProperty({ description: 'Primary key as User ID', example: 1 })\n  @PrimaryGeneratedColumn()\n  id: number;\n\n  @ApiProperty({ description: 'User name', example: 'Jhon Doe' })\n  @Column()\n  name: string;\n\n  @ApiProperty({\n    description: 'User email address',\n    example: 'jhon.doe@gmail.com',\n  })\n  @Column({\n    unique: true,\n  })\n  email: string;\n\n  @ApiProperty({ description: 'Hashed user password' })\n  @Column()\n  password: string;\n\n  @Column({ type: 'enum', enum: UserRoles, default: UserRoles.MEMBER })\n  role: UserRoles;\n\n  @ApiProperty({ description: 'When user was created' })\n  @CreateDateColumn()\n  createdAt: Date;\n\n  @ApiProperty({ description: 'When user was updated' })\n  @UpdateDateColumn()\n  updatedAt: Date;\n\n  @BeforeInsert()\n  async setPassword(password: string) {\n    const salt = await bcrypt.genSalt();\n    this.password = await bcrypt.hash(password || this.password, salt);\n  }\n}\n"
  },
  {
    "path": "server/src/modules/user/user.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { UserController } from './user.controller';\nimport { UserService } from './user.service';\n\n@Module({\n  providers: [UserService],\n  controllers: [UserController],\n  exports: [UserService],\n})\nexport class UserModule {}\n"
  },
  {
    "path": "server/src/modules/user/user.service.ts",
    "content": "import { Injectable } from '@nestjs/common';\nimport { UserRegisterRequestDto } from './dto/user-register.req.dto';\nimport { User } from './user.entity';\n\n@Injectable()\nexport class UserService {\n  async doUserRegistration(\n    userRegister: UserRegisterRequestDto,\n  ): Promise<User> {\n    const user = new User();\n    user.name = userRegister.name;\n    user.email = userRegister.email;\n    user.password = userRegister.password;\n\n    return await user.save();\n  }\n\n  async getUserByEmail(email: string): Promise<User | undefined> {\n    return User.findOne({ where: { email } });\n  }\n\n  async getUserById(id: number): Promise<User | undefined> {\n    return User.findOne({ where: { id } });\n  }\n}\n"
  },
  {
    "path": "server/test/app.e2e-spec.ts",
    "content": "import { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { AppModule } from './../src/app.module';\n\ndescribe('AppController (e2e)', () => {\n  let app: INestApplication;\n\n  beforeEach(async () => {\n    const moduleFixture: TestingModule = await Test.createTestingModule({\n      imports: [AppModule],\n    }).compile();\n\n    app = moduleFixture.createNestApplication();\n    await app.init();\n  });\n\n  it('/ (GET)', () => {\n    return request(app.getHttpServer())\n      .get('/')\n      .expect(200)\n      .expect('Hello World!');\n  });\n});\n"
  },
  {
    "path": "server/test/jest-e2e.json",
    "content": "{\n  \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n  \"rootDir\": \".\",\n  \"testEnvironment\": \"node\",\n  \"testRegex\": \".e2e-spec.ts$\",\n  \"transform\": {\n    \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n  }\n}\n"
  },
  {
    "path": "server/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"baseUrl\": \"./\",\n    \"incremental\": true\n  }\n}\n"
  },
  {
    "path": "server/uploads/.gitignore",
    "content": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore\n"
  }
]