20 changed files with 1236 additions and 26 deletions
@ -0,0 +1,14 @@ |
|||
{ |
|||
"development": { |
|||
"dialect": "sqlite", |
|||
"storage": "./database.sqlite" |
|||
}, |
|||
"test": { |
|||
"dialect": "sqlite", |
|||
"storage": ":memory:" |
|||
}, |
|||
"production": { |
|||
"dialect": "sqlite", |
|||
"storage": "./database.sqlite" |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
'use strict'; |
|||
|
|||
module.exports = { |
|||
up: async (queryInterface, Sequelize) => { |
|||
await queryInterface.createTable('Users', { |
|||
id: { |
|||
type: Sequelize.INTEGER, |
|||
primaryKey: true, |
|||
autoIncrement: true, |
|||
allowNull: false, |
|||
}, |
|||
name: { |
|||
type: Sequelize.STRING, |
|||
allowNull: false, |
|||
}, |
|||
email: { |
|||
type: Sequelize.STRING, |
|||
allowNull: false, |
|||
}, |
|||
createdAt: { |
|||
type: Sequelize.DATE, |
|||
allowNull: false, |
|||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), |
|||
}, |
|||
updatedAt: { |
|||
type: Sequelize.DATE, |
|||
allowNull: false, |
|||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), |
|||
}, |
|||
}); |
|||
}, |
|||
|
|||
down: async (queryInterface, Sequelize) => { |
|||
await queryInterface.dropTable('Users'); |
|||
}, |
|||
}; |
|||
@ -0,0 +1,33 @@ |
|||
'use strict'; |
|||
|
|||
/** @type {import('sequelize-cli').Migration} */ |
|||
module.exports = { |
|||
async up (queryInterface, Sequelize) { |
|||
const transaction = await queryInterface.sequelize.transaction(); |
|||
try { |
|||
await queryInterface.addColumn( |
|||
'Users', |
|||
'password', |
|||
{ |
|||
type: Sequelize.STRING, |
|||
}, |
|||
{ transaction } |
|||
); |
|||
await transaction.commit(); |
|||
} catch (err) { |
|||
await transaction.rollback(); |
|||
throw err; |
|||
} |
|||
}, |
|||
|
|||
async down (queryInterface, Sequelize) { |
|||
const transaction = await queryInterface.sequelize.transaction(); |
|||
try { |
|||
await queryInterface.removeColumn('Users', 'password', { transaction }); |
|||
await transaction.commit(); |
|||
} catch (err) { |
|||
await transaction.rollback(); |
|||
throw err; |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,43 @@ |
|||
'use strict'; |
|||
|
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
const Sequelize = require('sequelize'); |
|||
const process = require('process'); |
|||
const basename = path.basename(__filename); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../config/config.json')[env]; |
|||
const db = {}; |
|||
|
|||
let sequelize; |
|||
if (config.use_env_variable) { |
|||
sequelize = new Sequelize(process.env[config.use_env_variable], config); |
|||
} else { |
|||
sequelize = new Sequelize(config.database, config.username, config.password, config); |
|||
} |
|||
|
|||
fs |
|||
.readdirSync(__dirname) |
|||
.filter(file => { |
|||
return ( |
|||
file.indexOf('.') !== 0 && |
|||
file !== basename && |
|||
file.slice(-3) === '.js' && |
|||
file.indexOf('.test.js') === -1 |
|||
); |
|||
}) |
|||
.forEach(file => { |
|||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); |
|||
db[model.name] = model; |
|||
}); |
|||
|
|||
Object.keys(db).forEach(modelName => { |
|||
if (db[modelName].associate) { |
|||
db[modelName].associate(db); |
|||
} |
|||
}); |
|||
|
|||
db.sequelize = sequelize; |
|||
db.Sequelize = Sequelize; |
|||
|
|||
module.exports = db; |
|||
@ -0,0 +1,19 @@ |
|||
import { Column, Model, Table } from 'sequelize-typescript'; |
|||
|
|||
@Table |
|||
export class User extends Model<User> { |
|||
@Column({ |
|||
primaryKey: true, |
|||
autoIncrement: true, |
|||
}) |
|||
id: number; |
|||
|
|||
@Column |
|||
name: string; |
|||
|
|||
@Column |
|||
email: string; |
|||
|
|||
@Column |
|||
password: string; |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,18 @@ |
|||
import { Test, TestingModule } from '@nestjs/testing'; |
|||
import { AuthController } from './auth.controller'; |
|||
|
|||
describe('AuthController', () => { |
|||
let controller: AuthController; |
|||
|
|||
beforeEach(async () => { |
|||
const module: TestingModule = await Test.createTestingModule({ |
|||
controllers: [AuthController], |
|||
}).compile(); |
|||
|
|||
controller = module.get<AuthController>(AuthController); |
|||
}); |
|||
|
|||
it('should be defined', () => { |
|||
expect(controller).toBeDefined(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,30 @@ |
|||
// auth/auth.controller.ts
|
|||
import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common'; |
|||
import { AuthService } from './auth.service'; |
|||
import { JwtAuthGuard } from './jwt-auth.guard'; |
|||
import { User } from '../../models/user.model'; |
|||
|
|||
@Controller('auth') |
|||
export class AuthController { |
|||
constructor(private readonly authService: AuthService) {} |
|||
|
|||
@Post('login') |
|||
async login(@Body() body) { |
|||
const user = await this.authService.validateUser(body.email, body.password); |
|||
if (!user) { |
|||
return { error: 'Invalid credentials' }; |
|||
} |
|||
return this.authService.login(user); |
|||
} |
|||
|
|||
@Post('register') |
|||
async register(@Body() body: User) { |
|||
return this.authService.register(body); |
|||
} |
|||
|
|||
@UseGuards(JwtAuthGuard) |
|||
@Post('profile') |
|||
getProfile(@Request() req) { |
|||
return req.user; |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// auth/auth.module.ts
|
|||
import { Module } from '@nestjs/common'; |
|||
import { JwtModule } from '@nestjs/jwt'; |
|||
import { PassportModule } from '@nestjs/passport'; |
|||
import { ConfigModule, ConfigService } from '@nestjs/config'; |
|||
import { AuthService } from './auth.service'; |
|||
import { AuthController } from './auth.controller'; |
|||
import { JwtStrategy } from './jwt.strategy'; |
|||
import { UsersModule } from '../users/users.module'; |
|||
|
|||
@Module({ |
|||
imports: [ |
|||
UsersModule, |
|||
PassportModule, |
|||
JwtModule.registerAsync({ |
|||
imports: [ConfigModule], |
|||
useFactory: async (configService: ConfigService) => ({ |
|||
secret: configService.get<string>('JWT_SECRET'), |
|||
signOptions: { expiresIn: '60m' }, |
|||
}), |
|||
inject: [ConfigService], |
|||
}), |
|||
], |
|||
providers: [AuthService, JwtStrategy], |
|||
controllers: [AuthController], |
|||
}) |
|||
export class AuthModule {} |
|||
@ -0,0 +1,18 @@ |
|||
import { Test, TestingModule } from '@nestjs/testing'; |
|||
import { AuthService } from './auth.service'; |
|||
|
|||
describe('AuthService', () => { |
|||
let service: AuthService; |
|||
|
|||
beforeEach(async () => { |
|||
const module: TestingModule = await Test.createTestingModule({ |
|||
providers: [AuthService], |
|||
}).compile(); |
|||
|
|||
service = module.get<AuthService>(AuthService); |
|||
}); |
|||
|
|||
it('should be defined', () => { |
|||
expect(service).toBeDefined(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,35 @@ |
|||
// auth/auth.service.ts
|
|||
import { Injectable } from '@nestjs/common'; |
|||
import { JwtService } from '@nestjs/jwt'; |
|||
import * as bcrypt from 'bcrypt'; |
|||
import { UsersService } from '../users/users.service'; |
|||
import { User } from '../../models/user.model'; |
|||
import { JwtPayload } from './jwt-payload.interface'; |
|||
|
|||
@Injectable() |
|||
export class AuthService { |
|||
constructor( |
|||
private readonly usersService: UsersService, |
|||
private readonly jwtService: JwtService, |
|||
) {} |
|||
|
|||
async validateUser(email: string, pass: string): Promise<User> { |
|||
const user = await this.usersService.findByEmail(email); |
|||
if (user && bcrypt.compareSync(pass, user.password)) { |
|||
return user; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
async login(user: User) { |
|||
const payload: JwtPayload = { id: user.id }; |
|||
return { |
|||
access_token: this.jwtService.sign(payload), |
|||
}; |
|||
} |
|||
|
|||
async register(user: User) { |
|||
user.password = bcrypt.hashSync(user.password, 10); |
|||
return this.usersService.create(user); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
// auth/jwt-auth.guard.ts
|
|||
import { Injectable } from '@nestjs/common'; |
|||
import { AuthGuard } from '@nestjs/passport'; |
|||
|
|||
@Injectable() |
|||
export class JwtAuthGuard extends AuthGuard('jwt') {} |
|||
@ -0,0 +1,5 @@ |
|||
// auth/jwt-payload.interface.ts
|
|||
export interface JwtPayload { |
|||
id: number; |
|||
} |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
// auth/jwt.strategy.ts
|
|||
import { Injectable } from '@nestjs/common'; |
|||
import { PassportStrategy } from '@nestjs/passport'; |
|||
import { ExtractJwt, Strategy } from 'passport-jwt'; |
|||
import { ConfigService } from '@nestjs/config'; |
|||
import { JwtPayload } from './jwt-payload.interface'; |
|||
import { UsersService } from '../users/users.service'; |
|||
|
|||
@Injectable() |
|||
export class JwtStrategy extends PassportStrategy(Strategy) { |
|||
constructor( |
|||
private readonly usersService: UsersService, |
|||
private readonly configService: ConfigService, |
|||
) { |
|||
super({ |
|||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), |
|||
secretOrKey: configService.get<string>('JWT_SECRET'), |
|||
}); |
|||
} |
|||
|
|||
async validate(payload: JwtPayload) { |
|||
return this.usersService.findOne(payload.id); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// users/users.controller.ts
|
|||
import { Controller, Get, Post, Body, Param, Delete, Put } from '@nestjs/common'; |
|||
import { UsersService } from './users.service'; |
|||
import { User } from '../../models/user.model'; |
|||
|
|||
@Controller('users') |
|||
export class UsersController { |
|||
constructor(private readonly usersService: UsersService) {} |
|||
|
|||
@Get() |
|||
findAll(): Promise<User[]> { |
|||
return this.usersService.findAll(); |
|||
} |
|||
|
|||
@Get(':id') |
|||
findOne(@Param('id') id: number): Promise<User> { |
|||
return this.usersService.findOne(id); |
|||
} |
|||
|
|||
@Post() |
|||
create(@Body() user: User): Promise<User> { |
|||
return this.usersService.create(user); |
|||
} |
|||
|
|||
@Put(':id') |
|||
update(@Param('id') id: number, @Body() user: User): Promise<void> { |
|||
return this.usersService.update(id, user); |
|||
} |
|||
|
|||
@Delete(':id') |
|||
remove(@Param('id') id: number): Promise<void> { |
|||
return this.usersService.remove(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// users/users.module.ts
|
|||
import { Module } from '@nestjs/common'; |
|||
import { SequelizeModule } from '@nestjs/sequelize'; |
|||
import { UsersService } from './users.service'; |
|||
import { User } from '../../models/user.model' |
|||
|
|||
@Module({ |
|||
imports: [SequelizeModule.forFeature([User])], |
|||
providers: [UsersService], |
|||
exports: [UsersService], |
|||
}) |
|||
export class UsersModule {} |
|||
@ -0,0 +1,49 @@ |
|||
// users/users.service.ts
|
|||
import { Injectable } from '@nestjs/common'; |
|||
import { InjectModel } from '@nestjs/sequelize'; |
|||
import { User } from '../../models/user.model' |
|||
|
|||
@Injectable() |
|||
export class UsersService { |
|||
constructor( |
|||
@InjectModel(User) |
|||
private userModel: typeof User, |
|||
) {} |
|||
|
|||
findAll(): Promise<User[]> { |
|||
return this.userModel.findAll(); |
|||
} |
|||
|
|||
findOne(id: number): Promise<User> { |
|||
return this.userModel.findOne({ |
|||
where: { |
|||
id, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
findByEmail(email: string): Promise<User> { |
|||
return this.userModel.findOne({ |
|||
where: { |
|||
email, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
create(user: User): Promise<User> { |
|||
return this.userModel.create(user); |
|||
} |
|||
|
|||
async update(id: number, user: User): Promise<void> { |
|||
await this.userModel.update(user, { |
|||
where: { |
|||
id, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
async remove(id: number): Promise<void> { |
|||
const user = await this.findOne(id); |
|||
await user.destroy(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue