Browse Source

Basic Users

master
Quildra 1 year ago
parent
commit
4aff6356d9
  1. 14
      config/config.json
  2. 36
      migrations/20240717194130-create-users.js
  3. 33
      migrations/20240717195823-add-users-password.js
  4. 43
      models/index.js
  5. 19
      models/user.model.ts
  6. 827
      package-lock.json
  7. 8
      package.json
  8. 23
      src/app.module.ts
  9. 18
      src/auth/auth.controller.spec.ts
  10. 30
      src/auth/auth.controller.ts
  11. 27
      src/auth/auth.module.ts
  12. 18
      src/auth/auth.service.spec.ts
  13. 35
      src/auth/auth.service.ts
  14. 6
      src/auth/jwt-auth.guard.ts
  15. 5
      src/auth/jwt-payload.interface.ts
  16. 24
      src/auth/jwt.strategy.ts
  17. 1
      src/main.ts
  18. 34
      src/users/users.controller.ts
  19. 12
      src/users/users.module.ts
  20. 49
      src/users/users.service.ts

14
config/config.json

@ -0,0 +1,14 @@
{
"development": {
"dialect": "sqlite",
"storage": "./database.sqlite"
},
"test": {
"dialect": "sqlite",
"storage": ":memory:"
},
"production": {
"dialect": "sqlite",
"storage": "./database.sqlite"
}
}

36
migrations/20240717194130-create-users.js

@ -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');
},
};

33
migrations/20240717195823-add-users-password.js

@ -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;
}
}
};

43
models/index.js

@ -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;

19
models/user.model.ts

@ -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;
}

827
package-lock.json

File diff suppressed because it is too large

8
package.json

@ -23,9 +23,15 @@
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/sequelize": "^10.0.1",
"bcrypt": "^5.1.1",
"googleapis": "^140.0.1",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"sequelize": "^6.37.3",
@ -39,6 +45,7 @@
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/passport-google-oauth20": "^2.0.16",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
@ -47,6 +54,7 @@
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"sequelize-cli": "^6.6.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",

23
src/app.module.ts

@ -1,9 +1,30 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { SequelizeModule } from '@nestjs/sequelize';
import { UsersModule } from './users/users.module';
import { User } from '../models/user.model';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [],
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
SequelizeModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
dialect: 'sqlite',
storage: configService.get<string>('DB_NAME'),
autoLoadModels: true,
synchronize: false,
}),
inject: [ConfigService],
}),
UsersModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})

18
src/auth/auth.controller.spec.ts

@ -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();
});
});

30
src/auth/auth.controller.ts

@ -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;
}
}

27
src/auth/auth.module.ts

@ -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 {}

18
src/auth/auth.service.spec.ts

@ -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();
});
});

35
src/auth/auth.service.ts

@ -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);
}
}

6
src/auth/jwt-auth.guard.ts

@ -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') {}

5
src/auth/jwt-payload.interface.ts

@ -0,0 +1,5 @@
// auth/jwt-payload.interface.ts
export interface JwtPayload {
id: number;
}

24
src/auth/jwt.strategy.ts

@ -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);
}
}

1
src/main.ts

@ -3,6 +3,7 @@ import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(3000);
}
bootstrap();

34
src/users/users.controller.ts

@ -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);
}
}

12
src/users/users.module.ts

@ -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 {}

49
src/users/users.service.ts

@ -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…
Cancel
Save