Skip to content

Commit 3b62662

Browse files
committed
first commit
1 parent 50a4452 commit 3b62662

27 files changed

+6895
-0
lines changed

.eslintrc.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
parserOptions: {
4+
project: 'tsconfig.json',
5+
sourceType: 'module',
6+
},
7+
plugins: ['@typescript-eslint/eslint-plugin'],
8+
extends: [
9+
'plugin:@typescript-eslint/recommended',
10+
'prettier/@typescript-eslint',
11+
'plugin:prettier/recommended',
12+
],
13+
root: true,
14+
env: {
15+
node: true,
16+
jest: true,
17+
},
18+
rules: {
19+
'@typescript-eslint/interface-name-prefix': 'off',
20+
'@typescript-eslint/explicit-function-return-type': 'off',
21+
'@typescript-eslint/explicit-module-boundary-types': 'off',
22+
'@typescript-eslint/no-explicit-any': 'off',
23+
},
24+
};

.gitignore

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# compiled output
2+
/dist
3+
/node_modules
4+
5+
# env files
6+
.env*
7+
!.env.example
8+
9+
# Logs
10+
logs
11+
*.log
12+
npm-debug.log*
13+
yarn-debug.log*
14+
yarn-error.log*
15+
lerna-debug.log*
16+
17+
# OS
18+
.DS_Store
19+
20+
# Tests
21+
/coverage
22+
/.nyc_output
23+
24+
# IDEs and editors
25+
/.idea
26+
.project
27+
.classpath
28+
.c9/
29+
*.launch
30+
.settings/
31+
*.sublime-workspace
32+
33+
# IDE - VSCode
34+
.vscode/*
35+
!.vscode/settings.json
36+
!.vscode/tasks.json
37+
!.vscode/launch.json
38+
!.vscode/extensions.json

.prettierrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "es5",
4+
"printWidth": 90
5+
}

nest-cli.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"collection": "@nestjs/schematics",
3+
"sourceRoot": "src"
4+
}

package.json

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"name": "nestjs-mongodb",
3+
"version": "0.0.1",
4+
"description": "",
5+
"author": "",
6+
"private": true,
7+
"license": "MIT",
8+
"scripts": {
9+
"prebuild": "rimraf dist",
10+
"build": "nest build",
11+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12+
"start": "nest start",
13+
"start:dev": "nest start --watch",
14+
"start:debug": "nest start --debug --watch",
15+
"start:prod": "node dist/main",
16+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17+
"test": "jest",
18+
"test:watch": "jest --watch",
19+
"test:cov": "jest --coverage",
20+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21+
"test:e2e": "jest --config ./test/jest-e2e.json"
22+
},
23+
"dependencies": {
24+
"@nestjs/common": "^7.5.1",
25+
"@nestjs/config": "^0.5.0",
26+
"@nestjs/core": "^7.5.1",
27+
"@nestjs/jwt": "^7.2.0",
28+
"@nestjs/passport": "^7.1.0",
29+
"@nestjs/platform-express": "^7.5.1",
30+
"@nestjs/swagger": "^4.7.0",
31+
"@typegoose/typegoose": "^7.4.1",
32+
"bcryptjs": "^2.4.3",
33+
"helmet": "^4.2.0",
34+
"mongoose": "^5.10.13",
35+
"nestjs-typegoose": "^7.1.38",
36+
"passport": "^0.4.1",
37+
"passport-jwt": "^4.0.0",
38+
"reflect-metadata": "^0.1.13",
39+
"rimraf": "^3.0.2",
40+
"rxjs": "^6.6.3",
41+
"swagger-ui-express": "^4.1.4"
42+
},
43+
"devDependencies": {
44+
"@nestjs/cli": "^7.5.1",
45+
"@nestjs/schematics": "^7.1.3",
46+
"@nestjs/testing": "^7.5.1",
47+
"@types/express": "^4.17.8",
48+
"@types/jest": "^26.0.15",
49+
"@types/node": "^14.14.6",
50+
"@types/passport-jwt": "^3.0.3",
51+
"@types/supertest": "^2.0.10",
52+
"@typescript-eslint/eslint-plugin": "^4.6.1",
53+
"@typescript-eslint/parser": "^4.6.1",
54+
"eslint": "^7.12.1",
55+
"eslint-config-prettier": "^6.15.0",
56+
"eslint-plugin-prettier": "^3.1.4",
57+
"jest": "^26.6.3",
58+
"prettier": "^2.1.2",
59+
"supertest": "^6.0.0",
60+
"ts-jest": "^26.4.3",
61+
"ts-loader": "^8.0.8",
62+
"ts-node": "^9.0.0",
63+
"tsconfig-paths": "^3.9.0",
64+
"typescript": "^4.0.5"
65+
},
66+
"jest": {
67+
"moduleFileExtensions": [
68+
"js",
69+
"json",
70+
"ts"
71+
],
72+
"rootDir": "src",
73+
"testRegex": ".*\\.spec\\.ts$",
74+
"transform": {
75+
"^.+\\.(t|j)s$": "ts-jest"
76+
},
77+
"collectCoverageFrom": [
78+
"**/*.(t|j)s"
79+
],
80+
"coverageDirectory": "../coverage",
81+
"testEnvironment": "node"
82+
}
83+
}

src/app.controller.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Controller, Get, Redirect } from '@nestjs/common';
2+
import { AppService } from './app.service';
3+
import { PublicAPI } from './decorators/public-api.decorator';
4+
5+
@PublicAPI()
6+
@Controller()
7+
export class AppController {
8+
constructor(private readonly appService: AppService) {}
9+
10+
@Get()
11+
@Redirect('/api')
12+
redirect(): void {
13+
/** redirects to api document */
14+
}
15+
}

src/app.module.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Module } from '@nestjs/common';
2+
import { AppController } from './app.controller';
3+
import { AppService } from './app.service';
4+
import { AuthModule } from './auth/auth.module';
5+
import { UserModule } from './user/user.module';
6+
import { ConfigModule, ConfigService } from '@nestjs/config';
7+
import configuration from './config/configuration';
8+
import { TypegooseModule } from 'nestjs-typegoose';
9+
10+
@Module({
11+
imports: [
12+
ConfigModule.forRoot({
13+
load: [configuration],
14+
isGlobal: true,
15+
ignoreEnvFile: process.env.NODE_ENV === 'production',
16+
}),
17+
TypegooseModule.forRootAsync({
18+
imports: [ConfigModule],
19+
inject: [ConfigService],
20+
useFactory: async (configService: ConfigService) => ({
21+
/**
22+
* fixed deprecate warnings, see --> https://mongoosejs.com/docs/deprecations.html
23+
*/
24+
uri: configService.get<string>('db.mongo_url'),
25+
useNewUrlParser: true,
26+
useCreateIndex: true,
27+
useUnifiedTopology: true,
28+
useFindAndModify: false,
29+
}),
30+
}),
31+
AuthModule,
32+
UserModule,
33+
],
34+
controllers: [AppController],
35+
providers: [AppService],
36+
})
37+
export class AppModule {}

src/app.service.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { Injectable } from '@nestjs/common';
2+
3+
@Injectable()
4+
export class AppService {}

src/auth/auth.controller.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Body, Controller, Post } from '@nestjs/common';
2+
import { ApiTags } from '@nestjs/swagger';
3+
import { AuthService } from './auth.service';
4+
import { AuthCredentialsDto } from './auth.dto';
5+
import { User } from '../model/user.model';
6+
import { PublicAPI } from '../decorators/public-api.decorator';
7+
8+
@PublicAPI()
9+
@ApiTags('Auth')
10+
@Controller('auth')
11+
export class AuthController {
12+
constructor(private readonly authService: AuthService) {}
13+
14+
@Post('login')
15+
async login(@Body() credential: AuthCredentialsDto) {
16+
return this.authService.login(credential);
17+
}
18+
19+
@Post('register')
20+
async register(@Body() user: User) {
21+
return this.authService.register(user);
22+
}
23+
}

src/auth/auth.dto.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
3+
export class AuthCredentialsDto {
4+
@ApiProperty()
5+
username: string;
6+
7+
@ApiProperty()
8+
password: string;
9+
}
10+
11+
export class AuthResponseDto {
12+
access_token: string;
13+
}

src/auth/auth.module.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Module } from '@nestjs/common';
2+
import { AuthService } from './auth.service';
3+
import { AuthController } from './auth.controller';
4+
import { PassportModule } from '@nestjs/passport';
5+
import { JwtModule } from '@nestjs/jwt';
6+
import { ConfigModule, ConfigService } from '@nestjs/config';
7+
import { JwtStrategy } from './jwt.strategy';
8+
9+
@Module({
10+
imports: [
11+
PassportModule.register({
12+
defaultStrategy: 'jwt',
13+
property: 'user',
14+
}),
15+
JwtModule.registerAsync({
16+
imports: [ConfigModule],
17+
inject: [ConfigService],
18+
useFactory: async (configService: ConfigService) => ({
19+
secret: configService.get<string>('jwt.secret'),
20+
signOptions: {
21+
expiresIn: '7d',
22+
},
23+
}),
24+
}),
25+
],
26+
controllers: [AuthController],
27+
providers: [AuthService, JwtStrategy],
28+
})
29+
export class AuthModule {}

src/auth/auth.service.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
2+
import { JwtService } from '@nestjs/jwt';
3+
import { UserService } from '../user/user.service';
4+
import { AuthCredentialsDto, AuthResponseDto } from './auth.dto';
5+
import { User } from '../model/user.model';
6+
import { compareSync } from 'bcryptjs';
7+
8+
@Injectable()
9+
export class AuthService {
10+
constructor(
11+
private readonly jwtService: JwtService,
12+
private readonly userService: UserService
13+
) {}
14+
15+
async validateUser({
16+
username,
17+
password,
18+
}: AuthCredentialsDto): Promise<{ _id: string; username: string }> {
19+
const user: User = await this.userService.findOne({ username }, true);
20+
if (user && compareSync(password, user.password)) {
21+
const { _id, username } = user;
22+
return { _id, username };
23+
}
24+
return null;
25+
}
26+
27+
async login(credential: AuthCredentialsDto): Promise<AuthResponseDto> {
28+
const user = await this.validateUser(credential);
29+
if (!user) {
30+
throw new UnauthorizedException('Wrong username or password');
31+
}
32+
const access_token = this.jwtService.sign({ user });
33+
return { access_token };
34+
}
35+
36+
async register(user: User): Promise<AuthResponseDto> {
37+
const existedUser: User = await this.userService.findOne({ username: user.username });
38+
if (existedUser) {
39+
throw new BadRequestException('Username already existed');
40+
}
41+
await this.userService.create(user);
42+
return this.login({ username: user.username, password: user.password });
43+
}
44+
}

src/auth/jwt.strategy.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ExtractJwt, Strategy } from 'passport-jwt';
2+
import { PassportStrategy } from '@nestjs/passport';
3+
import { Injectable } from '@nestjs/common';
4+
import { ConfigService } from '@nestjs/config';
5+
import { User } from '../model/user.model';
6+
7+
@Injectable()
8+
export class JwtStrategy extends PassportStrategy(Strategy) {
9+
constructor(configService: ConfigService) {
10+
super({
11+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
12+
ignoreExpiration: false,
13+
secretOrKey: configService.get<string>('jwt.secret'),
14+
});
15+
}
16+
17+
async validate(payload: { user: User; iat: number; exp: number }): Promise<User> {
18+
const { iat, exp, user } = payload;
19+
return user;
20+
}
21+
}

src/config/configuration.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default () => ({
2+
node_env: process.env.NODE_ENV,
3+
port: parseInt(process.env.PORT, 10) || 3000,
4+
db: {
5+
mongo_url: process.env.MONGO_URL,
6+
},
7+
jwt: {
8+
secret: process.env.JWT_SECRET,
9+
},
10+
});
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { SetMetadata } from '@nestjs/common';
2+
3+
export const PublicAPI = () => SetMetadata('isPublicAPI', true);

src/decorators/user-id.decorator.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2+
3+
export const UserId = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
4+
const request = ctx.switchToHttp().getRequest();
5+
return request.user._id;
6+
});

0 commit comments

Comments
 (0)