├── .env ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── auth │ ├── Dtos │ │ ├── LoginDto.ts │ │ └── SignUpDto.ts │ ├── auth.controller.ts │ ├── auth.module.ts │ ├── auth.schema.ts │ ├── auth.service.ts │ ├── auth.types.ts │ ├── get-user.decorator.ts │ ├── jwt.strategy.ts │ ├── role.guard.ts │ └── roles.decorator.ts └── main.ts ├── tsconfig.build.json ├── tsconfig.json └── uploads └── 1703576207348-371859521_312871814585452_972757495755697985_n.jpg /.env: -------------------------------------------------------------------------------- 1 | JWT_SECRET=Aceiny631@@. 2 | MONGO_URI=mongodb+srv://Aceiny:LJncaXBX4nm9nVeX@taskmanager.jcz9knr.mongodb.net/SoaiBackendChallange?retryWrites=true&w=majority -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Installation 3 | 4 | ```bash 5 | $ npm install 6 | ``` 7 | 8 | ## Running the app 9 | 10 | ```bash 11 | # development 12 | $ npm run start 13 | 14 | # watch mode 15 | $ npm run start:dev 16 | 17 | # production mode 18 | $ npm run start:prod 19 | ``` 20 | 21 | ## about this project 22 | ```bash 23 | response time : 80ms-150ms-500ms 24 | 25 | /auth/login //POST login both user and admin can login 26 | /auth/signup //POST signup only admin can create a new account and assign a role to it weather user or admin , example 27 | { 28 | "username" : "aceiny", 29 | "password" : "1234", 30 | "role" : "USER" or 31 | "role" : "ADMIN" 32 | } 33 | /event //GET get all events , you can use filtering with all event values 34 | /event/:id //GET get a single event by id 35 | /event //POST create a new event with event data and event poster -image- can only be accesd by admin example 36 | { 37 | "title" : "this is a title", 38 | "description" : "wow", 39 | "date" : "25 sep 2022 ", 40 | "location" : "medjana " , 41 | "poster" : one image buffer 42 | } 43 | 44 | /event/:id //PATCH update any field of an existing event , needs to be admin example 45 | { 46 | "status" : "OPEN" or 47 | "status" : "CLOSED" 48 | } 49 | /event/:id //DELETE delte event , needs to be admin 50 | 51 | ``` -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-js", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/core": "^10.0.0", 25 | "@nestjs/jwt": "^10.2.0", 26 | "@nestjs/mongoose": "^10.0.2", 27 | "@nestjs/passport": "^10.0.3", 28 | "@nestjs/platform-express": "^10.0.0", 29 | "bcrypt": "^5.1.1", 30 | "class-transformer": "^0.5.1", 31 | "class-validator": "^0.14.0", 32 | "dotenv": "^16.3.1", 33 | "mongoose": "^8.0.3", 34 | "multer": "^1.4.5-lts.1", 35 | "passport": "^0.7.0", 36 | "passport-jwt": "^4.0.1", 37 | "reflect-metadata": "^0.1.13", 38 | "rxjs": "^7.8.1", 39 | "serve-static": "^1.15.0" 40 | }, 41 | "devDependencies": { 42 | "@nestjs/cli": "^10.0.0", 43 | "@nestjs/schematics": "^10.0.0", 44 | "@nestjs/testing": "^10.0.0", 45 | "@types/express": "^4.17.17", 46 | "@types/jest": "^29.5.2", 47 | "@types/node": "^20.3.1", 48 | "@types/supertest": "^2.0.12", 49 | "@typescript-eslint/eslint-plugin": "^6.0.0", 50 | "@typescript-eslint/parser": "^6.0.0", 51 | "eslint": "^8.42.0", 52 | "eslint-config-prettier": "^9.0.0", 53 | "eslint-plugin-prettier": "^5.0.0", 54 | "jest": "^29.5.0", 55 | "prettier": "^3.0.0", 56 | "source-map-support": "^0.5.21", 57 | "supertest": "^6.3.3", 58 | "ts-jest": "^29.1.0", 59 | "ts-loader": "^9.4.3", 60 | "ts-node": "^10.9.1", 61 | "tsconfig-paths": "^4.2.0", 62 | "typescript": "^5.1.3" 63 | }, 64 | "jest": { 65 | "moduleFileExtensions": [ 66 | "js", 67 | "json", 68 | "ts" 69 | ], 70 | "rootDir": "src", 71 | "testRegex": ".*\\.spec\\.ts$", 72 | "transform": { 73 | "^.+\\.(t|j)s$": "ts-jest" 74 | }, 75 | "collectCoverageFrom": [ 76 | "**/*.(t|j)s" 77 | ], 78 | "coverageDirectory": "../coverage", 79 | "testEnvironment": "node" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { AuthModule } from './auth/auth.module'; 6 | require('dotenv').config() 7 | @Module({ 8 | imports: [ 9 | MongooseModule.forRoot(process.env.MONGO_URI) , 10 | AuthModule 11 | ], 12 | controllers: [AppController], 13 | providers: [AppService], 14 | }) 15 | export class AppModule {} 16 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello This is a simple nest js app created by aceiny , go check other routes'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/auth/Dtos/LoginDto.ts: -------------------------------------------------------------------------------- 1 | import {IsEmail,IsNotEmpty, MinLength } from "class-validator"; 2 | 3 | export class LoginDto { 4 | @IsNotEmpty() 5 | @IsEmail() 6 | Email: string; 7 | 8 | @IsNotEmpty() 9 | @MinLength(4) 10 | Password: string; 11 | } -------------------------------------------------------------------------------- /src/auth/Dtos/SignUpDto.ts: -------------------------------------------------------------------------------- 1 | import {IsEmail,IsNotEmpty, IsPhoneNumber, MinLength } from "class-validator"; 2 | 3 | export class SignUpDto { 4 | @IsNotEmpty() 5 | Firstname : string 6 | 7 | @IsNotEmpty() 8 | Lastname: string 9 | 10 | @IsNotEmpty() 11 | @IsPhoneNumber('DZ') 12 | Phone : string 13 | 14 | @IsNotEmpty() 15 | @IsEmail() 16 | Email : string 17 | 18 | @IsNotEmpty() 19 | @MinLength(8) 20 | Password : string 21 | } -------------------------------------------------------------------------------- /src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Post, UsePipes, ValidationPipe } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { LoginDto} from './Dtos/LoginDto'; 4 | import { SignUpDto } from './Dtos/SignUpDto'; 5 | 6 | @Controller('auth') 7 | export class AuthController { 8 | constructor( 9 | private readonly authService : AuthService 10 | ){} 11 | 12 | @Post('/signup') 13 | @UsePipes(ValidationPipe) 14 | signUp(@Body() SignUpDto : SignUpDto){ 15 | return this.authService.Signup(SignUpDto) 16 | } 17 | 18 | @Post('/login') 19 | @UsePipes(ValidationPipe) 20 | login(@Body() LoginDto : LoginDto){ 21 | return this.authService.Login(LoginDto) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthController } from './auth.controller'; 3 | import { AuthService } from './auth.service'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { User, UserSchema } from './auth.schema'; 6 | import { JwtModule } from '@nestjs/jwt'; 7 | import { PassportModule } from '@nestjs/passport'; 8 | import { JwtStrategy } from './jwt.strategy'; 9 | import { RolesGuard } from './role.guard'; 10 | require('dotenv').config() 11 | @Module({ 12 | imports: [ 13 | PassportModule.register({ 14 | defaultStrategy : 'jwt' 15 | }), 16 | JwtModule.register({ 17 | secret : process.env.JWT_SECRET, 18 | signOptions : { 19 | expiresIn : '30d' 20 | } 21 | }), 22 | MongooseModule.forFeature([{name : User.name , schema : UserSchema }]) 23 | ], 24 | controllers: [AuthController], 25 | providers: [ 26 | AuthService, 27 | JwtStrategy, 28 | RolesGuard, 29 | ], 30 | exports : [ 31 | JwtStrategy, 32 | PassportModule, 33 | JwtModule 34 | ] 35 | 36 | }) 37 | export class AuthModule {} 38 | -------------------------------------------------------------------------------- /src/auth/auth.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; 2 | import { Document } from "mongoose"; 3 | 4 | @Schema() 5 | export class User extends Document { 6 | @Prop() 7 | Firstname : string 8 | 9 | @Prop() 10 | Lastname : string 11 | 12 | @Prop() 13 | Fullname : string 14 | 15 | @Prop() 16 | Phone : string 17 | 18 | @Prop({default : false}) 19 | IsVerfied : boolean 20 | 21 | @Prop({unique : true}) 22 | Email: string; 23 | 24 | @Prop() 25 | Password: string; 26 | 27 | @Prop() 28 | Role : string; 29 | 30 | @Prop() 31 | TWithdraw : number 32 | 33 | @Prop() 34 | Tresponses : number 35 | //@Prop() 36 | //RTSurvies SurveyId[] 37 | } 38 | 39 | export const UserSchema = SchemaFactory.createForClass(User); -------------------------------------------------------------------------------- /src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { ConflictException, Injectable, InternalServerErrorException, UnauthorizedException } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { User } from './auth.schema'; 4 | import { Model } from 'mongoose'; 5 | import * as bcrypt from 'bcrypt' 6 | import { JwtService } from '@nestjs/jwt'; 7 | import { SignUpDto } from './Dtos/SignUpDto'; 8 | import { LoginDto} from './Dtos/LoginDto'; 9 | @Injectable() 10 | export class AuthService { 11 | constructor( 12 | @InjectModel(User.name) 13 | private userModel : Model, 14 | private jwtService : JwtService 15 | ) {} 16 | 17 | async Signup(SignUpDto : SignUpDto) : Promise { 18 | const exist = await this.userModel.findOne({ email : SignUpDto.Email}) 19 | if(exist){ 20 | throw new ConflictException('Email already exist') 21 | } 22 | const { Firstname , Lastname , Phone , Email } = SignUpDto 23 | let { Password } = SignUpDto 24 | const salt = bcrypt.genSaltSync(10); 25 | Password = bcrypt.hashSync(Password, salt); 26 | const user = await this.userModel.create({ 27 | Firstname , 28 | Lastname , 29 | Phone , 30 | Email , 31 | Password, 32 | }) 33 | if(!user){ 34 | throw new InternalServerErrorException('User not created'); 35 | } 36 | return user 37 | } 38 | 39 | async Login(LoginDto : LoginDto) : Promise<{}> { 40 | const user = await this.userModel.findOne({Email : LoginDto.Email}) 41 | if(!user){ 42 | throw new UnauthorizedException('Email not exist') 43 | } 44 | if(!bcrypt.compareSync(LoginDto.Password, user.Password)){ 45 | throw new UnauthorizedException('Password not match') 46 | } 47 | return { 48 | Status : 200 , 49 | Token : this.jwtService.sign({id : user._id ,Email : user.Email, Role : user.Role}), 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/auth/auth.types.ts: -------------------------------------------------------------------------------- 1 | export enum userRole { 2 | ENTERPRISE = 'ENTERPRISE', 3 | ADMIN = 'ADMIN', 4 | COLLECTOR = 'COLLECTOR', 5 | } -------------------------------------------------------------------------------- /src/auth/get-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createParamDecorator, 3 | } from '@nestjs/common'; 4 | 5 | export const GetUser = createParamDecorator((data , req)=>{ 6 | return req.user 7 | }) -------------------------------------------------------------------------------- /src/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from "@nestjs/passport"; 2 | import { Strategy, ExtractJwt } from 'passport-jwt'; 3 | require('dotenv').config() 4 | 5 | export class JwtStrategy extends PassportStrategy(Strategy) { 6 | constructor(){ 7 | super({ 8 | jwtFromRequest : ExtractJwt.fromAuthHeaderAsBearerToken(), 9 | secretOrKey : process.env.JWT_SECRET 10 | }) 11 | } 12 | async validate(payload : any){ 13 | return { id : payload.id , email : payload.email , role : payload.role} 14 | } 15 | } -------------------------------------------------------------------------------- /src/auth/role.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { JwtService } from '@nestjs/jwt'; 4 | const ROLES_KEY = 'roles'; 5 | @Injectable() 6 | export class RolesGuard implements CanActivate { 7 | constructor( 8 | private reflector: Reflector, 9 | private jwtService: JwtService 10 | ) {} 11 | 12 | canActivate(context: ExecutionContext): boolean { 13 | const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | if (!requiredRoles) { 18 | return true; 19 | } 20 | const request = context.switchToHttp().getRequest(); 21 | const jwt = request.headers.authorization.split(' ')[1]; 22 | const { role } = this.jwtService.verify(jwt); 23 | console.log(role); 24 | return requiredRoles.includes(role); 25 | } 26 | } -------------------------------------------------------------------------------- /src/auth/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const ROLES_KEY = 'roles'; 4 | export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import * as serveStatic from 'serve-static'; 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | app.use('/uploads', serveStatic('uploads')); 7 | await app.listen(3000); 8 | } 9 | bootstrap(); -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /uploads/1703576207348-371859521_312871814585452_972757495755697985_n.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aceiny/Nest-Task-Manager/c8efc72bb3a2a415baf103e8c11537d3b5edf19f/uploads/1703576207348-371859521_312871814585452_972757495755697985_n.jpg --------------------------------------------------------------------------------