temp commit, not functioning
This commit is contained in:
parent
7fc284c2c0
commit
7be9dd3e89
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"typescript.format.enable": false,
|
||||
"javascript.format.enable": false,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"thunder-client.saveToWorkspace": true
|
||||
}
|
72
README.md
72
README.md
@ -9,6 +9,8 @@ This project is based on the [Basic Docker Project with Git CI](https://git.pric
|
||||
1. docker-compose setup for local development
|
||||
1. Minimal API implementation (version, healthcheck)
|
||||
|
||||
---
|
||||
|
||||
## Local development using docker-compose and attach with VSCode
|
||||
|
||||
### Preparation
|
||||
@ -47,6 +49,76 @@ This project is based on the [Basic Docker Project with Git CI](https://git.pric
|
||||
1. To use the VSCode debugger, click the debug and launch the `Debug Nest Framework`
|
||||
(See `launch.json`) for details
|
||||
|
||||
---
|
||||
|
||||
## Databases
|
||||
|
||||
This project requires connecting to the database (MySQL, MongoDB). There are docker-compose files contain the database service.
|
||||
|
||||
### Standalone DB service
|
||||
|
||||
If you try this project with the local Node.js environment, you can use standalone database service which provided by the docker-compose.db.yml.
|
||||
|
||||
It comes with these service:
|
||||
|
||||
1. MySQL/MariaDB server
|
||||
1. Adminer - MySQL Web GUI Client
|
||||
1. MonogoDB server
|
||||
1. Monogo Express Web GUI Client
|
||||
|
||||
Use the following command to manage the docker services/stack:
|
||||
|
||||
```bash
|
||||
# To start the service as daemon
|
||||
docker-compose -f docker-compose.db.yml up -d
|
||||
|
||||
# To stop and down the service (and network resources)
|
||||
docker-compose -f docker-compose.db.yml down
|
||||
```
|
||||
|
||||
#### Adminer
|
||||
|
||||
Adminer is the Web GUI for MySQL/Mariadb. Once the docker-compose.db.yml is started, you may access it by:
|
||||
|
||||
`http://localhost:33306`
|
||||
|
||||
#### Mongo Express
|
||||
|
||||
Mongo Express is the Web GUI for MongoDB. Once the docker-compose.db.yml is started, you may access it by:
|
||||
|
||||
`http://localhost:32017`
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Port binding issue
|
||||
|
||||
In WSL2 environment, you may get the following error sometime:
|
||||
|
||||
> Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:3306 -> 0.0.0.0:0: listen tcp 0.0.0.0:3306: bind: An attempt was made to access a socket in a way forbidden by its access permissions.
|
||||
|
||||
First of all, you can check if there is stopped container that occupied the port, you can use the prune command to clean up the container:
|
||||
|
||||
```bash
|
||||
docker container prune
|
||||
```
|
||||
|
||||
__NOTE:__
|
||||
>In this project, we change the exposed ports to high-port, not low-port like 3306.
|
||||
|
||||
Sometime, Windows firewall will block the port/port-range. It can be checked and add a rule via Powershell
|
||||
|
||||
```powershell
|
||||
# Check if 3306 is excluded:
|
||||
netsh int ipv4 show excludedportrange protocol=tcp
|
||||
|
||||
# Add excluded range/port: (Need to run-as admin)
|
||||
netsh int ipv4 add excludedportrange protocol=tcp startport=3306 numberofports=1
|
||||
#
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Swagger
|
||||
|
||||
The Nest Swagger framework is added into this project. For details, please see [Nestjs OpenAPI](https://docs.nestjs.com/openapi/introduction) documentation.
|
||||
|
@ -17,7 +17,7 @@ services:
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: example
|
||||
ports:
|
||||
- "3306:3306"
|
||||
- "23306:3306"
|
||||
volumes:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
# Without this, will get error when start up in WSL2
|
||||
|
@ -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: [
|
||||
|
7
src/mysqlcompany/README.md
Normal file
7
src/mysqlcompany/README.md
Normal 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
|
@ -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) {}
|
||||
|
@ -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';
|
@ -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,
|
7
src/mysqlcompany/mysqlcompany.controller.ts
Normal file
7
src/mysqlcompany/mysqlcompany.controller.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Controller } from '@nestjs/common';
|
||||
import { MySqlCompanyService } from './mysqlcompany.service';
|
||||
|
||||
@Controller()
|
||||
export class MySqlCompanyController {
|
||||
constructor(private readonly mySqlCompanyService: MySqlCompanyService) {}
|
||||
}
|
35
src/mysqlcompany/mysqlcompany.module.ts
Normal file
35
src/mysqlcompany/mysqlcompany.module.ts
Normal 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 {}
|
6
src/mysqlcompany/mysqlcompany.service.ts
Normal file
6
src/mysqlcompany/mysqlcompany.service.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class MySqlCompanyService {
|
||||
constructor(private readonly logger: Logger) {}
|
||||
}
|
@ -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({
|
@ -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) {}
|
||||
|
7
src/ormmongocompany/README.md
Normal file
7
src/ormmongocompany/README.md
Normal 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
|
10
src/ormmongocompany/department/README.md
Normal file
10
src/ormmongocompany/department/README.md
Normal 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
|
78
src/ormmongocompany/department/department.controller.ts
Normal file
78
src/ormmongocompany/department/department.controller.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
15
src/ormmongocompany/department/department.module.ts
Normal file
15
src/ormmongocompany/department/department.module.ts
Normal 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 {}
|
55
src/ormmongocompany/department/department.service.ts
Normal file
55
src/ormmongocompany/department/department.service.ts
Normal 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 } }),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
16
src/ormmongocompany/department/dto/department-create.dto.ts
Normal file
16
src/ormmongocompany/department/dto/department-create.dto.ts
Normal 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;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { DepartmentCreateDto } from './department-create.dto';
|
||||
|
||||
export class DepartmentUpdateDto extends PartialType(DepartmentCreateDto) {}
|
30
src/ormmongocompany/department/entities/department.entity.ts
Normal file
30
src/ormmongocompany/department/entities/department.entity.ts
Normal 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[];
|
||||
}
|
9
src/ormmongocompany/ormmongocompany.controller.ts
Normal file
9
src/ormmongocompany/ormmongocompany.controller.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Controller } from '@nestjs/common';
|
||||
import { OrmMongoCompanyService } from './ormmongocompany.service';
|
||||
|
||||
@Controller()
|
||||
export class OrmMongoCompanyController {
|
||||
constructor(
|
||||
private readonly ormMongoCompanyService: OrmMongoCompanyService,
|
||||
) {}
|
||||
}
|
35
src/ormmongocompany/ormmongocompany.module.ts
Normal file
35
src/ormmongocompany/ormmongocompany.module.ts
Normal 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 {}
|
6
src/ormmongocompany/ormmongocompany.service.ts
Normal file
6
src/ormmongocompany/ormmongocompany.service.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class OrmMongoCompanyService {
|
||||
constructor(private readonly logger: Logger) {}
|
||||
}
|
10
src/ormmongocompany/staffs/README.md
Normal file
10
src/ormmongocompany/staffs/README.md
Normal 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
|
36
src/ormmongocompany/staffs/dto/staff-create.dto.ts
Normal file
36
src/ormmongocompany/staffs/dto/staff-create.dto.ts
Normal 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;
|
||||
}
|
4
src/ormmongocompany/staffs/dto/staff-update.dto.ts
Normal file
4
src/ormmongocompany/staffs/dto/staff-update.dto.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { StaffCreateDto } from './staff-create.dto';
|
||||
|
||||
export class StaffUpdateDto extends PartialType(StaffCreateDto) {}
|
67
src/ormmongocompany/staffs/entities/staff.entity.ts
Normal file
67
src/ormmongocompany/staffs/entities/staff.entity.ts
Normal 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;
|
||||
}
|
76
src/ormmongocompany/staffs/staffs.controller.ts
Normal file
76
src/ormmongocompany/staffs/staffs.controller.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
15
src/ormmongocompany/staffs/staffs.module.ts
Normal file
15
src/ormmongocompany/staffs/staffs.module.ts
Normal 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 {}
|
64
src/ormmongocompany/staffs/staffs.service.ts
Normal file
64
src/ormmongocompany/staffs/staffs.service.ts
Normal 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
7
src/users/README.md
Normal 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.
|
1
thunder-tests/thunderActivity.json
Normal file
1
thunder-tests/thunderActivity.json
Normal file
@ -0,0 +1 @@
|
||||
[]
|
9
thunder-tests/thunderCollection.json
Normal file
9
thunder-tests/thunderCollection.json
Normal file
@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"_id": "8a14d631-7522-4fcd-a3b8-9a68166e5496",
|
||||
"colName": "Local NestJS App",
|
||||
"created": "2023-04-20T12:42:42.547Z",
|
||||
"sortNum": 10000,
|
||||
"folders": []
|
||||
}
|
||||
]
|
13
thunder-tests/thunderEnvironment.json
Normal file
13
thunder-tests/thunderEnvironment.json
Normal file
@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"_id": "af943170-fabe-4f13-9725-3bd52e585aa5",
|
||||
"name": "(Local Env)",
|
||||
"default": false,
|
||||
"global": true,
|
||||
"local": true,
|
||||
"sortNum": -1,
|
||||
"created": "2023-04-20T12:43:21.880Z",
|
||||
"modified": "2023-04-20T12:43:21.880Z",
|
||||
"data": []
|
||||
}
|
||||
]
|
63
thunder-tests/thunderclient.json
Normal file
63
thunder-tests/thunderclient.json
Normal file
@ -0,0 +1,63 @@
|
||||
[
|
||||
{
|
||||
"_id": "901d7d10-2ba0-481d-9230-182df284741e",
|
||||
"colId": "8a14d631-7522-4fcd-a3b8-9a68166e5496",
|
||||
"containerId": "",
|
||||
"name": "Get Users",
|
||||
"url": "http://localhost:3000/users",
|
||||
"method": "GET",
|
||||
"sortNum": 10000,
|
||||
"created": "2023-04-20T12:42:42.550Z",
|
||||
"modified": "2023-04-20T13:17:30.029Z",
|
||||
"headers": [],
|
||||
"params": [],
|
||||
"tests": []
|
||||
},
|
||||
{
|
||||
"_id": "db11f3a6-418b-43ae-9a1d-f2f667b5d0e6",
|
||||
"colId": "8a14d631-7522-4fcd-a3b8-9a68166e5496",
|
||||
"containerId": "",
|
||||
"name": "Add User",
|
||||
"url": "http://localhost:3000/users/",
|
||||
"method": "POST",
|
||||
"sortNum": 12500,
|
||||
"created": "2023-04-20T12:47:21.164Z",
|
||||
"modified": "2023-04-20T13:18:12.522Z",
|
||||
"headers": [],
|
||||
"params": [],
|
||||
"body": {
|
||||
"type": "json",
|
||||
"raw": "{\n \"name\": \"{{#name}} - {{#number, 100, 999}}\"\n}",
|
||||
"form": []
|
||||
},
|
||||
"tests": []
|
||||
},
|
||||
{
|
||||
"_id": "858c3b4a-186d-4338-9dab-4eb00a450808",
|
||||
"colId": "8a14d631-7522-4fcd-a3b8-9a68166e5496",
|
||||
"containerId": "",
|
||||
"name": "Get MySQL Comany Staffs",
|
||||
"url": "http://localhost:3000/mysql-company/staffs/",
|
||||
"method": "GET",
|
||||
"sortNum": 15000,
|
||||
"created": "2023-04-20T13:01:50.431Z",
|
||||
"modified": "2023-04-20T13:17:36.471Z",
|
||||
"headers": [],
|
||||
"params": [],
|
||||
"tests": []
|
||||
},
|
||||
{
|
||||
"_id": "017f39a8-137d-4ab9-bb7e-f0dbef38c899",
|
||||
"colId": "8a14d631-7522-4fcd-a3b8-9a68166e5496",
|
||||
"containerId": "",
|
||||
"name": "Get MySQL Comany Departments",
|
||||
"url": "http://localhost:3000/ormmongo-company/staffs",
|
||||
"method": "GET",
|
||||
"sortNum": 17500,
|
||||
"created": "2023-04-20T13:17:46.507Z",
|
||||
"modified": "2023-04-20T15:00:49.647Z",
|
||||
"headers": [],
|
||||
"params": [],
|
||||
"tests": []
|
||||
}
|
||||
]
|
@ -1,4 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "data"]
|
||||
}
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"dist",
|
||||
"**/*spec.ts",
|
||||
"data"
|
||||
]
|
||||
}
|
@ -18,4 +18,4 @@
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user