Add registration
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { AppController } from './routes/app.controller';
|
||||
import { AppService } from './routes/app.service';
|
||||
import { FinanceModule } from './routes/finance/finance.module';
|
||||
import { UsersModule } from './routes/users/users.module';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [UsersModule, FinanceModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
43
packages/backend/src/classes/auth/AuthManager.ts
Normal file
43
packages/backend/src/classes/auth/AuthManager.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { Database } from '../database/Database';
|
||||
import { UserDbEntity } from '../database/entities/User.entity';
|
||||
import { PasswordHasher } from '../cryptography/PasswordHasher';
|
||||
import { HashGenerator } from '../cryptography/HashGenerator';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
export class AuthManager {
|
||||
constructor() {}
|
||||
|
||||
static async createUserAccount(
|
||||
email: string,
|
||||
password: string,
|
||||
username: string,
|
||||
) {
|
||||
const existingUser = await this.#getUserByEmail(email);
|
||||
if (existingUser) return new BadRequestException('User already exists');
|
||||
|
||||
const passwordHash = await PasswordHasher.hash(password);
|
||||
const userHash = HashGenerator.generate(64);
|
||||
|
||||
const user = new UserDbEntity();
|
||||
user.id = randomUUID();
|
||||
user.email = email;
|
||||
user.password = passwordHash;
|
||||
user.username = username;
|
||||
user.hash = userHash;
|
||||
|
||||
await Database.repositories.users.save(user);
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
};
|
||||
}
|
||||
|
||||
static async #getUserByEmail(email: string): Promise<UserDbEntity | null> {
|
||||
if (!email || typeof email != 'string') return null;
|
||||
|
||||
return await Database.repositories.users.findOneBy({ email });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
export class HashGenerator {
|
||||
static generate(length: number) {
|
||||
return randomBytes(length / 2).toString('hex');
|
||||
}
|
||||
}
|
||||
16
packages/backend/src/classes/cryptography/PasswordHasher.ts
Normal file
16
packages/backend/src/classes/cryptography/PasswordHasher.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as bc from 'bcrypt';
|
||||
import { AUTH_PASSWORD_HASHING_ROUNDS } from 'src/config';
|
||||
|
||||
export class PasswordHasher {
|
||||
static async hash(password: string): Promise<string> {
|
||||
return await new Promise((r) =>
|
||||
bc.hash(password, AUTH_PASSWORD_HASHING_ROUNDS, (e, hash) => r(hash)),
|
||||
);
|
||||
}
|
||||
|
||||
static async compare(password: string, hash: string): Promise<boolean> {
|
||||
return await new Promise((r) =>
|
||||
bc.compare(password, hash, (e, res) => r(res)),
|
||||
);
|
||||
}
|
||||
}
|
||||
60
packages/backend/src/classes/database/Database.ts
Normal file
60
packages/backend/src/classes/database/Database.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { DB_DATABASE, DB_HOST, DB_PASSWORD, DB_USERNAME } from 'src/config';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { UserDbEntity } from './entities/User.entity';
|
||||
import { BankAccountDbEntity } from './entities/BankAccount.entity';
|
||||
|
||||
const ENTITIES = {
|
||||
users: UserDbEntity,
|
||||
bankAccounts: BankAccountDbEntity,
|
||||
};
|
||||
|
||||
export class Database {
|
||||
static instance: Database = new Database();
|
||||
|
||||
#dataSource: DataSource;
|
||||
|
||||
#dbType: 'postgres' = 'postgres';
|
||||
#dbHost = DB_HOST;
|
||||
#dbPort = 5432;
|
||||
#dbUsername = DB_USERNAME;
|
||||
#dbPassword = DB_PASSWORD;
|
||||
#dbDatabase = DB_DATABASE;
|
||||
|
||||
#entities = Object.values(ENTITIES);
|
||||
|
||||
constructor() {
|
||||
this.#dataSource = new DataSource({
|
||||
type: this.#dbType,
|
||||
host: this.#dbHost,
|
||||
port: this.#dbPort,
|
||||
username: this.#dbUsername,
|
||||
password: this.#dbPassword,
|
||||
database: this.#dbDatabase,
|
||||
synchronize: true,
|
||||
entities: this.#entities,
|
||||
});
|
||||
}
|
||||
|
||||
get repositories() {
|
||||
return {
|
||||
users: this.#dataSource.getRepository(UserDbEntity),
|
||||
bankAccounts: this.#dataSource.getRepository(BankAccountDbEntity),
|
||||
} as {
|
||||
[K in keyof typeof ENTITIES]: Repository<
|
||||
InstanceType<(typeof ENTITIES)[K]>
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
static get repositories() {
|
||||
return this.instance.repositories;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
await this.#dataSource.initialize();
|
||||
}
|
||||
|
||||
static async initialize() {
|
||||
await this.instance.initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
type Relation,
|
||||
} from 'typeorm';
|
||||
import { UserDbEntity } from './User.entity';
|
||||
|
||||
@Entity('bank_account')
|
||||
export class BankAccountDbEntity {
|
||||
@PrimaryColumn({
|
||||
type: 'varchar',
|
||||
length: 35,
|
||||
})
|
||||
id: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 100,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ManyToOne(() => UserDbEntity, (user) => user.bankAccounts)
|
||||
user: Relation<UserDbEntity>;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToMany,
|
||||
OneToMany,
|
||||
PrimaryColumn,
|
||||
Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BankAccountDbEntity } from './BankAccount.entity';
|
||||
|
||||
@Entity('user')
|
||||
export class UserDbEntity {
|
||||
@PrimaryColumn({
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
})
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
email: string;
|
||||
|
||||
@Column()
|
||||
password: string;
|
||||
|
||||
@Column()
|
||||
username: string;
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 64,
|
||||
})
|
||||
hash: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
|
||||
@OneToMany(() => BankAccountDbEntity, (bankAccount) => bankAccount.user)
|
||||
bankAccounts: Relation<BankAccountDbEntity>[];
|
||||
}
|
||||
6
packages/backend/src/config.ts
Normal file
6
packages/backend/src/config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const DB_HOST = process.env.IP_DATABASE;
|
||||
export const DB_USERNAME = process.env.POSTGRES_USER;
|
||||
export const DB_PASSWORD = process.env.POSTGRES_PASSWORD;
|
||||
export const DB_DATABASE = process.env.POSTGRES_DB;
|
||||
|
||||
export const AUTH_PASSWORD_HASHING_ROUNDS = 14;
|
||||
23
packages/backend/src/dto/CreateUserAccount.dto.ts
Normal file
23
packages/backend/src/dto/CreateUserAccount.dto.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
IsEmail,
|
||||
IsString,
|
||||
IsStrongPassword,
|
||||
Length,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateUserAccountDto {
|
||||
@IsString()
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@IsStrongPassword()
|
||||
@MaxLength(64)
|
||||
password: string;
|
||||
|
||||
@IsString()
|
||||
@Length(3, 64)
|
||||
username: string;
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { Database } from './classes/database/Database';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
|
||||
await Database.initialize();
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -14,9 +14,9 @@ describe('AppController', () => {
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
// describe('root', () => {
|
||||
// it('should return "Hello World!"', () => {
|
||||
// expect(appController.getHello()).toBe('Hello World!');
|
||||
// });
|
||||
// });
|
||||
});
|
||||
@@ -4,9 +4,4 @@ import { AppService } from './app.service';
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
4
packages/backend/src/routes/app.service.ts
Normal file
4
packages/backend/src/routes/app.service.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Controller } from '@nestjs/common';
|
||||
import { AccountService } from './account.service';
|
||||
|
||||
@Controller('/finance/account')
|
||||
export class AccountController {
|
||||
constructor(private readonly accountService: AccountService) {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AccountController } from './account.controller';
|
||||
import { AccountService } from './account.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AccountController],
|
||||
providers: [AccountService],
|
||||
})
|
||||
export class AccountModule {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AccountService {}
|
||||
9
packages/backend/src/routes/finance/finance.module.ts
Normal file
9
packages/backend/src/routes/finance/finance.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AccountModule } from './account/account.module';
|
||||
|
||||
@Module({
|
||||
imports: [AccountModule],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class FinanceModule {}
|
||||
15
packages/backend/src/routes/users/users.controller.ts
Normal file
15
packages/backend/src/routes/users/users.controller.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { UsersService } from './users.service';
|
||||
import { CreateUserAccountDto } from 'src/dto/CreateUserAccount.dto';
|
||||
|
||||
@Controller('/users')
|
||||
export class UsersController {
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@Post('/')
|
||||
async createUserAccount(
|
||||
@Body() { email, password, username }: CreateUserAccountDto,
|
||||
) {
|
||||
return await this.usersService.createUserAccount(email, password, username);
|
||||
}
|
||||
}
|
||||
10
packages/backend/src/routes/users/users.module.ts
Normal file
10
packages/backend/src/routes/users/users.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
9
packages/backend/src/routes/users/users.service.ts
Normal file
9
packages/backend/src/routes/users/users.service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthManager } from 'src/classes/auth/AuthManager';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
async createUserAccount(email: string, password: string, username: string) {
|
||||
return await AuthManager.createUserAccount(email, password, username);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user