temp commit, not functioning

This commit is contained in:
2023-04-20 23:18:31 +08:00
parent 7fc284c2c0
commit 7be9dd3e89
47 changed files with 785 additions and 18 deletions

View File

@@ -13,8 +13,8 @@ import { UsersModule } from './users/users.module';
import { AllExceptionsFilter } from './logger/any-exception.filter';
import loggerMiddleware from './logger/logger.middleware';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StaffsModule } from './staffs/staffs.module';
import { DepartmentModule } from './department/department.module';
import { MySqlCompanyModule } from './mysqlcompany/mysqlcompany.module';
import { OrmMongoCompanyModule } from './ormmongocompany/ormmongocompany.module';
@Module({
imports: [
@@ -24,10 +24,10 @@ import { DepartmentModule } from './department/department.module';
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
port: 23306,
username: 'root',
password: 'example',
database: 'example_nodejs_nest_crud',
database: 'example_nodejs_nest_crud_company',
// entities: ['dist/modules/**/*.mysql.entity{.ts,.js}'],
autoLoadEntities: true,
// IMPORTANT: disable sync
@@ -35,9 +35,12 @@ import { DepartmentModule } from './department/department.module';
// synchronize: true,
logging: true,
}),
// Router resigration for module (2nd level) will be declared inside the module
// RouterModule.register([
// ]),
UsersModule,
StaffsModule,
DepartmentModule,
MySqlCompanyModule,
OrmMongoCompanyModule,
],
controllers: [AppController],
providers: [

View File

@@ -0,0 +1,7 @@
# MysqlComany Module
MySQL-Comany module uses TypeORM + MySQL
This module demonstrates database relationships, including:
- One-to-many
- Many-to-many

View File

@@ -17,7 +17,7 @@ import { DepartmentCreateDto } from './dto/department-create.dto';
import { DepartmentUpdateDto } from './dto/department-update.dto';
import { EntityNotFoundError } from 'typeorm';
@Controller('department')
@Controller()
export class DepartmentController {
constructor(private readonly departmentService: DepartmentService) {}

View File

@@ -1,4 +1,4 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EntityNotFoundError, Repository } from 'typeorm';
import { Department } from './entities/department.entity';

View File

@@ -1,9 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { Staff } from 'src/staffs/entities/staff.entity';
import { Staff } from 'src/mysqlcompany/staffs/entities/staff.entity';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
// @Entity('departments')
@Entity()
@Entity('departments')
export class Department {
@ApiProperty({
type: Number,

View File

@@ -0,0 +1,7 @@
import { Controller } from '@nestjs/common';
import { MySqlCompanyService } from './mysqlcompany.service';
@Controller()
export class MySqlCompanyController {
constructor(private readonly mySqlCompanyService: MySqlCompanyService) {}
}

View File

@@ -0,0 +1,35 @@
import { Logger, Module } from '@nestjs/common';
import { MySqlCompanyService } from './mysqlcompany.service';
import { MySqlCompanyController } from './mysqlcompany.controller';
import { DepartmentModule } from './department/department.module';
import { StaffsModule } from './staffs/staffs.module';
import { RouterModule } from '@nestjs/core';
@Module({
imports: [
// The router register is commonly placed in app.module
// However, I suggest to put registation in the 2nd level module only
// for easy management (i.e. not for root and children level)
RouterModule.register([
{
path: 'mysql-company',
module: MySqlCompanyModule,
children: [
{
path: 'departments',
module: DepartmentModule,
},
{
path: 'staffs',
module: StaffsModule,
},
],
},
]),
DepartmentModule,
StaffsModule,
],
controllers: [MySqlCompanyController],
providers: [Logger, MySqlCompanyService],
})
export class MySqlCompanyModule {}

View File

@@ -0,0 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class MySqlCompanyService {
constructor(private readonly logger: Logger) {}
}

View File

@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { Department } from 'src/department/entities/department.entity';
import { Department } from 'src/mysqlcompany/department/entities/department.entity';
import {
Column,
Entity,
@@ -9,7 +9,7 @@ import {
PrimaryGeneratedColumn,
} from 'typeorm';
// @Entity('staffs')
@Entity('staffs')
@Entity()
export class Staff {
@ApiProperty({

View File

@@ -17,7 +17,7 @@ import { StaffCreateDto } from './dto/staff-create.dto';
import { StaffUpdateDto } from './dto/staff-update.dto';
import { EntityNotFoundError } from 'typeorm';
@Controller('staffs')
@Controller()
export class StaffsController {
constructor(private readonly staffsService: StaffsService) {}

View File

@@ -0,0 +1,7 @@
# MysqlComany Module
MySQL-Comany module uses TypeORM + MySQL
This module demonstrates database relationships, including:
- One-to-many
- Many-to-many

View File

@@ -0,0 +1,10 @@
# Department Module
- This module demonstrates using:
- CRUD
- TypeORM: MySQL
- TypeORM Repository
- No QueryBuilder
- TypeOrmModule.forFeature & TypeOrmModule.forRoot{ autoLoadEntities: true }
- The Entities
- DB and Web-API entities are shared

View File

@@ -0,0 +1,78 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
NotFoundException,
HttpCode,
BadRequestException,
} from '@nestjs/common';
import { ApiResponse } from '@nestjs/swagger';
import { DepartmentService } from './department.service';
import { Department } from './entities/department.entity';
import { DepartmentCreateDto } from './dto/department-create.dto';
import { DepartmentUpdateDto } from './dto/department-update.dto';
import { EntityNotFoundError } from 'typeorm';
@Controller()
export class DepartmentController {
constructor(private readonly departmentService: DepartmentService) {}
private handleEntityNotFoundError(id: number, e: Error) {
if (e instanceof EntityNotFoundError) {
throw new NotFoundException(`Not found: id=${id}`);
} else {
throw e;
}
}
@Get()
@ApiResponse({ type: [Department] })
findAll(): Promise<Department[]> {
return this.departmentService.findAll();
}
@Get(':id')
@ApiResponse({ type: Department })
async findOne(@Param('id') id: number): Promise<Department | null> {
// NOTE: the + operator returns the numeric representation of the object.
return this.departmentService.findOne(+id).catch((err) => {
this.handleEntityNotFoundError(id, err);
return null;
});
}
@Post()
@ApiResponse({ type: [Department] })
create(@Body() departmentCreateDto: DepartmentCreateDto) {
return this.departmentService.create(departmentCreateDto);
}
@Patch(':id')
// Status 204 because no content will be returned
@HttpCode(204)
async update(
@Param('id') id: number,
@Body() departmentUpdateDto: DepartmentUpdateDto,
): Promise<void> {
if (Object.keys(departmentUpdateDto).length === 0) {
throw new BadRequestException('Request body is empty');
}
await this.departmentService
.update(+id, departmentUpdateDto)
.catch((err) => {
this.handleEntityNotFoundError(id, err);
});
}
@Delete(':id')
@HttpCode(204)
async remove(@Param('id') id: number): Promise<void> {
await this.departmentService.remove(+id).catch((err) => {
this.handleEntityNotFoundError(id, err);
});
}
}

View File

@@ -0,0 +1,15 @@
import { Logger, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DepartmentService } from './department.service';
import { DepartmentController } from './department.controller';
import { Department } from './entities/department.entity';
@Module({
imports: [TypeOrmModule.forFeature([Department])],
controllers: [DepartmentController],
providers: [Logger, DepartmentService],
// If you want to use the repository outside of the module
// which imports TypeOrmModule.forFeature, you'll need to re-export the providers generated by it.
// exports: [TypeOrmModule],
})
export class DepartmentModule {}

View File

@@ -0,0 +1,55 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EntityNotFoundError, Repository } from 'typeorm';
import { Department } from './entities/department.entity';
import { DepartmentCreateDto } from './dto/department-create.dto';
import { DepartmentUpdateDto } from './dto/department-update.dto';
@Injectable()
export class DepartmentService {
constructor(
private readonly logger: Logger,
@InjectRepository(Department)
private departmentRepository: Repository<Department>,
) {}
create(departmentCreateDto: DepartmentCreateDto): Promise<Department> {
// Use Repository.create() will copy the values to destination object.
const department = this.departmentRepository.create(departmentCreateDto);
return this.departmentRepository.save(department);
}
findAll(): Promise<Department[]> {
return this.departmentRepository.find();
}
findOne(id: number): Promise<Department> {
return this.departmentRepository.findOneOrFail({ where: { id } });
}
async update(
id: number,
departmentUpdateDto: DepartmentUpdateDto,
): Promise<void> {
await this.isExist(id);
await this.departmentRepository.update({ id }, departmentUpdateDto);
}
async remove(id: number): Promise<void> {
await this.isExist(id);
await this.departmentRepository.delete(id);
}
// Helper function: Check if the entity exist.
// If entity does not exsit, return the Promise.reject()
private async isExist(id: number): Promise<void> {
const cnt = await this.departmentRepository.countBy({ id });
if (cnt > 0) {
return;
} else {
return Promise.reject(
new EntityNotFoundError(Department, { where: { id } }),
);
}
}
}

View File

@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class DepartmentCreateDto {
// NOTE: Since the id is auto-inc, so no id for the creation
// id: number;
@ApiProperty({
type: String,
name: 'name',
description: 'Department name',
required: true,
})
@IsString()
@IsNotEmpty()
name: string;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { DepartmentCreateDto } from './department-create.dto';
export class DepartmentUpdateDto extends PartialType(DepartmentCreateDto) {}

View File

@@ -0,0 +1,30 @@
import { ApiProperty } from '@nestjs/swagger';
import { Staff } from 'src/mysqlcompany/staffs/entities/staff.entity';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
// @Entity('departments')
@Entity('departments')
export class Department {
@ApiProperty({
type: Number,
name: 'id',
description: 'id of department, auto-incremented',
required: true,
})
@PrimaryGeneratedColumn('increment')
id: number;
@ApiProperty({
type: String,
name: 'name',
description: 'Department name',
required: true,
})
@Column({
type: 'varchar',
})
name: string;
// @OneToMany((type) => Staff, (staff) => staff.departmentId)
// staffs: Staff[];
}

View File

@@ -0,0 +1,9 @@
import { Controller } from '@nestjs/common';
import { OrmMongoCompanyService } from './ormmongocompany.service';
@Controller()
export class OrmMongoCompanyController {
constructor(
private readonly ormMongoCompanyService: OrmMongoCompanyService,
) {}
}

View File

@@ -0,0 +1,35 @@
import { Logger, Module } from '@nestjs/common';
import { OrmMongoCompanyService } from './ormmongocompany.service';
import { OrmMongoCompanyController } from './ormmongocompany.controller';
import { DepartmentModule } from './department/department.module';
import { StaffsModule } from './staffs/staffs.module';
import { RouterModule } from '@nestjs/core';
@Module({
imports: [
// The router register is commonly placed in app.module
// However, I suggest to put registation in the 2nd level module only
// for easy management (i.e. not for root and children level)
RouterModule.register([
{
path: 'ormmongo-company',
module: OrmMongoCompanyModule,
children: [
{
path: 'departments',
module: DepartmentModule,
},
{
path: 'staffs',
module: StaffsModule,
},
],
},
]),
DepartmentModule,
StaffsModule,
],
controllers: [OrmMongoCompanyController],
providers: [Logger, OrmMongoCompanyService],
})
export class OrmMongoCompanyModule {}

View File

@@ -0,0 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class OrmMongoCompanyService {
constructor(private readonly logger: Logger) {}
}

View File

@@ -0,0 +1,10 @@
# Staffs Module
- This module demonstrates using:
- CRUD
- TypeORM: MySQL
- TypeORM Repository
- No QueryBuilder
- TypeOrmModule.forFeature & TypeOrmModule.forRoot{ autoLoadEntities: true }
- The Entities
- DB and Web-API entities are shared

View File

@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsNumber, IsOptional } from 'class-validator';
export class StaffCreateDto {
// NOTE: Since the id is auto-inc, so no id for the creation
// id: number;
@ApiProperty({
type: String,
name: 'name',
description: 'Staff name',
required: true,
})
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty({
type: Number,
name: 'staffCode',
description: 'Staff code/ID',
required: false,
})
@IsNumber()
@IsOptional()
staffCode: number;
@ApiProperty({
type: Number,
name: 'departmentId',
description: 'Department ID',
required: false,
})
@IsNumber()
@IsOptional()
departmentId: number;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { StaffCreateDto } from './staff-create.dto';
export class StaffUpdateDto extends PartialType(StaffCreateDto) {}

View File

@@ -0,0 +1,67 @@
import { ApiProperty } from '@nestjs/swagger';
import { Department } from 'src/mysqlcompany/department/entities/department.entity';
import {
Column,
Entity,
JoinColumn,
JoinTable,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
@Entity('staffs')
@Entity()
export class Staff {
@ApiProperty({
type: Number,
name: 'id',
description: 'id of staff, auto-incremented',
required: true,
})
@PrimaryGeneratedColumn('increment')
id: number;
@ApiProperty({
type: String,
name: 'name',
description: 'Staff name',
required: true,
})
@Column({
type: 'varchar',
})
name: string;
@ApiProperty({
type: Number,
name: 'staffCode',
description: 'Staff code/ID',
required: false,
})
@Column({
name: 'staff_code',
type: 'varchar',
length: 10,
nullable: true,
})
staffCode: number;
@ApiProperty({
type: Number,
name: 'departmentId',
description: 'Department ID',
required: false,
})
@Column({
// Match existing column naming convention
name: 'department_id',
type: 'tinyint',
unsigned: true,
nullable: true,
})
departmentId: number;
@ManyToOne(() => Department)
@JoinColumn({ name: 'department_id' })
department: Department;
}

View File

@@ -0,0 +1,76 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
NotFoundException,
HttpCode,
BadRequestException,
} from '@nestjs/common';
import { ApiResponse } from '@nestjs/swagger';
import { StaffsService } from './staffs.service';
import { Staff } from './entities/staff.entity';
import { StaffCreateDto } from './dto/staff-create.dto';
import { StaffUpdateDto } from './dto/staff-update.dto';
import { EntityNotFoundError } from 'typeorm';
@Controller()
export class StaffsController {
constructor(private readonly staffsService: StaffsService) {}
private handleEntityNotFoundError(id: number, e: Error) {
if (e instanceof EntityNotFoundError) {
throw new NotFoundException(`Not found: id=${id}`);
} else {
throw e;
}
}
@Get()
@ApiResponse({ type: [Staff] })
findAll(): Promise<Staff[]> {
return this.staffsService.findAll();
}
@Get(':id')
@ApiResponse({ type: Staff })
async findOne(@Param('id') id: number): Promise<Staff | null> {
// NOTE: the + operator returns the numeric representation of the object.
return this.staffsService.findOne(+id).catch((err) => {
this.handleEntityNotFoundError(id, err);
return null;
});
}
@Post()
@ApiResponse({ type: [Staff] })
create(@Body() staffCreateDto: StaffCreateDto) {
return this.staffsService.create(staffCreateDto);
}
@Patch(':id')
// Status 204 because no content will be returned
@HttpCode(204)
async update(
@Param('id') id: number,
@Body() staffUpdateDto: StaffUpdateDto,
): Promise<void> {
if (Object.keys(staffUpdateDto).length === 0) {
throw new BadRequestException('Request body is empty');
}
await this.staffsService.update(+id, staffUpdateDto).catch((err) => {
this.handleEntityNotFoundError(id, err);
});
}
@Delete(':id')
@HttpCode(204)
async remove(@Param('id') id: number): Promise<void> {
await this.staffsService.remove(+id).catch((err) => {
this.handleEntityNotFoundError(id, err);
});
}
}

View File

@@ -0,0 +1,15 @@
import { Logger, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StaffsService } from './staffs.service';
import { StaffsController } from './staffs.controller';
import { Staff } from './entities/staff.entity';
@Module({
imports: [TypeOrmModule.forFeature([Staff])],
controllers: [StaffsController],
providers: [Logger, StaffsService],
// If you want to use the repository outside of the module
// which imports TypeOrmModule.forFeature, you'll need to re-export the providers generated by it.
// exports: [TypeOrmModule],
})
export class StaffsModule {}

View File

@@ -0,0 +1,64 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EntityNotFoundError, Repository } from 'typeorm';
import { Staff } from './entities/staff.entity';
import { StaffCreateDto } from './dto/staff-create.dto';
import { StaffUpdateDto } from './dto/staff-update.dto';
@Injectable()
export class StaffsService {
constructor(
private readonly logger: Logger,
@InjectRepository(Staff)
private staffsRepository: Repository<Staff>,
) {}
create(staffCreateDto: StaffCreateDto): Promise<Staff> {
// Use Repository.create() will copy the values to destination object.
const staff = this.staffsRepository.create(staffCreateDto);
return this.staffsRepository.save(staff);
}
findAll(): Promise<Staff[]> {
return this.staffsRepository.find({
relations: {
department: true,
},
select: {
id: true,
name: true,
departmentId: false,
department: {
id: false,
name: true,
},
},
});
}
findOne(id: number): Promise<Staff> {
return this.staffsRepository.findOneOrFail({ where: { id } });
}
async update(id: number, staffUpdateDto: StaffUpdateDto): Promise<void> {
await this.isExist(id);
await this.staffsRepository.update({ id }, staffUpdateDto);
}
async remove(id: number): Promise<void> {
await this.isExist(id);
await this.staffsRepository.delete(id);
}
// Helper function: Check if the entity exist.
// If entity does not exsit, return the Promise.reject()
private async isExist(id: number): Promise<void> {
const cnt = await this.staffsRepository.countBy({ id });
if (cnt > 0) {
return;
} else {
return Promise.reject(new EntityNotFoundError(Staff, { where: { id } }));
}
}
}

7
src/users/README.md Normal file
View File

@@ -0,0 +1,7 @@
# User Module
User module use Repository class to handle the 'database' representation.
The Respository class uses a map object (in-memory) as a database. It is not persistence storage.
I am trying to apply the Repository pattern concept here and abstract the data layer out of the business logic.