Repository: amitavdevzone/nest-js-quiz-manager
Branch: master
Commit: 3ff3564299b5
Files: 76
Total size: 52.2 KB
Directory structure:
gitextract_f_h8i67j/
├── LICENSE
├── readme.md
└── server/
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package.json
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── app.utils.ts
│ ├── common/
│ │ ├── constants/
│ │ │ └── event.constants.ts
│ │ ├── decorator/
│ │ │ └── api-pagination.response.ts
│ │ ├── dto/
│ │ │ └── paginated.dto.ts
│ │ ├── exceptions/
│ │ │ └── api-token-payement.exception.ts
│ │ └── middleware/
│ │ └── api-token-check.middleware.ts
│ ├── config/
│ │ ├── app.config.ts
│ │ ├── jwt.config.ts
│ │ ├── typeorm.config-migrations.ts
│ │ └── typeorm.config.ts
│ ├── database/
│ │ ├── data/
│ │ │ └── quiz.data.ts
│ │ ├── factories/
│ │ │ ├── quiz.factory.ts
│ │ │ └── user.factory.ts
│ │ ├── migrations/
│ │ │ ├── 1649938237326-BaseMigrations.ts
│ │ │ └── 1651075479367-add_user_role_column.ts
│ │ └── seeds/
│ │ ├── setup-data.seed.ts
│ │ └── user-create.seed.ts
│ ├── main.ts
│ └── modules/
│ ├── auth/
│ │ ├── admin-role.guard.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ │ └── login.dto.ts
│ │ ├── jwt-auth.guard.ts
│ │ ├── jwt.strategy.ts
│ │ ├── local-auth.guard.ts
│ │ ├── local.strategy.ts
│ │ ├── roles.decorator.ts
│ │ └── roles.guard.ts
│ ├── quiz/
│ │ ├── controllers/
│ │ │ ├── option.controller.ts
│ │ │ ├── question.controller.ts
│ │ │ ├── quiz.controller.ts
│ │ │ └── response.controller.ts
│ │ ├── dto/
│ │ │ ├── create-option.dto.ts
│ │ │ ├── create-question.dto.ts
│ │ │ └── create-quiz.dto.ts
│ │ ├── entities/
│ │ │ ├── option.entity.ts
│ │ │ ├── question.entity.ts
│ │ │ └── quiz.entity.ts
│ │ ├── events/
│ │ │ └── response-add.event.ts
│ │ ├── quiz.module.ts
│ │ ├── repositories/
│ │ │ ├── option.repository.ts
│ │ │ ├── question.repository.ts
│ │ │ └── quiz.repository.ts
│ │ └── services/
│ │ ├── option.service.ts
│ │ ├── question.service.ts
│ │ ├── quiz.service.ts
│ │ └── response.service.ts
│ ├── search/
│ │ ├── controllers/
│ │ │ └── search.controller.ts
│ │ ├── dto/
│ │ │ └── search-movie.dto.ts
│ │ ├── search.module.ts
│ │ └── search.service.ts
│ └── user/
│ ├── dto/
│ │ └── user-register.req.dto.ts
│ ├── enums/
│ │ └── user.enum.ts
│ ├── user.controller.ts
│ ├── user.entity.ts
│ ├── user.module.ts
│ └── user.service.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── uploads/
└── .gitignore
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 amitavdevzone
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: readme.md
================================================
# Quiz Manager app
This 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.
The 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.
================================================
FILE: server/.editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_size = 2
indent_style = space
trim_trailing_whitespace = true
================================================
FILE: server/.eslintrc.js
================================================
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
================================================
FILE: server/.gitignore
================================================
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Important secrets
.env
.env.prod
# Uploads
!uploads
uploads/*
================================================
FILE: server/.prettierrc
================================================
{
"singleQuote": true,
"trailingComma": "all"
}
================================================
FILE: server/README.md
================================================
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<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>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<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>
<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>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<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>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[](https://opencollective.com/nest#backer)
[](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest 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).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).
================================================
FILE: server/nest-cli.json
================================================
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
================================================
FILE: server/package.json
================================================
{
"name": "server",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm:cli": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli -f src/config/typeorm.config-migrations.ts",
"migration:generate": "npm run typeorm:cli -- migration:generate -d src/database/migrations -n",
"migration:create": "npm run typeorm:cli -- migration:create -d src/database/migrations -n",
"migration:run": "npm run typeorm:cli -- migration:run",
"migration:revert": "npm run typeorm:cli -- migration:revert",
"seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config -n src/config/typeorm.config-migrations.ts",
"seed:run": "ts-node ./node_modules/typeorm-seeding/dist/cli.js seed -n src/config/typeorm.config-migrations.ts",
"db:refresh": "npm run typeorm:cli schema:drop && npm run migration:run && npm run seed:run"
},
"dependencies": {
"@nestjs/common": "^8.4.4",
"@nestjs/config": "^2.0.0",
"@nestjs/core": "^8.4.4",
"@nestjs/event-emitter": "^1.1.0",
"@nestjs/jwt": "^8.0.0",
"@nestjs/passport": "^8.2.1",
"@nestjs/platform-express": "^8.4.4",
"@nestjs/swagger": "^5.2.1",
"@nestjs/typeorm": "^8.0.3",
"@ngneat/falso": "^5.0.0",
"@types/bcrypt": "^5.0.0",
"bcrypt": "^5.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"meilisearch": "^0.25.1",
"mysql2": "^2.3.3",
"nestjs-typeorm-paginate": "^3.2.0",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.5",
"swagger-ui-express": "^4.3.0",
"typeorm": "^0.2.45",
"typeorm-seeding": "^1.6.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.5",
"@nestjs/schematics": "^8.0.10",
"@nestjs/testing": "^8.4.4",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/multer": "^1.4.7",
"@types/node": "^17.0.25",
"@types/passport": "^1.0.7",
"@types/passport-jwt": "^3.0.6",
"@types/passport-local": "^1.0.34",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"eslint": "^8.13.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1",
"prettier": "^2.6.2",
"supertest": "^6.2.2",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.1",
"typescript": "^4.6.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
================================================
FILE: server/src/app.controller.spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
================================================
FILE: server/src/app.controller.ts
================================================
import {
Controller,
Get,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Post('/file')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './uploads',
filename: (req, file, callback) => {
const uniqueSuffix =
Date.now() + '-' + Math.round(Math.random() * 1e9);
const ext = extname(file.originalname);
const filename = `${uniqueSuffix}${ext}`;
callback(null, filename);
},
}),
}),
)
handleUpload(@UploadedFile() file: Express.Multer.File) {
console.log('file', file);
return 'File upload API';
}
}
================================================
FILE: server/src/app.module.ts
================================================
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { typeOrmAsyncConfig } from './config/typeorm.config';
import { QuizModule } from './modules/quiz/quiz.module';
import { UserModule } from './modules/user/user.module';
import { AuthModule } from './modules/auth/auth.module';
import { ApiTokenCheckMiddleware } from './common/middleware/api-token-check.middleware';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { SearchModule } from './modules/search/search.module';
import { MulterModule } from '@nestjs/platform-express';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync(typeOrmAsyncConfig),
EventEmitterModule.forRoot(),
MulterModule.register({ dest: './uploads' }),
QuizModule,
UserModule,
AuthModule,
SearchModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(ApiTokenCheckMiddleware)
.forRoutes({ path: '/', method: RequestMethod.ALL });
}
}
================================================
FILE: server/src/app.service.ts
================================================
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
================================================
FILE: server/src/app.utils.ts
================================================
import { HttpStatus, ValidationPipe } from '@nestjs/common';
const PASSWORD_RULE = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/;
const PASSWORD_RULE_MESSAGE =
'Password should have 1 upper case, lowcase letter along with a number and special character.';
const VALIDATION_PIPE = new ValidationPipe({
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
});
export const REGEX = {
PASSWORD_RULE,
};
export const MESSAGES = {
PASSWORD_RULE_MESSAGE,
};
export const SETTINGS = {
VALIDATION_PIPE,
};
================================================
FILE: server/src/common/constants/event.constants.ts
================================================
export const events = {
RESPONSE_SUBMITTED: 'response.submitted',
};
================================================
FILE: server/src/common/decorator/api-pagination.response.ts
================================================
import { applyDecorators, Type } from '@nestjs/common';
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
import { PaginatedDto } from '../dto/paginated.dto';
interface IPaginatedDecoratorApiResponse {
model: Type<any>;
description?: string;
}
export const ApiPaginatedResponse = <TModel extends Type<any>>(
options: IPaginatedDecoratorApiResponse,
) => {
return applyDecorators(
ApiExtraModels(PaginatedDto),
ApiOkResponse({
description: options.description || 'Successfully received model list',
schema: {
allOf: [
{ $ref: getSchemaPath(PaginatedDto) },
{
properties: {
items: {
type: 'array',
items: { $ref: getSchemaPath(options.model) },
},
meta: {
type: 'any',
default: {
totalItems: 2,
itemCount: 2,
itemsPerPage: 2,
totalPages: 1,
currentPage: 1,
},
},
},
},
],
},
}),
);
};
================================================
FILE: server/src/common/dto/paginated.dto.ts
================================================
interface PaginationMeta {
totalItems: number;
itemCount: number;
itemsPerPage: number;
totalPages: number;
currentPage: number;
}
class PaginatedDto<TData> {
items: TData[];
meta: PaginationMeta;
}
export { PaginationMeta, PaginatedDto };
================================================
FILE: server/src/common/exceptions/api-token-payement.exception.ts
================================================
import { HttpException, HttpStatus } from '@nestjs/common';
export class ApiTokenPaymentException extends HttpException {
constructor() {
super('Token suggest payment is required', HttpStatus.PAYMENT_REQUIRED);
}
}
================================================
FILE: server/src/common/middleware/api-token-check.middleware.ts
================================================
import { NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { ApiTokenPaymentException } from '../exceptions/api-token-payement.exception';
export class ApiTokenCheckMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
if (req.headers['api-token'] !== 'my-token') {
throw new ApiTokenPaymentException();
}
next();
}
}
================================================
FILE: server/src/config/app.config.ts
================================================
export default () => ({
appSecret: process.env.APP_SECRET,
});
================================================
FILE: server/src/config/jwt.config.ts
================================================
import { JwtModuleAsyncOptions } from '@nestjs/jwt';
import appConfig from './app.config';
export const jwtConfig: JwtModuleAsyncOptions = {
useFactory: () => {
return {
secret: appConfig().appSecret,
signOptions: { expiresIn: '1d' },
};
},
};
================================================
FILE: server/src/config/typeorm.config-migrations.ts
================================================
import { typeOrmConfig } from './typeorm.config';
export = typeOrmConfig;
================================================
FILE: server/src/config/typeorm.config.ts
================================================
import { ConfigModule, ConfigService } from '@nestjs/config';
import {
TypeOrmModuleAsyncOptions,
TypeOrmModuleOptions,
} from '@nestjs/typeorm';
export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = {
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (): Promise<TypeOrmModuleOptions> => {
return {
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USERNAME,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],
cli: {
migrationsDir: __dirname + '/../database/migrations',
},
extra: {
charset: 'utf8mb4_unicode_ci',
},
synchronize: false,
logging: true,
};
},
};
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USERNAME,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],
cli: {
migrationsDir: __dirname + '/../database/migrations',
},
extra: {
charset: 'utf8mb4_unicode_ci',
},
synchronize: false,
logging: true,
};
================================================
FILE: server/src/database/data/quiz.data.ts
================================================
interface ISampleData {
quizTitle: string;
quizDescription: string;
questions: Array<IQuestionData>;
}
interface IQuestionData {
question: string;
options: Array<{ text: string; isCorrect: boolean }>;
}
export const quizSampleData: Array<ISampleData> = [
{
quizTitle: 'Laravel beginner level quiz',
quizDescription:
'In this quiz, you are going to be asked some basic questions which will target your knowledge of Laravel',
questions: [
{
question: 'How to put Laravel applications in maintenance mode?',
options: [
{ text: 'php artisan maintenance:on', isCorrect: false },
{ text: 'php artisan down', isCorrect: true },
{ text: 'php artisan maintenance:up', isCorrect: false },
{ text: 'php artisan maintenance:down', isCorrect: false },
],
},
{
question: 'What is the role of Service provider?',
options: [
{
text: 'They allow Laravel to know about the packages which are present and how to bootstrap them',
isCorrect: true,
},
{
text: 'They allow Laravel to provide services for individual modules',
isCorrect: false,
},
],
},
],
},
{
quizTitle: 'React Js beginner level quiz',
quizDescription:
'In this quiz, you are going to be asked some basic questions which will target your knowledge of React Js',
questions: [
{
question: 'How to put Laravel applications in maintenance mode?',
options: [
{ text: 'php artisan maintenance:on', isCorrect: false },
{ text: 'php artisan down', isCorrect: true },
{ text: 'php artisan maintenance:up', isCorrect: false },
{ text: 'php artisan maintenance:down', isCorrect: false },
],
},
],
},
];
================================================
FILE: server/src/database/factories/quiz.factory.ts
================================================
import { randDatabaseColumn, randParagraph, randSentence } from '@ngneat/falso';
import { define } from 'typeorm-seeding';
import { Quiz } from '../../modules/quiz/entities/quiz.entity';
define(Quiz, () => {
const quiz = new Quiz();
quiz.title = randSentence();
quiz.description = randParagraph();
return quiz;
});
================================================
FILE: server/src/database/factories/user.factory.ts
================================================
import { randEmail, randFullName, randPassword } from '@ngneat/falso';
import { define } from 'typeorm-seeding';
import { User } from '../../modules/user/user.entity';
define(User, () => {
const user = new User();
user.name = randFullName();
user.email = randEmail();
user.password = randPassword();
return user;
});
================================================
FILE: server/src/database/migrations/1649938237326-BaseMigrations.ts
================================================
import {MigrationInterface, QueryRunner} from "typeorm";
export class BaseMigrations1649938237326 implements MigrationInterface {
name = 'BaseMigrations1649938237326'
public async up(queryRunner: QueryRunner): Promise<void> {
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`);
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`);
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`);
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`);
await queryRunner.query(`ALTER TABLE \`questions\` ADD CONSTRAINT \`FK_35d54f06d12ea78d4842aed6b6d\` FOREIGN KEY (\`quizId\`) REFERENCES \`quizes\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE \`options\` ADD CONSTRAINT \`FK_46b668c49a6c4154d4643d875a5\` FOREIGN KEY (\`questionId\`) REFERENCES \`questions\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`options\` DROP FOREIGN KEY \`FK_46b668c49a6c4154d4643d875a5\``);
await queryRunner.query(`ALTER TABLE \`questions\` DROP FOREIGN KEY \`FK_35d54f06d12ea78d4842aed6b6d\``);
await queryRunner.query(`DROP TABLE \`options\``);
await queryRunner.query(`DROP TABLE \`questions\``);
await queryRunner.query(`DROP TABLE \`quizes\``);
await queryRunner.query(`DROP INDEX \`IDX_97672ac88f789774dd47f7c8be\` ON \`users\``);
await queryRunner.query(`DROP TABLE \`users\``);
}
}
================================================
FILE: server/src/database/migrations/1651075479367-add_user_role_column.ts
================================================
import {MigrationInterface, QueryRunner} from "typeorm";
export class addUserRoleColumn1651075479367 implements MigrationInterface {
name = 'addUserRoleColumn1651075479367'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`users\` ADD \`role\` enum ('admin', 'member') NOT NULL DEFAULT 'member'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`users\` DROP COLUMN \`role\``);
}
}
================================================
FILE: server/src/database/seeds/setup-data.seed.ts
================================================
import { Connection, getManager } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding';
import { Option } from '../../modules/quiz/entities/option.entity';
import { Question } from '../../modules/quiz/entities/question.entity';
import { Quiz } from '../../modules/quiz/entities/quiz.entity';
import { quizSampleData } from '../data/quiz.data';
export class SetupData implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<void> {
console.log('quizSampleData', quizSampleData);
await getManager().query('SET foreign_key_checks = 0');
await getManager().query('TRUNCATE quizes');
await getManager().query('TRUNCATE questions');
await getManager().query('TRUNCATE options');
await getManager().query('SET foreign_key_checks = 1');
for (let i = 0; i < quizSampleData.length; i++) {
const { quizTitle, quizDescription, questions } = quizSampleData[i];
const quiz = new Quiz();
quiz.title = quizTitle;
quiz.description = quizDescription;
await quiz.save();
for (let j = 0; j < questions.length; j++) {
const { question, options } = questions[j];
const que = new Question();
que.question = question;
que.quiz = quiz;
await que.save();
for (let k = 0; k < options.length; k++) {
const { isCorrect, text } = options[k];
const opt = new Option();
opt.isCorrect = isCorrect;
opt.text = text;
opt.question = que;
await opt.save();
}
}
}
}
}
================================================
FILE: server/src/database/seeds/user-create.seed.ts
================================================
import { Connection, getManager } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding';
import { UserRoles } from '../../modules/user/enums/user.enum';
import { User } from '../../modules/user/user.entity';
export class UserCreateSeed implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<void> {
await getManager().query('TRUNCATE users');
await factory(User)().create({
name: 'Amitav Roy',
email: 'reachme@amitavroy.com',
password: 'Password@123',
role: UserRoles.ADMIN,
});
// await factory(User)().createMany(20);
}
}
================================================
FILE: server/src/main.ts
================================================
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
const config = new DocumentBuilder()
.addBearerAuth()
.setTitle('Quiz manager API')
.setDescription('Quiz manager API description')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(process.env.PORT || 3000);
}
bootstrap();
================================================
FILE: server/src/modules/auth/admin-role.guard.ts
================================================
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { UserRoles } from '../user/enums/user.enum';
import { UserService } from '../user/user.service';
@Injectable()
export class AdminRoleGuard implements CanActivate {
constructor(private userService: UserService) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if (request?.user) {
const { id } = request.user;
const user = await this.userService.getUserById(id);
return user.role === UserRoles.ADMIN;
}
return false;
}
}
================================================
FILE: server/src/modules/auth/auth.controller.ts
================================================
import {
Body,
Controller,
Get,
Post,
Request,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { JwtAuthGuard } from './jwt-auth.guard';
import { LocalAuthGuard } from './local-auth.guard';
@ApiTags('Auth')
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req, @Body() loginDto: LoginDto): Promise<any> {
return this.authService.generateToken(req.user);
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('user')
async user(@Request() req): Promise<any> {
return req.user;
}
}
================================================
FILE: server/src/modules/auth/auth.module.ts
================================================
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { LocalStrategy } from './local.strategy';
import { UserModule } from '../user/user.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
import { jwtConfig } from '../../config/jwt.config';
@Module({
imports: [UserModule, PassportModule, JwtModule.registerAsync(jwtConfig)],
providers: [AuthService, LocalStrategy, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule {}
================================================
FILE: server/src/modules/auth/auth.service.ts
================================================
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}
async validateUserCreds(email: string, password: string): Promise<any> {
const user = await this.userService.getUserByEmail(email);
if (!user) throw new BadRequestException();
if (!(await bcrypt.compare(password, user.password)))
throw new UnauthorizedException();
return user;
}
generateToken(user: any) {
return {
access_token: this.jwtService.sign({
name: user.name,
sub: user.id,
}),
};
}
}
================================================
FILE: server/src/modules/auth/dto/login.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDto {
@ApiProperty({
description: 'Email address of the user',
example: 'reachme@amitavroy.com',
})
@IsNotEmpty()
@IsEmail()
username: string;
@ApiProperty({
description: 'Password in plain text',
example: 'Password@123',
})
@IsNotEmpty()
password: string;
}
================================================
FILE: server/src/modules/auth/jwt-auth.guard.ts
================================================
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
================================================
FILE: server/src/modules/auth/jwt.strategy.ts
================================================
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import appConfig from '../../config/app.config';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: appConfig().appSecret,
});
}
async validate(payload: any) {
return {
id: payload.sub,
name: payload.name,
tenant: 'amitav',
};
}
}
================================================
FILE: server/src/modules/auth/local-auth.guard.ts
================================================
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
================================================
FILE: server/src/modules/auth/local.strategy.ts
================================================
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(email: string, password: string) {
const user = await this.authService.validateUserCreds(email, password);
if (!user) throw new UnauthorizedException();
return user;
}
}
================================================
FILE: server/src/modules/auth/roles.decorator.ts
================================================
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
================================================
FILE: server/src/modules/auth/roles.guard.ts
================================================
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { UserService } from '../user/user.service';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector, private userService: UserService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
const request = context.switchToHttp().getRequest();
if (request?.user) {
const { id } = request.user;
const user = await this.userService.getUserById(id);
return roles.includes(user.role);
}
return false;
}
}
================================================
FILE: server/src/modules/quiz/controllers/option.controller.ts
================================================
import {
Body,
Controller,
Post,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiBearerAuth, ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { CreateOptionDto } from '../dto/create-option.dto';
import { Option } from '../entities/option.entity';
import { OptionService } from '../services/option.service';
import { QuestionService } from '../services/question.service';
@ApiTags('Questions')
@ApiBearerAuth()
@Controller('question/option')
export class OptionController {
constructor(
private optionService: OptionService,
private questionService: QuestionService,
) {}
@Post('')
@UsePipes(ValidationPipe)
@ApiCreatedResponse({
description: 'The option that got created',
type: Option,
})
async saveOptionToQuestion(@Body() createOption: CreateOptionDto) {
const question = await this.questionService.findQuestionById(
createOption.questionId,
);
const option = await this.optionService.creatOption(createOption, question);
return { question, createOption, option };
}
}
================================================
FILE: server/src/modules/quiz/controllers/question.controller.ts
================================================
import {
Body,
Controller,
Get,
Post,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiBearerAuth, ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { CreateQuestionDto } from '../dto/create-question.dto';
import { Question } from '../entities/question.entity';
import { QuestionService } from '../services/question.service';
import { QuizService } from '../services/quiz.service';
@ApiTags('Questions')
@ApiBearerAuth()
@Controller('question')
export class QuestionController {
constructor(
private questionService: QuestionService,
private quizService: QuizService,
) {}
@Post('')
@UsePipes(ValidationPipe)
@ApiCreatedResponse({
description: 'Question added to a quiz',
type: Question,
})
async saveQuestion(@Body() question: CreateQuestionDto): Promise<Question> {
const quiz = await this.quizService.getQuizById(question.quizId);
return await this.questionService.createQuestion(question, quiz);
}
}
================================================
FILE: server/src/modules/quiz/controllers/quiz.controller.ts
================================================
import {
Body,
Controller,
DefaultValuePipe,
Get,
Param,
ParseIntPipe,
Post,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import {
ApiCreatedResponse,
ApiOkResponse,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import { IPaginationOptions, Pagination } from 'nestjs-typeorm-paginate';
import { ApiPaginatedResponse } from '../../../common/decorator/api-pagination.response';
import { AdminRoleGuard } from '../../auth/admin-role.guard';
import { JwtAuthGuard } from '../../auth/jwt-auth.guard';
import { Roles } from '../../auth/roles.decorator';
import { RolesGuard } from '../../auth/roles.guard';
import { CreateQuizDto } from '../dto/create-quiz.dto';
import { Quiz } from '../entities/quiz.entity';
import { QuizService } from '../services/quiz.service';
@ApiTags('Quiz')
@Controller('quiz')
@ApiSecurity('bearer')
@UseGuards(JwtAuthGuard)
export class QuizController {
constructor(private quizService: QuizService) {}
@Get('/')
@ApiPaginatedResponse({ model: Quiz, description: 'List of quizzes' })
async getAllQuiz(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 1,
): Promise<Pagination<Quiz>> {
const options: IPaginationOptions = {
limit,
page,
};
return await this.quizService.paginate(options);
}
@Get('/:id')
@ApiOkResponse({ description: 'Get a quiz by id', type: Quiz })
async getQuizById(@Param('id', ParseIntPipe) id: number): Promise<Quiz> {
return await this.quizService.getQuizById(id);
}
@ApiCreatedResponse({ description: 'The quiz that got created', type: Quiz })
@Post('/create')
@UsePipes(ValidationPipe)
@UseGuards(RolesGuard)
@Roles('admin')
async createQuiz(@Body() quizData: CreateQuizDto): Promise<Quiz> {
return await this.quizService.createNewQuiz(quizData);
}
}
================================================
FILE: server/src/modules/quiz/controllers/response.controller.ts
================================================
import { Controller, Post } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ApiTags } from '@nestjs/swagger';
import { events } from '../../../common/constants/event.constants';
import { ResponseAddEvent } from '../events/response-add.event';
@Controller('/response')
@ApiTags('Response')
export class ResponseController {
constructor(private eventEmitter: EventEmitter2) {}
@Post('')
async handleQuestionResponse() {
// insert data into the response table
console.log('This is inside the controller');
const payload = new ResponseAddEvent();
payload.userId = 1;
payload.optionId = 33;
this.eventEmitter.emit(events.RESPONSE_SUBMITTED, payload);
return { message: 'Response taken' };
}
}
================================================
FILE: server/src/modules/quiz/dto/create-option.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, Length } from 'class-validator';
export class CreateOptionDto {
@ApiProperty({
description: 'The option for a question',
example: 'Owl',
})
@IsNotEmpty()
@Length(2, 255)
text: string;
@ApiProperty({
description: 'The ID of the question',
example: 1,
})
@IsNotEmpty()
questionId: number;
@ApiProperty({
description: 'Whether the option is correct or not',
example: true,
})
@IsNotEmpty()
isCorrect: boolean;
}
================================================
FILE: server/src/modules/quiz/dto/create-question.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, Length } from 'class-validator';
export class CreateQuestionDto {
@ApiProperty({
description: 'The actual question',
example: 'A sample question',
})
@IsNotEmpty()
@Length(3, 255)
question: string;
@ApiProperty({
description: 'The quiz id to which the question is associated.',
example: 1,
})
@IsNotEmpty()
quizId: number;
}
================================================
FILE: server/src/modules/quiz/dto/create-quiz.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, Length } from 'class-validator';
export class CreateQuizDto {
@ApiProperty({
description: 'The title of the quiz',
example: 'How good are your with Laravel?',
})
@IsNotEmpty({ message: 'The quiz should have a title' })
@Length(3, 255)
title: string;
@ApiProperty({
description: 'A small description for the user',
example:
'This quiz will ask your questions on Laravel and test your knowledge.',
})
@IsNotEmpty()
@Length(3)
description: string;
}
================================================
FILE: server/src/modules/quiz/entities/option.entity.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import {
BaseEntity,
Column,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Question } from './question.entity';
@Entity('options')
export class Option extends BaseEntity {
@ApiProperty({ description: 'Primary key as Option ID', example: 1 })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ description: 'The actual option', example: 'Owl' })
@Column({
type: 'varchar',
})
text: string;
@ApiProperty({ description: 'Whether option is correct', example: true })
@Column({
type: 'boolean',
})
isCorrect: boolean;
@ManyToOne(() => Question, (question) => question.options)
question: Question;
}
================================================
FILE: server/src/modules/quiz/entities/question.entity.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import {
BaseEntity,
Column,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Option } from './option.entity';
import { Quiz } from './quiz.entity';
@Entity('questions')
export class Question extends BaseEntity {
@ApiProperty({
description: 'The primary ID of question.',
example: 1,
})
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
description: 'The actual question',
example: 'What is the question?',
})
@Column({
type: 'varchar',
})
question: string;
@ApiProperty({
description: 'Quiz of the question',
})
@ManyToOne(() => Quiz, (quiz) => quiz.questions)
quiz: Quiz;
@ApiProperty({
description: 'Options of the question',
})
@OneToMany(() => Option, (option) => option.question)
options: Option[];
}
================================================
FILE: server/src/modules/quiz/entities/quiz.entity.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import {
BaseEntity,
Column,
Entity,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Question } from './question.entity';
@Entity('quizes')
export class Quiz extends BaseEntity {
@ApiProperty({ description: 'Primary key as Quiz ID', example: 1 })
@PrimaryGeneratedColumn({
comment: 'The quiz unique identifier',
})
id: number;
@ApiProperty({
description: 'Title of the quiz',
example: 'Sample Laravel quiz',
})
@Column({
type: 'varchar',
})
title: string;
@ApiProperty({
description: 'Description of the quiz',
example: 'Lorem ipsum',
})
@Column({
type: 'text',
})
description: string;
@ApiProperty({
description: 'Quiz active or inactive state',
example: true,
})
@Column({
type: 'boolean',
default: 1,
})
isActive: boolean;
@ApiProperty({
description: 'List of questions',
})
@OneToMany(() => Question, (question) => question.quiz)
questions: Question[];
}
================================================
FILE: server/src/modules/quiz/events/response-add.event.ts
================================================
export class ResponseAddEvent {
userId: number;
optionId: number;
}
================================================
FILE: server/src/modules/quiz/quiz.module.ts
================================================
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { QuestionController } from './controllers/question.controller';
import { QuestionRepository } from './repositories/question.repository';
import { QuestionService } from './services/question.service';
import { QuizController } from './controllers/quiz.controller';
import { QuizRepository } from './repositories/quiz.repository';
import { QuizService } from './services/quiz.service';
import { OptionRepository } from './repositories/option.repository';
import { OptionController } from './controllers/option.controller';
import { OptionService } from './services/option.service';
import { UserModule } from '../user/user.module';
import { ResponseController } from './controllers/response.controller';
import { ResponseService } from './services/response.service';
@Module({
controllers: [
QuizController,
QuestionController,
OptionController,
ResponseController,
],
imports: [
TypeOrmModule.forFeature([
QuizRepository,
QuestionRepository,
OptionRepository,
]),
UserModule,
],
providers: [QuizService, QuestionService, OptionService, ResponseService],
})
export class QuizModule {}
================================================
FILE: server/src/modules/quiz/repositories/option.repository.ts
================================================
import { EntityRepository, Repository } from 'typeorm';
import { Option } from '../entities/option.entity';
@EntityRepository(Option)
export class OptionRepository extends Repository<Option> {}
================================================
FILE: server/src/modules/quiz/repositories/question.repository.ts
================================================
import { EntityRepository, Repository } from 'typeorm';
import { Question } from '../entities/question.entity';
@EntityRepository(Question)
export class QuestionRepository extends Repository<Question> {}
================================================
FILE: server/src/modules/quiz/repositories/quiz.repository.ts
================================================
import { EntityRepository, Repository } from 'typeorm';
import { Quiz } from '../entities/quiz.entity';
@EntityRepository(Quiz)
export class QuizRepository extends Repository<Quiz> {}
================================================
FILE: server/src/modules/quiz/services/option.service.ts
================================================
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateOptionDto } from '../dto/create-option.dto';
import { Question } from '../entities/question.entity';
import { OptionRepository } from '../repositories/option.repository';
@Injectable()
export class OptionService {
constructor(
@InjectRepository(OptionRepository)
private optionRepository: OptionRepository,
) {}
async creatOption(option: CreateOptionDto, question: Question) {
const newOption = await this.optionRepository.save({
text: option.text,
isCorrect: option.isCorrect,
});
question.options = [...question.options, newOption];
await question.save();
return newOption;
}
}
================================================
FILE: server/src/modules/quiz/services/question.service.ts
================================================
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateQuestionDto } from '../dto/create-question.dto';
import { Question } from '../entities/question.entity';
import { QuestionRepository } from '../repositories/question.repository';
import { Quiz } from '../entities/quiz.entity';
@Injectable()
export class QuestionService {
constructor(
@InjectRepository(QuestionRepository)
private questionRepository: QuestionRepository,
) {}
async findQuestionById(id: number): Promise<Question> {
return await this.questionRepository.findOne(id, {
relations: ['quiz', 'options'],
});
}
async createQuestion(
question: CreateQuestionDto,
quiz: Quiz,
): Promise<Question> {
const newQuestion = await this.questionRepository.save({
question: question.question,
});
quiz.questions = [...quiz.questions, newQuestion];
await quiz.save();
return newQuestion;
}
}
================================================
FILE: server/src/modules/quiz/services/quiz.service.ts
================================================
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';
import {
IPaginationOptions,
paginate,
Pagination,
} from 'nestjs-typeorm-paginate';
import { events } from '../../../common/constants/event.constants';
import { CreateQuizDto } from '../dto/create-quiz.dto';
import { Quiz } from '../entities/quiz.entity';
import { ResponseAddEvent } from '../events/response-add.event';
import { QuizRepository } from '../repositories/quiz.repository';
@Injectable()
export class QuizService {
constructor(
@InjectRepository(QuizRepository) private quizRepository: QuizRepository,
) {}
async getAllQuiz(): Promise<Quiz[]> {
return await this.quizRepository
.createQueryBuilder('q')
.leftJoinAndSelect('q.questions', 'qt')
.getMany();
}
async paginate(options: IPaginationOptions): Promise<Pagination<Quiz>> {
const qb = this.quizRepository.createQueryBuilder('q');
qb.orderBy('q.id', 'DESC');
return paginate<Quiz>(qb, options);
}
async getQuizById(id: number): Promise<Quiz> {
return await this.quizRepository.findOne(id, {
relations: ['questions', 'questions.options'],
});
}
async createNewQuiz(quiz: CreateQuizDto) {
return await this.quizRepository.save(quiz);
}
@OnEvent(events.RESPONSE_SUBMITTED)
checkQuizCompeleted(payload: ResponseAddEvent) {
console.log('checkQuizCompeleted', payload);
}
}
================================================
FILE: server/src/modules/quiz/services/response.service.ts
================================================
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '../../../common/constants/event.constants';
import { ResponseAddEvent } from '../events/response-add.event';
@Injectable()
export class ResponseService {
@OnEvent(events.RESPONSE_SUBMITTED)
handleIfResponseIsCorrect(payload: ResponseAddEvent) {
console.log('handleIfResponseIsCorrect', payload);
}
}
================================================
FILE: server/src/modules/search/controllers/search.controller.ts
================================================
import { Body, Controller, Get, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { SearchMovieDto } from '../dto/search-movie.dto';
import { SearchService } from '../search.service';
@ApiTags('Search')
@Controller('search')
export class SearchController {
constructor(private searchService: SearchService) {}
@Get('/')
public async getSearch() {
const resp = await this.searchService.addDocuments([
{ id: 1, title: 'Carol', genres: ['Romance', 'Drama'] },
{ id: 2, title: 'Wonder Woman', genres: ['Action', 'Adventure'] },
{ id: 3, title: 'Life of Pi', genres: ['Adventure', 'Drama'] },
{
id: 4,
title: 'Mad Max: Fury Road',
genres: ['Adventure', 'Science Fiction'],
},
{ id: 5, title: 'Moana', genres: ['Fantasy', 'Action'] },
{ id: 6, title: 'Philadelphia', genres: ['Drama'] },
{ id: 7, title: 'Kung Fu Panda', genres: ['Cartoon', 'Drama'] },
]);
console.log(resp);
}
@Post('/')
public async searchMovie(@Body() search: SearchMovieDto) {
return await this.searchService.search(search.text, {
attributesToHighlight: ['*'],
});
}
}
================================================
FILE: server/src/modules/search/dto/search-movie.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, Length } from 'class-validator';
export class SearchMovieDto {
@ApiProperty({
description: 'The name of the movie that you want to search',
example: 'wo',
})
@IsNotEmpty()
@Length(2, 255)
text: string;
}
================================================
FILE: server/src/modules/search/search.module.ts
================================================
import { Module } from '@nestjs/common';
import { SearchController } from './controllers/search.controller';
import { SearchService } from './search.service';
@Module({
controllers: [SearchController],
providers: [SearchService],
})
export class SearchModule {}
================================================
FILE: server/src/modules/search/search.service.ts
================================================
import { Injectable } from '@nestjs/common';
import MeiliSearch, { Index, SearchParams } from 'meilisearch';
@Injectable()
export class SearchService {
private _client: MeiliSearch;
constructor() {
this._client = new MeiliSearch({
host: 'http://192.168.1.141:7700/',
});
}
private getMovieIndex(): Index {
return this._client.index('movies');
}
public async addDocuments(documents) {
const index = this.getMovieIndex();
return await index.addDocuments(documents);
}
public async search(text: string, searchParams?: SearchParams) {
console.log(searchParams);
const index = this.getMovieIndex();
return await index.search(text, searchParams);
}
}
================================================
FILE: server/src/modules/user/dto/user-register.req.dto.ts
================================================
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, Length, Matches } from 'class-validator';
import { MESSAGES, REGEX } from 'src/app.utils';
export class UserRegisterRequestDto {
@ApiProperty({
description: 'The name of the User',
example: 'Jhon Doe',
})
@IsNotEmpty()
name: string;
@ApiProperty({
description: 'The email address of the User',
example: 'jhon.doe@gmail.com',
})
@IsNotEmpty()
@IsEmail()
email: string;
@ApiProperty({
description: 'The password of the User',
example: 'Password@123',
})
@IsNotEmpty()
@Length(8, 24)
@Matches(REGEX.PASSWORD_RULE, { message: MESSAGES.PASSWORD_RULE_MESSAGE })
password: string;
@ApiProperty({
description: 'Confirm the password',
example: 'Password@123',
})
@IsNotEmpty()
@Length(8, 24)
@Matches(REGEX.PASSWORD_RULE, { message: MESSAGES.PASSWORD_RULE_MESSAGE })
confirm: string;
}
================================================
FILE: server/src/modules/user/enums/user.enum.ts
================================================
enum UserRoles {
ADMIN = 'admin',
MEMBER = 'member',
}
export { UserRoles };
================================================
FILE: server/src/modules/user/user.controller.ts
================================================
import { Body, Controller, Post } from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiCreatedResponse,
ApiTags,
} from '@nestjs/swagger';
import { SETTINGS } from 'src/app.utils';
import { UserRegisterRequestDto } from './dto/user-register.req.dto';
import { User } from './user.entity';
import { UserService } from './user.service';
@ApiTags('User')
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Post('/register')
@ApiCreatedResponse({
description: 'Created user object as response',
type: User,
})
@ApiBadRequestResponse({ description: 'User cannot register. Try again!' })
async doUserRegistration(
@Body(SETTINGS.VALIDATION_PIPE)
userRegister: UserRegisterRequestDto,
): Promise<User> {
return await this.userService.doUserRegistration(userRegister);
}
}
================================================
FILE: server/src/modules/user/user.entity.ts
================================================
import {
BaseEntity,
BeforeInsert,
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import * as bcrypt from 'bcrypt';
import { ApiProperty } from '@nestjs/swagger';
import { UserRoles } from './enums/user.enum';
@Entity({ name: 'users' })
export class User extends BaseEntity {
@ApiProperty({ description: 'Primary key as User ID', example: 1 })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ description: 'User name', example: 'Jhon Doe' })
@Column()
name: string;
@ApiProperty({
description: 'User email address',
example: 'jhon.doe@gmail.com',
})
@Column({
unique: true,
})
email: string;
@ApiProperty({ description: 'Hashed user password' })
@Column()
password: string;
@Column({ type: 'enum', enum: UserRoles, default: UserRoles.MEMBER })
role: UserRoles;
@ApiProperty({ description: 'When user was created' })
@CreateDateColumn()
createdAt: Date;
@ApiProperty({ description: 'When user was updated' })
@UpdateDateColumn()
updatedAt: Date;
@BeforeInsert()
async setPassword(password: string) {
const salt = await bcrypt.genSalt();
this.password = await bcrypt.hash(password || this.password, salt);
}
}
================================================
FILE: server/src/modules/user/user.module.ts
================================================
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@Module({
providers: [UserService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule {}
================================================
FILE: server/src/modules/user/user.service.ts
================================================
import { Injectable } from '@nestjs/common';
import { UserRegisterRequestDto } from './dto/user-register.req.dto';
import { User } from './user.entity';
@Injectable()
export class UserService {
async doUserRegistration(
userRegister: UserRegisterRequestDto,
): Promise<User> {
const user = new User();
user.name = userRegister.name;
user.email = userRegister.email;
user.password = userRegister.password;
return await user.save();
}
async getUserByEmail(email: string): Promise<User | undefined> {
return User.findOne({ where: { email } });
}
async getUserById(id: number): Promise<User | undefined> {
return User.findOne({ where: { id } });
}
}
================================================
FILE: server/test/app.e2e-spec.ts
================================================
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
================================================
FILE: server/test/jest-e2e.json
================================================
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
================================================
FILE: server/tsconfig.build.json
================================================
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
================================================
FILE: server/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true
}
}
================================================
FILE: server/uploads/.gitignore
================================================
# Ignore everything in this directory
*
# Except this file
!.gitignore
gitextract_f_h8i67j/
├── LICENSE
├── readme.md
└── server/
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package.json
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── app.utils.ts
│ ├── common/
│ │ ├── constants/
│ │ │ └── event.constants.ts
│ │ ├── decorator/
│ │ │ └── api-pagination.response.ts
│ │ ├── dto/
│ │ │ └── paginated.dto.ts
│ │ ├── exceptions/
│ │ │ └── api-token-payement.exception.ts
│ │ └── middleware/
│ │ └── api-token-check.middleware.ts
│ ├── config/
│ │ ├── app.config.ts
│ │ ├── jwt.config.ts
│ │ ├── typeorm.config-migrations.ts
│ │ └── typeorm.config.ts
│ ├── database/
│ │ ├── data/
│ │ │ └── quiz.data.ts
│ │ ├── factories/
│ │ │ ├── quiz.factory.ts
│ │ │ └── user.factory.ts
│ │ ├── migrations/
│ │ │ ├── 1649938237326-BaseMigrations.ts
│ │ │ └── 1651075479367-add_user_role_column.ts
│ │ └── seeds/
│ │ ├── setup-data.seed.ts
│ │ └── user-create.seed.ts
│ ├── main.ts
│ └── modules/
│ ├── auth/
│ │ ├── admin-role.guard.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ │ └── login.dto.ts
│ │ ├── jwt-auth.guard.ts
│ │ ├── jwt.strategy.ts
│ │ ├── local-auth.guard.ts
│ │ ├── local.strategy.ts
│ │ ├── roles.decorator.ts
│ │ └── roles.guard.ts
│ ├── quiz/
│ │ ├── controllers/
│ │ │ ├── option.controller.ts
│ │ │ ├── question.controller.ts
│ │ │ ├── quiz.controller.ts
│ │ │ └── response.controller.ts
│ │ ├── dto/
│ │ │ ├── create-option.dto.ts
│ │ │ ├── create-question.dto.ts
│ │ │ └── create-quiz.dto.ts
│ │ ├── entities/
│ │ │ ├── option.entity.ts
│ │ │ ├── question.entity.ts
│ │ │ └── quiz.entity.ts
│ │ ├── events/
│ │ │ └── response-add.event.ts
│ │ ├── quiz.module.ts
│ │ ├── repositories/
│ │ │ ├── option.repository.ts
│ │ │ ├── question.repository.ts
│ │ │ └── quiz.repository.ts
│ │ └── services/
│ │ ├── option.service.ts
│ │ ├── question.service.ts
│ │ ├── quiz.service.ts
│ │ └── response.service.ts
│ ├── search/
│ │ ├── controllers/
│ │ │ └── search.controller.ts
│ │ ├── dto/
│ │ │ └── search-movie.dto.ts
│ │ ├── search.module.ts
│ │ └── search.service.ts
│ └── user/
│ ├── dto/
│ │ └── user-register.req.dto.ts
│ ├── enums/
│ │ └── user.enum.ts
│ ├── user.controller.ts
│ ├── user.entity.ts
│ ├── user.module.ts
│ └── user.service.ts
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── uploads/
└── .gitignore
SYMBOL INDEX (122 symbols across 53 files)
FILE: server/src/app.controller.ts
class AppController (line 14) | class AppController {
method constructor (line 15) | constructor(private readonly appService: AppService) {}
method getHello (line 18) | getHello(): string {
method handleUpload (line 37) | handleUpload(@UploadedFile() file: Express.Multer.File) {
FILE: server/src/app.module.ts
class AppModule (line 33) | class AppModule implements NestModule {
method configure (line 34) | configure(consumer: MiddlewareConsumer) {
FILE: server/src/app.service.ts
class AppService (line 4) | class AppService {
method getHello (line 5) | getHello(): string {
FILE: server/src/app.utils.ts
constant PASSWORD_RULE (line 3) | const PASSWORD_RULE = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$...
constant PASSWORD_RULE_MESSAGE (line 5) | const PASSWORD_RULE_MESSAGE =
constant VALIDATION_PIPE (line 8) | const VALIDATION_PIPE = new ValidationPipe({
constant REGEX (line 12) | const REGEX = {
constant MESSAGES (line 16) | const MESSAGES = {
constant SETTINGS (line 20) | const SETTINGS = {
FILE: server/src/common/decorator/api-pagination.response.ts
type IPaginatedDecoratorApiResponse (line 5) | interface IPaginatedDecoratorApiResponse {
FILE: server/src/common/dto/paginated.dto.ts
type PaginationMeta (line 1) | interface PaginationMeta {
class PaginatedDto (line 9) | class PaginatedDto<TData> {
FILE: server/src/common/exceptions/api-token-payement.exception.ts
class ApiTokenPaymentException (line 3) | class ApiTokenPaymentException extends HttpException {
method constructor (line 4) | constructor() {
FILE: server/src/common/middleware/api-token-check.middleware.ts
class ApiTokenCheckMiddleware (line 5) | class ApiTokenCheckMiddleware implements NestMiddleware {
method use (line 6) | use(req: Request, res: Response, next: NextFunction) {
FILE: server/src/database/data/quiz.data.ts
type ISampleData (line 1) | interface ISampleData {
type IQuestionData (line 7) | interface IQuestionData {
FILE: server/src/database/migrations/1649938237326-BaseMigrations.ts
class BaseMigrations1649938237326 (line 3) | class BaseMigrations1649938237326 implements MigrationInterface {
method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 15) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: server/src/database/migrations/1651075479367-add_user_role_column.ts
class addUserRoleColumn1651075479367 (line 3) | class addUserRoleColumn1651075479367 implements MigrationInterface {
method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
method down (line 10) | public async down(queryRunner: QueryRunner): Promise<void> {
FILE: server/src/database/seeds/setup-data.seed.ts
class SetupData (line 8) | class SetupData implements Seeder {
method run (line 9) | public async run(factory: Factory, connection: Connection): Promise<vo...
FILE: server/src/database/seeds/user-create.seed.ts
class UserCreateSeed (line 6) | class UserCreateSeed implements Seeder {
method run (line 7) | public async run(factory: Factory, connection: Connection): Promise<vo...
FILE: server/src/main.ts
function bootstrap (line 5) | async function bootstrap() {
FILE: server/src/modules/auth/admin-role.guard.ts
class AdminRoleGuard (line 7) | class AdminRoleGuard implements CanActivate {
method constructor (line 8) | constructor(private userService: UserService) {}
method canActivate (line 10) | async canActivate(context: ExecutionContext) {
FILE: server/src/modules/auth/auth.controller.ts
class AuthController (line 17) | class AuthController {
method constructor (line 18) | constructor(private authService: AuthService) {}
method login (line 22) | async login(@Request() req, @Body() loginDto: LoginDto): Promise<any> {
method user (line 29) | async user(@Request() req): Promise<any> {
FILE: server/src/modules/auth/auth.module.ts
class AuthModule (line 16) | class AuthModule {}
FILE: server/src/modules/auth/auth.service.ts
class AuthService (line 11) | class AuthService {
method constructor (line 12) | constructor(
method validateUserCreds (line 17) | async validateUserCreds(email: string, password: string): Promise<any> {
method generateToken (line 28) | generateToken(user: any) {
FILE: server/src/modules/auth/dto/login.dto.ts
class LoginDto (line 4) | class LoginDto {
FILE: server/src/modules/auth/jwt-auth.guard.ts
class JwtAuthGuard (line 5) | class JwtAuthGuard extends AuthGuard('jwt') {}
FILE: server/src/modules/auth/jwt.strategy.ts
class JwtStrategy (line 5) | class JwtStrategy extends PassportStrategy(Strategy) {
method constructor (line 6) | constructor() {
method validate (line 13) | async validate(payload: any) {
FILE: server/src/modules/auth/local-auth.guard.ts
class LocalAuthGuard (line 5) | class LocalAuthGuard extends AuthGuard('local') {}
FILE: server/src/modules/auth/local.strategy.ts
class LocalStrategy (line 7) | class LocalStrategy extends PassportStrategy(Strategy) {
method constructor (line 8) | constructor(private authService: AuthService) {
method validate (line 12) | async validate(email: string, password: string) {
FILE: server/src/modules/auth/roles.guard.ts
class RolesGuard (line 6) | class RolesGuard implements CanActivate {
method constructor (line 7) | constructor(private reflector: Reflector, private userService: UserSer...
method canActivate (line 9) | async canActivate(context: ExecutionContext): Promise<boolean> {
FILE: server/src/modules/quiz/controllers/option.controller.ts
class OptionController (line 17) | class OptionController {
method constructor (line 18) | constructor(
method saveOptionToQuestion (line 29) | async saveOptionToQuestion(@Body() createOption: CreateOptionDto) {
FILE: server/src/modules/quiz/controllers/question.controller.ts
class QuestionController (line 18) | class QuestionController {
method constructor (line 19) | constructor(
method saveQuestion (line 30) | async saveQuestion(@Body() question: CreateQuestionDto): Promise<Quest...
FILE: server/src/modules/quiz/controllers/quiz.controller.ts
class QuizController (line 35) | class QuizController {
method constructor (line 36) | constructor(private quizService: QuizService) {}
method getAllQuiz (line 40) | async getAllQuiz(
method getQuizById (line 53) | async getQuizById(@Param('id', ParseIntPipe) id: number): Promise<Quiz> {
method createQuiz (line 62) | async createQuiz(@Body() quizData: CreateQuizDto): Promise<Quiz> {
FILE: server/src/modules/quiz/controllers/response.controller.ts
class ResponseController (line 9) | class ResponseController {
method constructor (line 10) | constructor(private eventEmitter: EventEmitter2) {}
method handleQuestionResponse (line 13) | async handleQuestionResponse() {
FILE: server/src/modules/quiz/dto/create-option.dto.ts
class CreateOptionDto (line 4) | class CreateOptionDto {
FILE: server/src/modules/quiz/dto/create-question.dto.ts
class CreateQuestionDto (line 4) | class CreateQuestionDto {
FILE: server/src/modules/quiz/dto/create-quiz.dto.ts
class CreateQuizDto (line 4) | class CreateQuizDto {
FILE: server/src/modules/quiz/entities/option.entity.ts
class Option (line 12) | class Option extends BaseEntity {
FILE: server/src/modules/quiz/entities/question.entity.ts
class Question (line 14) | class Question extends BaseEntity {
FILE: server/src/modules/quiz/entities/quiz.entity.ts
class Quiz (line 12) | class Quiz extends BaseEntity {
FILE: server/src/modules/quiz/events/response-add.event.ts
class ResponseAddEvent (line 1) | class ResponseAddEvent {
FILE: server/src/modules/quiz/quiz.module.ts
class QuizModule (line 33) | class QuizModule {}
FILE: server/src/modules/quiz/repositories/option.repository.ts
class OptionRepository (line 5) | class OptionRepository extends Repository<Option> {}
FILE: server/src/modules/quiz/repositories/question.repository.ts
class QuestionRepository (line 5) | class QuestionRepository extends Repository<Question> {}
FILE: server/src/modules/quiz/repositories/quiz.repository.ts
class QuizRepository (line 5) | class QuizRepository extends Repository<Quiz> {}
FILE: server/src/modules/quiz/services/option.service.ts
class OptionService (line 8) | class OptionService {
method constructor (line 9) | constructor(
method creatOption (line 14) | async creatOption(option: CreateOptionDto, question: Question) {
FILE: server/src/modules/quiz/services/question.service.ts
class QuestionService (line 9) | class QuestionService {
method constructor (line 10) | constructor(
method findQuestionById (line 15) | async findQuestionById(id: number): Promise<Question> {
method createQuestion (line 21) | async createQuestion(
FILE: server/src/modules/quiz/services/quiz.service.ts
class QuizService (line 17) | class QuizService {
method constructor (line 18) | constructor(
method getAllQuiz (line 22) | async getAllQuiz(): Promise<Quiz[]> {
method paginate (line 29) | async paginate(options: IPaginationOptions): Promise<Pagination<Quiz>> {
method getQuizById (line 36) | async getQuizById(id: number): Promise<Quiz> {
method createNewQuiz (line 42) | async createNewQuiz(quiz: CreateQuizDto) {
method checkQuizCompeleted (line 47) | checkQuizCompeleted(payload: ResponseAddEvent) {
FILE: server/src/modules/quiz/services/response.service.ts
class ResponseService (line 7) | class ResponseService {
method handleIfResponseIsCorrect (line 9) | handleIfResponseIsCorrect(payload: ResponseAddEvent) {
FILE: server/src/modules/search/controllers/search.controller.ts
class SearchController (line 8) | class SearchController {
method constructor (line 9) | constructor(private searchService: SearchService) {}
method getSearch (line 12) | public async getSearch() {
method searchMovie (line 31) | public async searchMovie(@Body() search: SearchMovieDto) {
FILE: server/src/modules/search/dto/search-movie.dto.ts
class SearchMovieDto (line 4) | class SearchMovieDto {
FILE: server/src/modules/search/search.module.ts
class SearchModule (line 9) | class SearchModule {}
FILE: server/src/modules/search/search.service.ts
class SearchService (line 5) | class SearchService {
method constructor (line 7) | constructor() {
method getMovieIndex (line 13) | private getMovieIndex(): Index {
method addDocuments (line 17) | public async addDocuments(documents) {
method search (line 22) | public async search(text: string, searchParams?: SearchParams) {
FILE: server/src/modules/user/dto/user-register.req.dto.ts
class UserRegisterRequestDto (line 5) | class UserRegisterRequestDto {
FILE: server/src/modules/user/enums/user.enum.ts
type UserRoles (line 1) | enum UserRoles {
FILE: server/src/modules/user/user.controller.ts
class UserController (line 15) | class UserController {
method constructor (line 16) | constructor(private userService: UserService) {}
method doUserRegistration (line 24) | async doUserRegistration(
FILE: server/src/modules/user/user.entity.ts
class User (line 15) | class User extends BaseEntity {
method setPassword (line 49) | async setPassword(password: string) {
FILE: server/src/modules/user/user.module.ts
class UserModule (line 10) | class UserModule {}
FILE: server/src/modules/user/user.service.ts
class UserService (line 6) | class UserService {
method doUserRegistration (line 7) | async doUserRegistration(
method getUserByEmail (line 18) | async getUserByEmail(email: string): Promise<User | undefined> {
method getUserById (line 22) | async getUserById(id: number): Promise<User | undefined> {
Condensed preview — 76 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (61K chars).
[
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2022 amitavdevzone\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "readme.md",
"chars": 405,
"preview": "# Quiz Manager app\n\nThis is a sample application built for my Youtube series on Quiz manager application. The backend AP"
},
{
"path": "server/.editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_size = 2\nindent_style = space\ntrim_"
},
{
"path": "server/.eslintrc.js",
"chars": 666,
"preview": "module.exports = {\n parser: '@typescript-eslint/parser',\n parserOptions: {\n project: 'tsconfig.json',\n sourceTyp"
},
{
"path": "server/.gitignore",
"chars": 442,
"preview": "# 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*"
},
{
"path": "server/.prettierrc",
"chars": 51,
"preview": "{\n \"singleQuote\": true,\n \"trailingComma\": \"all\"\n}"
},
{
"path": "server/README.md",
"chars": 3338,
"preview": "<p align=\"center\">\n <a href=\"http://nestjs.com/\" target=\"blank\"><img src=\"https://nestjs.com/img/logo_text.svg\" width=\""
},
{
"path": "server/nest-cli.json",
"chars": 64,
"preview": "{\n \"collection\": \"@nestjs/schematics\",\n \"sourceRoot\": \"src\"\n}\n"
},
{
"path": "server/package.json",
"chars": 3529,
"preview": "{\n \"name\": \"server\",\n \"version\": \"0.0.1\",\n \"description\": \"\",\n \"author\": \"\",\n \"private\": true,\n \"license\": \"UNLICE"
},
{
"path": "server/src/app.controller.spec.ts",
"chars": 617,
"preview": "import { Test, TestingModule } from '@nestjs/testing';\nimport { AppController } from './app.controller';\nimport { AppSer"
},
{
"path": "server/src/app.controller.ts",
"chars": 1019,
"preview": "import {\n Controller,\n Get,\n Post,\n UploadedFile,\n UseInterceptors,\n} from '@nestjs/common';\nimport { FileIntercept"
},
{
"path": "server/src/app.module.ts",
"chars": 1336,
"preview": "import {\n MiddlewareConsumer,\n Module,\n NestModule,\n RequestMethod,\n} from '@nestjs/common';\nimport { ConfigModule }"
},
{
"path": "server/src/app.service.ts",
"chars": 142,
"preview": "import { Injectable } from '@nestjs/common';\n\n@Injectable()\nexport class AppService {\n getHello(): string {\n return "
},
{
"path": "server/src/app.utils.ts",
"chars": 533,
"preview": "import { HttpStatus, ValidationPipe } from '@nestjs/common';\n\nconst PASSWORD_RULE = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9"
},
{
"path": "server/src/common/constants/event.constants.ts",
"chars": 71,
"preview": "export const events = {\n RESPONSE_SUBMITTED: 'response.submitted',\n};\n"
},
{
"path": "server/src/common/decorator/api-pagination.response.ts",
"chars": 1151,
"preview": "import { applyDecorators, Type } from '@nestjs/common';\nimport { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@n"
},
{
"path": "server/src/common/dto/paginated.dto.ts",
"chars": 256,
"preview": "interface PaginationMeta {\n totalItems: number;\n itemCount: number;\n itemsPerPage: number;\n totalPages: number;\n cu"
},
{
"path": "server/src/common/exceptions/api-token-payement.exception.ts",
"chars": 224,
"preview": "import { HttpException, HttpStatus } from '@nestjs/common';\n\nexport class ApiTokenPaymentException extends HttpException"
},
{
"path": "server/src/common/middleware/api-token-check.middleware.ts",
"chars": 437,
"preview": "import { NestMiddleware } from '@nestjs/common';\nimport { NextFunction, Request, Response } from 'express';\nimport { Api"
},
{
"path": "server/src/config/app.config.ts",
"chars": 65,
"preview": "export default () => ({\n appSecret: process.env.APP_SECRET,\n});\n"
},
{
"path": "server/src/config/jwt.config.ts",
"chars": 270,
"preview": "import { JwtModuleAsyncOptions } from '@nestjs/jwt';\n\nimport appConfig from './app.config';\n\nexport const jwtConfig: Jwt"
},
{
"path": "server/src/config/typeorm.config-migrations.ts",
"chars": 74,
"preview": "import { typeOrmConfig } from './typeorm.config';\nexport = typeOrmConfig;\n"
},
{
"path": "server/src/config/typeorm.config.ts",
"chars": 1430,
"preview": "import { ConfigModule, ConfigService } from '@nestjs/config';\nimport {\n TypeOrmModuleAsyncOptions,\n TypeOrmModuleOptio"
},
{
"path": "server/src/database/data/quiz.data.ts",
"chars": 1877,
"preview": "interface ISampleData {\n quizTitle: string;\n quizDescription: string;\n questions: Array<IQuestionData>;\n}\n\ninterface "
},
{
"path": "server/src/database/factories/quiz.factory.ts",
"chars": 324,
"preview": "import { randDatabaseColumn, randParagraph, randSentence } from '@ngneat/falso';\nimport { define } from 'typeorm-seeding"
},
{
"path": "server/src/database/factories/user.factory.ts",
"chars": 328,
"preview": "import { randEmail, randFullName, randPassword } from '@ngneat/falso';\nimport { define } from 'typeorm-seeding';\nimport "
},
{
"path": "server/src/database/migrations/1649938237326-BaseMigrations.ts",
"chars": 2477,
"preview": "import {MigrationInterface, QueryRunner} from \"typeorm\";\n\nexport class BaseMigrations1649938237326 implements MigrationI"
},
{
"path": "server/src/database/migrations/1651075479367-add_user_role_column.ts",
"chars": 524,
"preview": "import {MigrationInterface, QueryRunner} from \"typeorm\";\n\nexport class addUserRoleColumn1651075479367 implements Migrati"
},
{
"path": "server/src/database/seeds/setup-data.seed.ts",
"chars": 1575,
"preview": "import { Connection, getManager } from 'typeorm';\nimport { Factory, Seeder } from 'typeorm-seeding';\nimport { Option } f"
},
{
"path": "server/src/database/seeds/user-create.seed.ts",
"chars": 614,
"preview": "import { Connection, getManager } from 'typeorm';\nimport { Factory, Seeder } from 'typeorm-seeding';\nimport { UserRoles "
},
{
"path": "server/src/main.ts",
"chars": 606,
"preview": "import { NestFactory } from '@nestjs/core';\nimport { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';\nimport { A"
},
{
"path": "server/src/modules/auth/admin-role.guard.ts",
"chars": 637,
"preview": "import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { "
},
{
"path": "server/src/modules/auth/auth.controller.ts",
"chars": 776,
"preview": "import {\n Body,\n Controller,\n Get,\n Post,\n Request,\n UseGuards,\n} from '@nestjs/common';\nimport { ApiBearerAuth, A"
},
{
"path": "server/src/modules/auth/auth.module.ts",
"chars": 637,
"preview": "import { Module } from '@nestjs/common';\nimport { AuthService } from './auth.service';\nimport { AuthController } from '."
},
{
"path": "server/src/modules/auth/auth.service.ts",
"chars": 828,
"preview": "import {\n BadRequestException,\n Injectable,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { UserService } f"
},
{
"path": "server/src/modules/auth/dto/login.dto.ts",
"chars": 415,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsEmail, IsNotEmpty } from 'class-validator';\n\nexport class Logi"
},
{
"path": "server/src/modules/auth/jwt-auth.guard.ts",
"chars": 160,
"preview": "import { Injectable } from '@nestjs/common';\nimport { AuthGuard } from '@nestjs/passport';\n\n@Injectable()\nexport class J"
},
{
"path": "server/src/modules/auth/jwt.strategy.ts",
"chars": 499,
"preview": "import { PassportStrategy } from '@nestjs/passport';\nimport { ExtractJwt, Strategy } from 'passport-jwt';\nimport appConf"
},
{
"path": "server/src/modules/auth/local-auth.guard.ts",
"chars": 164,
"preview": "import { Injectable } from '@nestjs/common';\nimport { AuthGuard } from '@nestjs/passport';\n\n@Injectable()\nexport class L"
},
{
"path": "server/src/modules/auth/local.strategy.ts",
"chars": 558,
"preview": "import { Injectable, UnauthorizedException } from '@nestjs/common';\nimport { PassportStrategy } from '@nestjs/passport';"
},
{
"path": "server/src/modules/auth/roles.decorator.ts",
"chars": 121,
"preview": "import { SetMetadata } from '@nestjs/common';\n\nexport const Roles = (...roles: string[]) => SetMetadata('roles', roles);"
},
{
"path": "server/src/modules/auth/roles.guard.ts",
"chars": 710,
"preview": "import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nim"
},
{
"path": "server/src/modules/quiz/controllers/option.controller.ts",
"chars": 1065,
"preview": "import {\n Body,\n Controller,\n Post,\n UsePipes,\n ValidationPipe,\n} from '@nestjs/common';\nimport { ApiBearerAuth, Ap"
},
{
"path": "server/src/modules/quiz/controllers/question.controller.ts",
"chars": 983,
"preview": "import {\n Body,\n Controller,\n Get,\n Post,\n UsePipes,\n ValidationPipe,\n} from '@nestjs/common';\nimport { ApiBearerA"
},
{
"path": "server/src/modules/quiz/controllers/quiz.controller.ts",
"chars": 1928,
"preview": "import {\n Body,\n Controller,\n DefaultValuePipe,\n Get,\n Param,\n ParseIntPipe,\n Post,\n Query,\n UseGuards,\n UsePi"
},
{
"path": "server/src/modules/quiz/controllers/response.controller.ts",
"chars": 768,
"preview": "import { Controller, Post } from '@nestjs/common';\nimport { EventEmitter2 } from '@nestjs/event-emitter';\nimport { ApiTa"
},
{
"path": "server/src/modules/quiz/dto/create-option.dto.ts",
"chars": 530,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class Creat"
},
{
"path": "server/src/modules/quiz/dto/create-question.dto.ts",
"chars": 429,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class Creat"
},
{
"path": "server/src/modules/quiz/dto/create-quiz.dto.ts",
"chars": 559,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class Creat"
},
{
"path": "server/src/modules/quiz/entities/option.entity.ts",
"chars": 715,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n BaseEntity,\n Column,\n Entity,\n ManyToOne,\n PrimaryGenerate"
},
{
"path": "server/src/modules/quiz/entities/question.entity.ts",
"chars": 867,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n BaseEntity,\n Column,\n Entity,\n ManyToOne,\n OneToMany,\n Pr"
},
{
"path": "server/src/modules/quiz/entities/quiz.entity.ts",
"chars": 1029,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport {\n BaseEntity,\n Column,\n Entity,\n OneToMany,\n PrimaryGenerate"
},
{
"path": "server/src/modules/quiz/events/response-add.event.ts",
"chars": 72,
"preview": "export class ResponseAddEvent {\n userId: number;\n optionId: number;\n}\n"
},
{
"path": "server/src/modules/quiz/quiz.module.ts",
"chars": 1238,
"preview": "import { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { QuestionController } "
},
{
"path": "server/src/modules/quiz/repositories/option.repository.ts",
"chars": 195,
"preview": "import { EntityRepository, Repository } from 'typeorm';\nimport { Option } from '../entities/option.entity';\n\n@EntityRepo"
},
{
"path": "server/src/modules/quiz/repositories/question.repository.ts",
"chars": 205,
"preview": "import { EntityRepository, Repository } from 'typeorm';\nimport { Question } from '../entities/question.entity';\n\n@Entity"
},
{
"path": "server/src/modules/quiz/repositories/quiz.repository.ts",
"chars": 185,
"preview": "import { EntityRepository, Repository } from 'typeorm';\nimport { Quiz } from '../entities/quiz.entity';\n\n@EntityReposito"
},
{
"path": "server/src/modules/quiz/services/option.service.ts",
"chars": 744,
"preview": "import { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { CreateOptionDt"
},
{
"path": "server/src/modules/quiz/services/question.service.ts",
"chars": 978,
"preview": "import { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { CreateQuestion"
},
{
"path": "server/src/modules/quiz/services/quiz.service.ts",
"chars": 1483,
"preview": "import { Injectable } from '@nestjs/common';\nimport { OnEvent } from '@nestjs/event-emitter';\nimport { InjectRepository "
},
{
"path": "server/src/modules/quiz/services/response.service.ts",
"chars": 429,
"preview": "import { Injectable } from '@nestjs/common';\nimport { OnEvent } from '@nestjs/event-emitter';\nimport { events } from '.."
},
{
"path": "server/src/modules/search/controllers/search.controller.ts",
"chars": 1180,
"preview": "import { Body, Controller, Get, Post } from '@nestjs/common';\nimport { ApiTags } from '@nestjs/swagger';\nimport { Search"
},
{
"path": "server/src/modules/search/dto/search-movie.dto.ts",
"chars": 291,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsNotEmpty, Length } from 'class-validator';\n\nexport class Searc"
},
{
"path": "server/src/modules/search/search.module.ts",
"chars": 267,
"preview": "import { Module } from '@nestjs/common';\nimport { SearchController } from './controllers/search.controller';\nimport { Se"
},
{
"path": "server/src/modules/search/search.service.ts",
"chars": 705,
"preview": "import { Injectable } from '@nestjs/common';\nimport MeiliSearch, { Index, SearchParams } from 'meilisearch';\n\n@Injectabl"
},
{
"path": "server/src/modules/user/dto/user-register.req.dto.ts",
"chars": 932,
"preview": "import { ApiProperty } from '@nestjs/swagger';\nimport { IsEmail, IsNotEmpty, Length, Matches } from 'class-validator';\ni"
},
{
"path": "server/src/modules/user/enums/user.enum.ts",
"chars": 82,
"preview": "enum UserRoles {\n ADMIN = 'admin',\n MEMBER = 'member',\n}\n\nexport { UserRoles };\n"
},
{
"path": "server/src/modules/user/user.controller.ts",
"chars": 864,
"preview": "import { Body, Controller, Post } from '@nestjs/common';\nimport {\n ApiBadRequestResponse,\n ApiCreatedResponse,\n ApiTa"
},
{
"path": "server/src/modules/user/user.entity.ts",
"chars": 1251,
"preview": "import {\n BaseEntity,\n BeforeInsert,\n Column,\n CreateDateColumn,\n Entity,\n PrimaryGeneratedColumn,\n UpdateDateCol"
},
{
"path": "server/src/modules/user/user.module.ts",
"chars": 267,
"preview": "import { Module } from '@nestjs/common';\nimport { UserController } from './user.controller';\nimport { UserService } from"
},
{
"path": "server/src/modules/user/user.service.ts",
"chars": 696,
"preview": "import { Injectable } from '@nestjs/common';\nimport { UserRegisterRequestDto } from './dto/user-register.req.dto';\nimpor"
},
{
"path": "server/test/app.e2e-spec.ts",
"chars": 630,
"preview": "import { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport * as re"
},
{
"path": "server/test/jest-e2e.json",
"chars": 183,
"preview": "{\n \"moduleFileExtensions\": [\"js\", \"json\", \"ts\"],\n \"rootDir\": \".\",\n \"testEnvironment\": \"node\",\n \"testRegex\": \".e2e-sp"
},
{
"path": "server/tsconfig.build.json",
"chars": 97,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
},
{
"path": "server/tsconfig.json",
"chars": 339,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"declaration\": true,\n \"removeComments\": true,\n \"emitDecorat"
},
{
"path": "server/uploads/.gitignore",
"chars": 71,
"preview": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore\n"
}
]
About this extraction
This page contains the full source code of the amitavdevzone/nest-js-quiz-manager GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 76 files (52.2 KB), approximately 15.7k tokens, and a symbol index with 122 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.