mm-share-lib 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +81 -78
- package/src/{common/constant → constant}/entity-state.constant.ts +4 -4
- package/src/{common/constant → constant}/index.ts +1 -1
- package/src/{common/generic → generic}/entity/entity.generic.ts +34 -34
- package/src/{common/generic → generic}/entity/index.ts +1 -1
- package/src/{common/generic → generic}/index.ts +2 -2
- package/src/{common/generic → generic}/service/index.ts +1 -1
- package/src/{common/generic → generic}/service/service.generic.ts +112 -112
- package/src/index.ts +2 -1
- package/src/util/date.util.spec.ts +49 -0
- package/src/util/date.util.ts +10 -1
- package/src/util/generator.util.spec.ts +79 -35
- package/src/util/generator.util.ts +27 -18
- package/src/common/generic/entity/entity.generic.spec.ts +0 -44
- package/src/common/index.ts +0 -2
- /package/src/{common/generic/service/service.generic.spec.ts → exception/index.ts} +0 -0
package/package.json
CHANGED
@@ -1,78 +1,81 @@
|
|
1
|
-
{
|
2
|
-
"name": "mm-share-lib",
|
3
|
-
"version": "0.0.
|
4
|
-
"description": "Share the generic service, entity, dto.",
|
5
|
-
"author": "Mesa SOT",
|
6
|
-
"license": "MIT",
|
7
|
-
"main": "dist/index.js",
|
8
|
-
"types": "dist/index.d.ts",
|
9
|
-
"scripts": {
|
10
|
-
"build": "nest build",
|
11
|
-
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
12
|
-
"start": "nest start",
|
13
|
-
"start:dev": "nest start --watch",
|
14
|
-
"start:debug": "nest start --debug --watch",
|
15
|
-
"start:prod": "node dist/main",
|
16
|
-
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
17
|
-
"test": "jest",
|
18
|
-
"test:watch": "jest --watch",
|
19
|
-
"test:cov": "jest --coverage",
|
20
|
-
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
21
|
-
"test:e2e": "jest --config ./test/jest-e2e.json",
|
22
|
-
"prepare": "husky install"
|
23
|
-
},
|
24
|
-
"dependencies": {
|
25
|
-
"@babel/runtime": "^7.22.5",
|
26
|
-
"@nestjs/common": "^9.4.2",
|
27
|
-
"@nestjs/core": "^9.4.2",
|
28
|
-
"@nestjs/platform-express": "^9.4.2",
|
29
|
-
"@nestjs/typeorm": "^9.0.1",
|
30
|
-
"class-transformer": "^0.5.1",
|
31
|
-
"class-validator": "^0.14.0",
|
32
|
-
"
|
33
|
-
"
|
34
|
-
"
|
35
|
-
"
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
"@nestjs/
|
41
|
-
"@
|
42
|
-
"@
|
43
|
-
"@types/
|
44
|
-
"@types/
|
45
|
-
"@
|
46
|
-
"@
|
47
|
-
"
|
48
|
-
"eslint-
|
49
|
-
"eslint
|
50
|
-
"
|
51
|
-
"
|
52
|
-
"prettier": "^2.
|
53
|
-
"
|
54
|
-
"
|
55
|
-
"
|
56
|
-
"
|
57
|
-
"
|
58
|
-
"
|
59
|
-
"
|
60
|
-
|
61
|
-
|
62
|
-
"
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
"
|
73
|
-
"
|
74
|
-
|
75
|
-
"
|
76
|
-
|
77
|
-
|
78
|
-
|
1
|
+
{
|
2
|
+
"name": "mm-share-lib",
|
3
|
+
"version": "0.0.3",
|
4
|
+
"description": "Share the generic service, entity, dto.",
|
5
|
+
"author": "Mesa SOT",
|
6
|
+
"license": "MIT",
|
7
|
+
"main": "dist/index.js",
|
8
|
+
"types": "dist/index.d.ts",
|
9
|
+
"scripts": {
|
10
|
+
"build": "nest build",
|
11
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
12
|
+
"start": "nest start",
|
13
|
+
"start:dev": "nest start --watch",
|
14
|
+
"start:debug": "nest start --debug --watch",
|
15
|
+
"start:prod": "node dist/main",
|
16
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
17
|
+
"test": "jest",
|
18
|
+
"test:watch": "jest --watch",
|
19
|
+
"test:cov": "jest --coverage",
|
20
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
21
|
+
"test:e2e": "jest --config ./test/jest-e2e.json",
|
22
|
+
"prepare": "husky install"
|
23
|
+
},
|
24
|
+
"dependencies": {
|
25
|
+
"@babel/runtime": "^7.22.5",
|
26
|
+
"@nestjs/common": "^9.4.2",
|
27
|
+
"@nestjs/core": "^9.4.2",
|
28
|
+
"@nestjs/platform-express": "^9.4.2",
|
29
|
+
"@nestjs/typeorm": "^9.0.1",
|
30
|
+
"class-transformer": "^0.5.1",
|
31
|
+
"class-validator": "^0.14.0",
|
32
|
+
"dayjs": "^1.11.8",
|
33
|
+
"reflect-metadata": "^0.1.13",
|
34
|
+
"rxjs": "^7.8.1",
|
35
|
+
"typeorm": "^0.3.16",
|
36
|
+
"bcrypt": "^5.1.0",
|
37
|
+
"typesense": "^1.5.4"
|
38
|
+
},
|
39
|
+
"devDependencies": {
|
40
|
+
"@nestjs/cli": "^9.5.0",
|
41
|
+
"@nestjs/schematics": "^9.2.0",
|
42
|
+
"@nestjs/testing": "^9.4.2",
|
43
|
+
"@types/bcrypt": "^5.0.0",
|
44
|
+
"@types/express": "^4.17.17",
|
45
|
+
"@types/jest": "29.5.2",
|
46
|
+
"@types/node": "20.2.5",
|
47
|
+
"@types/supertest": "^2.0.12",
|
48
|
+
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
49
|
+
"@typescript-eslint/parser": "^5.59.9",
|
50
|
+
"eslint": "^8.42.0",
|
51
|
+
"eslint-config-prettier": "^8.8.0",
|
52
|
+
"eslint-plugin-prettier": "^4.2.1",
|
53
|
+
"husky": "^8.0.3",
|
54
|
+
"jest": "29.5.0",
|
55
|
+
"prettier": "^2.8.8",
|
56
|
+
"source-map-support": "^0.5.21",
|
57
|
+
"supertest": "^6.3.3",
|
58
|
+
"ts-jest": "29.1.0",
|
59
|
+
"ts-loader": "^9.4.3",
|
60
|
+
"ts-node": "^10.9.1",
|
61
|
+
"tsconfig-paths": "4.2.0",
|
62
|
+
"typescript": "^5.1.3"
|
63
|
+
},
|
64
|
+
"jest": {
|
65
|
+
"moduleFileExtensions": [
|
66
|
+
"js",
|
67
|
+
"json",
|
68
|
+
"ts"
|
69
|
+
],
|
70
|
+
"rootDir": "src",
|
71
|
+
"testRegex": ".*\\.spec\\.ts$",
|
72
|
+
"transform": {
|
73
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
74
|
+
},
|
75
|
+
"collectCoverageFrom": [
|
76
|
+
"**/*.(t|j)s"
|
77
|
+
],
|
78
|
+
"coverageDirectory": "../coverage",
|
79
|
+
"testEnvironment": "node"
|
80
|
+
}
|
81
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
export enum EntityStateConstant {
|
2
|
-
Active = 1,
|
3
|
-
Archived = 0,
|
4
|
-
}
|
1
|
+
export enum EntityStateConstant {
|
2
|
+
Active = 1,
|
3
|
+
Archived = 0,
|
4
|
+
}
|
@@ -1 +1 @@
|
|
1
|
-
export * from './entity-state.constant';
|
1
|
+
export * from './entity-state.constant';
|
@@ -1,34 +1,34 @@
|
|
1
|
-
import {
|
2
|
-
Column,
|
3
|
-
VersionColumn,
|
4
|
-
CreateDateColumn,
|
5
|
-
UpdateDateColumn,
|
6
|
-
PrimaryGeneratedColumn,
|
7
|
-
} from 'typeorm';
|
8
|
-
|
9
|
-
export class EntityGeneric {
|
10
|
-
constructor(id?: number) {
|
11
|
-
this.id = id;
|
12
|
-
}
|
13
|
-
|
14
|
-
@PrimaryGeneratedColumn()
|
15
|
-
id: number;
|
16
|
-
|
17
|
-
@CreateDateColumn()
|
18
|
-
createdAt: Date;
|
19
|
-
|
20
|
-
@UpdateDateColumn()
|
21
|
-
updatedAt: Date;
|
22
|
-
|
23
|
-
@Column()
|
24
|
-
createdBy: number;
|
25
|
-
|
26
|
-
@Column({ select: false })
|
27
|
-
updatedBy: number;
|
28
|
-
|
29
|
-
@Column()
|
30
|
-
state: number;
|
31
|
-
|
32
|
-
@VersionColumn({ select: false })
|
33
|
-
version: number;
|
34
|
-
}
|
1
|
+
import {
|
2
|
+
Column,
|
3
|
+
VersionColumn,
|
4
|
+
CreateDateColumn,
|
5
|
+
UpdateDateColumn,
|
6
|
+
PrimaryGeneratedColumn,
|
7
|
+
} from 'typeorm';
|
8
|
+
|
9
|
+
export class EntityGeneric {
|
10
|
+
constructor(id?: number) {
|
11
|
+
this.id = id;
|
12
|
+
}
|
13
|
+
|
14
|
+
@PrimaryGeneratedColumn()
|
15
|
+
id: number;
|
16
|
+
|
17
|
+
@CreateDateColumn()
|
18
|
+
createdAt: Date;
|
19
|
+
|
20
|
+
@UpdateDateColumn()
|
21
|
+
updatedAt: Date;
|
22
|
+
|
23
|
+
@Column()
|
24
|
+
createdBy: number;
|
25
|
+
|
26
|
+
@Column({ select: false })
|
27
|
+
updatedBy: number;
|
28
|
+
|
29
|
+
@Column()
|
30
|
+
state: number;
|
31
|
+
|
32
|
+
@VersionColumn({ select: false })
|
33
|
+
version: number;
|
34
|
+
}
|
@@ -1 +1 @@
|
|
1
|
-
export * from './entity.generic';
|
1
|
+
export * from './entity.generic';
|
@@ -1,2 +1,2 @@
|
|
1
|
-
export * from './entity/entity.generic';
|
2
|
-
export * from './service/service.generic';
|
1
|
+
export * from './entity/entity.generic';
|
2
|
+
export * from './service/service.generic';
|
@@ -1 +1 @@
|
|
1
|
-
export * from './service.generic';
|
1
|
+
export * from './service.generic';
|
@@ -1,112 +1,112 @@
|
|
1
|
-
import {
|
2
|
-
Repository,
|
3
|
-
Connection,
|
4
|
-
EntityManager,
|
5
|
-
SelectQueryBuilder,
|
6
|
-
} from 'typeorm';
|
7
|
-
import { EntityGeneric } from '../entity';
|
8
|
-
import { PaginationDto, BaseFilterDto } from '
|
9
|
-
import { EntityStateConstant } from '../../constant';
|
10
|
-
import { PaginationResponse } from '
|
11
|
-
|
12
|
-
export abstract class ServiceGeneric<
|
13
|
-
Entity extends EntityGeneric,
|
14
|
-
CustomRepository extends Repository<Entity>,
|
15
|
-
> {
|
16
|
-
protected readonly entityName: string;
|
17
|
-
protected readonly loggable: boolean = false;
|
18
|
-
protected repository: CustomRepository;
|
19
|
-
protected connection: Connection;
|
20
|
-
constructor(
|
21
|
-
protected readonly connectionOrManager: Connection | EntityManager,
|
22
|
-
repositoryType: { new (connection: Connection): CustomRepository },
|
23
|
-
) {
|
24
|
-
if (connectionOrManager instanceof EntityManager) {
|
25
|
-
this.connection = connectionOrManager.connection;
|
26
|
-
} else {
|
27
|
-
this.connection = connectionOrManager;
|
28
|
-
}
|
29
|
-
this.repository = this.connection.getCustomRepository(repositoryType);
|
30
|
-
}
|
31
|
-
|
32
|
-
async create(entity: Entity): Promise<Entity> {
|
33
|
-
return this.repository.save(entity);
|
34
|
-
}
|
35
|
-
|
36
|
-
async update(entity: Entity): Promise<Entity> {
|
37
|
-
return this.repository.save(entity);
|
38
|
-
}
|
39
|
-
|
40
|
-
async getListWithPagination(
|
41
|
-
paginationDto: PaginationDto,
|
42
|
-
callback?: (query: SelectQueryBuilder<Entity>) => void,
|
43
|
-
): Promise<PaginationResponse<Entity>> {
|
44
|
-
const { limit = 25, offset = 0 } = paginationDto;
|
45
|
-
const query = this.repository.createQueryBuilder(this.entityName);
|
46
|
-
query.limit(limit);
|
47
|
-
query.offset(offset);
|
48
|
-
query.where(`${this.entityName}.state != :state`, {
|
49
|
-
state: EntityStateConstant.Archived,
|
50
|
-
});
|
51
|
-
const defaultSelectable = ['createdAt', 'updatedAt'];
|
52
|
-
query.orderBy(`${this.entityName}.updatedAt`, 'DESC');
|
53
|
-
const selection = defaultSelectable.map(
|
54
|
-
(column: string) => `${this.entityName}.${column}`,
|
55
|
-
);
|
56
|
-
query.addSelect(selection);
|
57
|
-
if (callback != null) {
|
58
|
-
callback(query);
|
59
|
-
}
|
60
|
-
const entities = await query.getMany();
|
61
|
-
const total = await query.getCount();
|
62
|
-
const response = new PaginationResponse(entities, total, limit, offset);
|
63
|
-
return response;
|
64
|
-
}
|
65
|
-
|
66
|
-
async getAutocompleteWithPagination(
|
67
|
-
paginationDto: PaginationDto,
|
68
|
-
filter: BaseFilterDto,
|
69
|
-
callback?: (query: SelectQueryBuilder<Entity>) => void,
|
70
|
-
): Promise<PaginationResponse<Entity>> {
|
71
|
-
const { limit = 25, offset = 0 } = paginationDto;
|
72
|
-
const query = this.repository.createQueryBuilder(this.entityName);
|
73
|
-
query.limit(limit);
|
74
|
-
query.offset(offset);
|
75
|
-
query.where(`${this.entityName}.state != :state`, {
|
76
|
-
state: EntityStateConstant.Archived,
|
77
|
-
});
|
78
|
-
const defaultSelectable = ['createdAt', 'updatedAt'];
|
79
|
-
query.orderBy(`${this.entityName}.updatedAt`, 'DESC');
|
80
|
-
const selection = defaultSelectable.map(
|
81
|
-
(column: string) => `${this.entityName}.${column}`,
|
82
|
-
);
|
83
|
-
query.addSelect(selection);
|
84
|
-
if (callback != null) {
|
85
|
-
callback(query);
|
86
|
-
}
|
87
|
-
const { excludeIds = [], includeIds = [] } = { ...filter };
|
88
|
-
// Exclude some ids from the list.
|
89
|
-
if (excludeIds?.length > 0) {
|
90
|
-
query.andWhere(`${this.entityName}.id NOT IN (:ids)`, {
|
91
|
-
ids: excludeIds,
|
92
|
-
});
|
93
|
-
}
|
94
|
-
let entities = await query.getMany();
|
95
|
-
const total = await query.getCount();
|
96
|
-
// Include some ids to the list.
|
97
|
-
const allIds: number[] = [];
|
98
|
-
for (const id of includeIds) {
|
99
|
-
const entity = entities.filter((entity: Entity) => entity.id === id);
|
100
|
-
if (entity.length == 0) {
|
101
|
-
allIds.push(id);
|
102
|
-
}
|
103
|
-
}
|
104
|
-
if (allIds.length > 0) {
|
105
|
-
query.where(`${this.entityName}.id IN (:ids)`, { ids: allIds });
|
106
|
-
const data = await query.getMany();
|
107
|
-
entities = data.concat(entities);
|
108
|
-
}
|
109
|
-
const response = new PaginationResponse(entities, total, limit, offset);
|
110
|
-
return response;
|
111
|
-
}
|
112
|
-
}
|
1
|
+
import {
|
2
|
+
Repository,
|
3
|
+
Connection,
|
4
|
+
EntityManager,
|
5
|
+
SelectQueryBuilder,
|
6
|
+
} from 'typeorm';
|
7
|
+
import { EntityGeneric } from '../entity';
|
8
|
+
import { PaginationDto, BaseFilterDto } from '../../dto';
|
9
|
+
import { EntityStateConstant } from '../../constant';
|
10
|
+
import { PaginationResponse } from '../../response';
|
11
|
+
|
12
|
+
export abstract class ServiceGeneric<
|
13
|
+
Entity extends EntityGeneric,
|
14
|
+
CustomRepository extends Repository<Entity>,
|
15
|
+
> {
|
16
|
+
protected readonly entityName: string;
|
17
|
+
protected readonly loggable: boolean = false;
|
18
|
+
protected repository: CustomRepository;
|
19
|
+
protected connection: Connection;
|
20
|
+
constructor(
|
21
|
+
protected readonly connectionOrManager: Connection | EntityManager,
|
22
|
+
repositoryType: { new (connection: Connection): CustomRepository },
|
23
|
+
) {
|
24
|
+
if (connectionOrManager instanceof EntityManager) {
|
25
|
+
this.connection = connectionOrManager.connection;
|
26
|
+
} else {
|
27
|
+
this.connection = connectionOrManager;
|
28
|
+
}
|
29
|
+
this.repository = this.connection.getCustomRepository(repositoryType);
|
30
|
+
}
|
31
|
+
|
32
|
+
async create(entity: Entity): Promise<Entity> {
|
33
|
+
return this.repository.save(entity);
|
34
|
+
}
|
35
|
+
|
36
|
+
async update(entity: Entity): Promise<Entity> {
|
37
|
+
return this.repository.save(entity);
|
38
|
+
}
|
39
|
+
|
40
|
+
async getListWithPagination(
|
41
|
+
paginationDto: PaginationDto,
|
42
|
+
callback?: (query: SelectQueryBuilder<Entity>) => void,
|
43
|
+
): Promise<PaginationResponse<Entity>> {
|
44
|
+
const { limit = 25, offset = 0 } = paginationDto;
|
45
|
+
const query = this.repository.createQueryBuilder(this.entityName);
|
46
|
+
query.limit(limit);
|
47
|
+
query.offset(offset);
|
48
|
+
query.where(`${this.entityName}.state != :state`, {
|
49
|
+
state: EntityStateConstant.Archived,
|
50
|
+
});
|
51
|
+
const defaultSelectable = ['createdAt', 'updatedAt'];
|
52
|
+
query.orderBy(`${this.entityName}.updatedAt`, 'DESC');
|
53
|
+
const selection = defaultSelectable.map(
|
54
|
+
(column: string) => `${this.entityName}.${column}`,
|
55
|
+
);
|
56
|
+
query.addSelect(selection);
|
57
|
+
if (callback != null) {
|
58
|
+
callback(query);
|
59
|
+
}
|
60
|
+
const entities = await query.getMany();
|
61
|
+
const total = await query.getCount();
|
62
|
+
const response = new PaginationResponse(entities, total, limit, offset);
|
63
|
+
return response;
|
64
|
+
}
|
65
|
+
|
66
|
+
async getAutocompleteWithPagination(
|
67
|
+
paginationDto: PaginationDto,
|
68
|
+
filter: BaseFilterDto,
|
69
|
+
callback?: (query: SelectQueryBuilder<Entity>) => void,
|
70
|
+
): Promise<PaginationResponse<Entity>> {
|
71
|
+
const { limit = 25, offset = 0 } = paginationDto;
|
72
|
+
const query = this.repository.createQueryBuilder(this.entityName);
|
73
|
+
query.limit(limit);
|
74
|
+
query.offset(offset);
|
75
|
+
query.where(`${this.entityName}.state != :state`, {
|
76
|
+
state: EntityStateConstant.Archived,
|
77
|
+
});
|
78
|
+
const defaultSelectable = ['createdAt', 'updatedAt'];
|
79
|
+
query.orderBy(`${this.entityName}.updatedAt`, 'DESC');
|
80
|
+
const selection = defaultSelectable.map(
|
81
|
+
(column: string) => `${this.entityName}.${column}`,
|
82
|
+
);
|
83
|
+
query.addSelect(selection);
|
84
|
+
if (callback != null) {
|
85
|
+
callback(query);
|
86
|
+
}
|
87
|
+
const { excludeIds = [], includeIds = [] } = { ...filter };
|
88
|
+
// Exclude some ids from the list.
|
89
|
+
if (excludeIds?.length > 0) {
|
90
|
+
query.andWhere(`${this.entityName}.id NOT IN (:ids)`, {
|
91
|
+
ids: excludeIds,
|
92
|
+
});
|
93
|
+
}
|
94
|
+
let entities = await query.getMany();
|
95
|
+
const total = await query.getCount();
|
96
|
+
// Include some ids to the list.
|
97
|
+
const allIds: number[] = [];
|
98
|
+
for (const id of includeIds) {
|
99
|
+
const entity = entities.filter((entity: Entity) => entity.id === id);
|
100
|
+
if (entity.length == 0) {
|
101
|
+
allIds.push(id);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
if (allIds.length > 0) {
|
105
|
+
query.where(`${this.entityName}.id IN (:ids)`, { ids: allIds });
|
106
|
+
const data = await query.getMany();
|
107
|
+
entities = data.concat(entities);
|
108
|
+
}
|
109
|
+
const response = new PaginationResponse(entities, total, limit, offset);
|
110
|
+
return response;
|
111
|
+
}
|
112
|
+
}
|
package/src/index.ts
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
import { addSecond, isExpired } from './date.util';
|
3
|
+
|
4
|
+
describe('Utility Functions', () => {
|
5
|
+
test('addSecond should return a Date object with the specified duration added', () => {
|
6
|
+
// Arrange
|
7
|
+
const duration = 10;
|
8
|
+
|
9
|
+
// Act
|
10
|
+
const result = addSecond(duration);
|
11
|
+
|
12
|
+
// Assert
|
13
|
+
const expectedDate = dayjs().add(duration, 'seconds').toDate();
|
14
|
+
expect(result).toEqual(expectedDate);
|
15
|
+
});
|
16
|
+
|
17
|
+
test('isExpired should return true for a past date', () => {
|
18
|
+
// Arrange
|
19
|
+
const pastDate = dayjs().subtract(1, 'day').toDate();
|
20
|
+
|
21
|
+
// Act
|
22
|
+
const result = isExpired(pastDate);
|
23
|
+
|
24
|
+
// Assert
|
25
|
+
expect(result).toBe(true);
|
26
|
+
});
|
27
|
+
|
28
|
+
test('isExpired should return false for a future date', () => {
|
29
|
+
// Arrange
|
30
|
+
const futureDate = dayjs().add(1, 'day').toDate();
|
31
|
+
|
32
|
+
// Act
|
33
|
+
const result = isExpired(futureDate);
|
34
|
+
|
35
|
+
// Assert
|
36
|
+
expect(result).toBe(false);
|
37
|
+
});
|
38
|
+
|
39
|
+
test('isExpired should return false for the current date', () => {
|
40
|
+
// Arrange
|
41
|
+
const currentDate = dayjs().toDate();
|
42
|
+
|
43
|
+
// Act
|
44
|
+
const result = isExpired(currentDate);
|
45
|
+
|
46
|
+
// Assert
|
47
|
+
expect(result).toBe(false);
|
48
|
+
});
|
49
|
+
});
|
package/src/util/date.util.ts
CHANGED
@@ -1 +1,10 @@
|
|
1
|
-
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
|
3
|
+
export const addSecond = (duration: number): Date => {
|
4
|
+
return dayjs().add(duration, 'seconds').toDate();
|
5
|
+
};
|
6
|
+
|
7
|
+
export const isExpired = (date: Date): boolean => {
|
8
|
+
const currentDate = dayjs();
|
9
|
+
return dayjs(date).isBefore(currentDate);
|
10
|
+
};
|
@@ -1,35 +1,79 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
const
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
1
|
+
import * as bcrypt from 'bcrypt';
|
2
|
+
import {
|
3
|
+
generateKey,
|
4
|
+
generateDigitCode,
|
5
|
+
generatePassword,
|
6
|
+
} from './generator.util';
|
7
|
+
|
8
|
+
describe('Utils', () => {
|
9
|
+
describe('generateKey', () => {
|
10
|
+
it('should generate a key of the specified length', () => {
|
11
|
+
const keyLength = 10;
|
12
|
+
const key = generateKey(keyLength);
|
13
|
+
expect(key).toHaveLength(keyLength);
|
14
|
+
});
|
15
|
+
|
16
|
+
it('should generate a key with default length if not specified', () => {
|
17
|
+
const defaultKeyLength = 36;
|
18
|
+
const key = generateKey();
|
19
|
+
expect(key).toHaveLength(defaultKeyLength);
|
20
|
+
});
|
21
|
+
});
|
22
|
+
|
23
|
+
describe('generateDigitCode', () => {
|
24
|
+
it('should generate a digit code of the specified length', () => {
|
25
|
+
const codeLength = 6;
|
26
|
+
const code = generateDigitCode(codeLength);
|
27
|
+
expect(code).toHaveLength(codeLength);
|
28
|
+
expect(Number(code)).toBeGreaterThanOrEqual(100000);
|
29
|
+
expect(Number(code)).toBeLessThanOrEqual(999999);
|
30
|
+
});
|
31
|
+
|
32
|
+
it('should generate a digit code with default length if not specified', () => {
|
33
|
+
const defaultCodeLength = 4;
|
34
|
+
const code = generateDigitCode();
|
35
|
+
expect(code).toHaveLength(defaultCodeLength);
|
36
|
+
expect(Number(code)).toBeGreaterThanOrEqual(1000);
|
37
|
+
expect(Number(code)).toBeLessThanOrEqual(9999);
|
38
|
+
});
|
39
|
+
});
|
40
|
+
|
41
|
+
describe('Utility Functions', () => {
|
42
|
+
test('generatePassword should return a hashed password with the specified salt rounds', async () => {
|
43
|
+
// Arrange
|
44
|
+
const password = 'password123';
|
45
|
+
const saltRounds = 12;
|
46
|
+
|
47
|
+
// Act
|
48
|
+
const hashedPassword = await generatePassword(password, saltRounds);
|
49
|
+
|
50
|
+
// Assert
|
51
|
+
expect(typeof hashedPassword).toBe('string');
|
52
|
+
expect(hashedPassword).not.toBe(password);
|
53
|
+
expect(await bcrypt.compare(password, hashedPassword)).toBeTruthy();
|
54
|
+
});
|
55
|
+
|
56
|
+
test('generatePassword should use the default salt rounds when no value is provided', async () => {
|
57
|
+
// Arrange
|
58
|
+
const password = 'password123';
|
59
|
+
const defaultSaltRounds = 10;
|
60
|
+
|
61
|
+
// Act
|
62
|
+
const hashedPassword = await generatePassword(password);
|
63
|
+
|
64
|
+
// Assert
|
65
|
+
expect(typeof hashedPassword).toBe('string');
|
66
|
+
expect(hashedPassword).not.toBe(password);
|
67
|
+
expect(await bcrypt.compare(password, hashedPassword)).toBeTruthy();
|
68
|
+
expect(bcrypt.getRounds(hashedPassword)).toBe(defaultSaltRounds);
|
69
|
+
});
|
70
|
+
|
71
|
+
test('generatePassword should throw an error when an invalid password is provided', async () => {
|
72
|
+
// Arrange
|
73
|
+
const invalidPassword = '';
|
74
|
+
|
75
|
+
// Act and Assert
|
76
|
+
await expect(generatePassword(invalidPassword)).rejects.toThrow();
|
77
|
+
});
|
78
|
+
});
|
79
|
+
});
|
@@ -1,18 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
}
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
const
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
}
|
1
|
+
import * as bcrypt from 'bcrypt';
|
2
|
+
|
3
|
+
export const generateKey = (length = 36) => {
|
4
|
+
const chars =
|
5
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
6
|
+
let result = '';
|
7
|
+
for (let i = length; i > 0; i--) {
|
8
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
9
|
+
}
|
10
|
+
return result;
|
11
|
+
};
|
12
|
+
|
13
|
+
export const generateDigitCode = (length = 4): string => {
|
14
|
+
const startAt = 1 + '0'.repeat(length - 1);
|
15
|
+
const endAt = 9 + '0'.repeat(length - 1);
|
16
|
+
const generateCode = `${Math.floor(
|
17
|
+
Number(startAt) + Math.random() * Number(endAt),
|
18
|
+
)}`;
|
19
|
+
return generateCode;
|
20
|
+
};
|
21
|
+
|
22
|
+
export const generatePassword = async (
|
23
|
+
password: string,
|
24
|
+
saltOrRounds = 10,
|
25
|
+
): Promise<string> => {
|
26
|
+
return await bcrypt.hash(password, saltOrRounds);
|
27
|
+
};
|
@@ -1,44 +0,0 @@
|
|
1
|
-
import { EntityGeneric } from './entity.generic';
|
2
|
-
|
3
|
-
describe('EntityGeneric', () => {
|
4
|
-
let entity: EntityGeneric;
|
5
|
-
|
6
|
-
beforeEach(() => {
|
7
|
-
entity = new EntityGeneric();
|
8
|
-
});
|
9
|
-
|
10
|
-
it('should have an id property', () => {
|
11
|
-
expect(entity).toHaveProperty('id');
|
12
|
-
expect(entity.id).toBeUndefined(); // Assuming the constructor sets id as undefined
|
13
|
-
});
|
14
|
-
|
15
|
-
it('should have a createdAt property', () => {
|
16
|
-
expect(entity).toHaveProperty('createdAt');
|
17
|
-
expect(entity.createdAt).toBeInstanceOf(Date);
|
18
|
-
});
|
19
|
-
|
20
|
-
it('should have an updatedAt property', () => {
|
21
|
-
expect(entity).toHaveProperty('updatedAt');
|
22
|
-
expect(entity.updatedAt).toBeInstanceOf(Date);
|
23
|
-
});
|
24
|
-
|
25
|
-
it('should have a createdBy property', () => {
|
26
|
-
expect(entity).toHaveProperty('createdBy');
|
27
|
-
expect(entity.createdBy).toBeUndefined(); // Assuming createdBy is not set in the constructor
|
28
|
-
});
|
29
|
-
|
30
|
-
it('should have an updatedBy property', () => {
|
31
|
-
expect(entity).toHaveProperty('updatedBy');
|
32
|
-
expect(entity.updatedBy).toBeUndefined(); // Assuming updatedBy is not set in the constructor
|
33
|
-
});
|
34
|
-
|
35
|
-
it('should have a state property', () => {
|
36
|
-
expect(entity).toHaveProperty('state');
|
37
|
-
expect(entity.state).toBeUndefined(); // Assuming state is not set in the constructor
|
38
|
-
});
|
39
|
-
|
40
|
-
it('should have a version property', () => {
|
41
|
-
expect(entity).toHaveProperty('version');
|
42
|
-
expect(entity.version).toBeUndefined(); // Assuming version is not set in the constructor
|
43
|
-
});
|
44
|
-
});
|
package/src/common/index.ts
DELETED
File without changes
|