@smartsoft001/crud-shell-nestjs 2.76.0 → 2.81.0
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/index.js +1840 -0
- package/package.json +3 -3
- package/src/lib/controllers/crud/crud.controller.d.ts +39 -0
- package/src/lib/controllers/crud/query-to-mongo.d.ts +5 -0
- package/src/lib/controllers/{index.ts → index.d.ts} +1 -3
- package/src/lib/gateways/crud/crud.gateway.d.ts +18 -0
- package/src/lib/gateways/index.d.ts +2 -0
- package/src/lib/guards/auth/auth.guard.d.ts +10 -0
- package/src/lib/nestjs.module.d.ts +31 -0
- package/.eslintrc +0 -13
- package/jest.config.ts +0 -27
- package/project.json +0 -43
- package/src/lib/controllers/crud/crud.controller.spec.ts +0 -111
- package/src/lib/controllers/crud/crud.controller.ts +0 -355
- package/src/lib/controllers/crud/query-to-mongo.spec.ts +0 -32
- package/src/lib/controllers/crud/query-to-mongo.ts +0 -295
- package/src/lib/gateways/crud/crud.gateway.spec.ts +0 -122
- package/src/lib/gateways/crud/crud.gateway.ts +0 -73
- package/src/lib/gateways/index.ts +0 -3
- package/src/lib/guards/auth/auth.guard.spec.ts +0 -61
- package/src/lib/guards/auth/auth.guard.ts +0 -22
- package/src/lib/nestjs.module.ts +0 -94
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -11
- package/tsconfig.spec.json +0 -20
- /package/src/{index.ts → index.d.ts} +0 -0
- /package/{test-setup.ts → test-setup.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartsoft001/crud-shell-nestjs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.81.0",
|
|
4
4
|
"description": "Utils to crud",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"url": "https://github.com/emiljuchnikowski/smartsoft/issues"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@smartsoft001/crud-shell-app-services": "2.
|
|
20
|
+
"@smartsoft001/crud-shell-app-services": "2.81.0",
|
|
21
21
|
"@smartsoft001/crud-shell-dto": "*",
|
|
22
|
-
"@smartsoft001/domain-core": "2.
|
|
22
|
+
"@smartsoft001/domain-core": "2.81.0"
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://github.com/emiljuchnikowski/smartsoft#readme"
|
|
25
25
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Response, Request } from 'express';
|
|
2
|
+
import { CreateManyMode } from '@smartsoft001/crud-domain';
|
|
3
|
+
import { CrudService } from '@smartsoft001/crud-shell-app-services';
|
|
4
|
+
import { IEntity } from '@smartsoft001/domain-core';
|
|
5
|
+
import { IUser } from '@smartsoft001/users';
|
|
6
|
+
export declare class CrudController<T extends IEntity<string>> {
|
|
7
|
+
protected readonly service: CrudService<T>;
|
|
8
|
+
constructor(service: CrudService<T>);
|
|
9
|
+
static getLink(req: Request): string;
|
|
10
|
+
create(data: T, user: IUser, res: Response): Promise<Response>;
|
|
11
|
+
createMany(data: T[], user: IUser, res: Response, mode: CreateManyMode): Promise<Response>;
|
|
12
|
+
readById(params: {
|
|
13
|
+
id: string;
|
|
14
|
+
}, user: IUser): Promise<T>;
|
|
15
|
+
read(user: IUser, req: Request, res: Response): Promise<void>;
|
|
16
|
+
update(params: {
|
|
17
|
+
id: string;
|
|
18
|
+
}, data: T, user: IUser): Promise<void>;
|
|
19
|
+
updatePartial(params: {
|
|
20
|
+
id: string;
|
|
21
|
+
}, data: Partial<T>, user: IUser): Promise<void>;
|
|
22
|
+
delete(params: {
|
|
23
|
+
id: string;
|
|
24
|
+
}, user: IUser): Promise<void>;
|
|
25
|
+
uploadAttachment(request: Request, response: Response): any;
|
|
26
|
+
downloadAttachment(id: string, request: Request, response: Response): Promise<void>;
|
|
27
|
+
deleteAttachment(id: string): Promise<void>;
|
|
28
|
+
protected getQueryObject(queryObject: any): {
|
|
29
|
+
criteria: any;
|
|
30
|
+
options: any;
|
|
31
|
+
links: any;
|
|
32
|
+
};
|
|
33
|
+
protected parseToXlsx(data: T[]): any;
|
|
34
|
+
protected parseToCsv(data: T[]): string;
|
|
35
|
+
protected getDataWithFields(data: Array<T>): {
|
|
36
|
+
res: any;
|
|
37
|
+
fields: any;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, WsResponse } from '@nestjs/websockets';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { Socket } from 'socket.io';
|
|
4
|
+
import { CrudService } from '@smartsoft001/crud-shell-app-services';
|
|
5
|
+
import { ItemChangedData } from '@smartsoft001/crud-shell-dtos';
|
|
6
|
+
import { IEntity } from '@smartsoft001/domain-core';
|
|
7
|
+
export declare class CrudGateway<T extends IEntity<string>> implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
|
|
8
|
+
private service;
|
|
9
|
+
private _clientsSubscriptions;
|
|
10
|
+
constructor(service: CrudService<T>);
|
|
11
|
+
handleFilter(data: {
|
|
12
|
+
id?: string;
|
|
13
|
+
}, client: Socket): Observable<WsResponse<ItemChangedData>>;
|
|
14
|
+
afterInit(server: any): void;
|
|
15
|
+
handleDisconnect(client: any): void;
|
|
16
|
+
private clearSubscription;
|
|
17
|
+
handleConnection(client: any, ...args: any[]): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const AuthJwtGuard_base: import("@nestjs/passport").Type<import("@nestjs/passport").IAuthGuard>;
|
|
2
|
+
export declare class AuthJwtGuard extends AuthJwtGuard_base {
|
|
3
|
+
private readonly logger;
|
|
4
|
+
handleRequest(err: any, user: any, info: any): any;
|
|
5
|
+
}
|
|
6
|
+
declare const AuthOrAnonymousJwtGuard_base: import("@nestjs/passport").Type<import("@nestjs/passport").IAuthGuard>;
|
|
7
|
+
export declare class AuthOrAnonymousJwtGuard extends AuthOrAnonymousJwtGuard_base {
|
|
8
|
+
handleRequest(err: any, user: any, info: any): any;
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { SharedConfig } from '@smartsoft001/nestjs';
|
|
3
|
+
export declare class CrudShellNestjsModule {
|
|
4
|
+
static forRoot<T>(options: SharedConfig & {
|
|
5
|
+
db: {
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
database: string;
|
|
9
|
+
username?: string;
|
|
10
|
+
password?: string;
|
|
11
|
+
collection?: string;
|
|
12
|
+
type?: T;
|
|
13
|
+
};
|
|
14
|
+
} & {
|
|
15
|
+
restApi: boolean;
|
|
16
|
+
socket: boolean;
|
|
17
|
+
}): DynamicModule;
|
|
18
|
+
}
|
|
19
|
+
export declare class CrudShellNestjsCoreModule {
|
|
20
|
+
static forRoot<T>(options: SharedConfig & {
|
|
21
|
+
db: {
|
|
22
|
+
host: string;
|
|
23
|
+
port: number;
|
|
24
|
+
database: string;
|
|
25
|
+
username?: string;
|
|
26
|
+
password?: string;
|
|
27
|
+
collection?: string;
|
|
28
|
+
type?: any;
|
|
29
|
+
};
|
|
30
|
+
}): DynamicModule;
|
|
31
|
+
}
|
package/.eslintrc
DELETED
package/jest.config.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
export default {
|
|
3
|
-
globals: {},
|
|
4
|
-
transform: {
|
|
5
|
-
'^.+\\.[tj]sx?$': [
|
|
6
|
-
'ts-jest',
|
|
7
|
-
{
|
|
8
|
-
tsConfig: '<rootDir>/tsconfig.spec.json',
|
|
9
|
-
},
|
|
10
|
-
],
|
|
11
|
-
},
|
|
12
|
-
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
|
13
|
-
coverageDirectory: '../../../../coverage/packages/crud/shell/nestjs',
|
|
14
|
-
displayName: 'crud-shell-nestjs',
|
|
15
|
-
testEnvironment: 'node',
|
|
16
|
-
preset: '../../../../jest.preset.js',
|
|
17
|
-
/* TODO: Update to latest Jest snapshotFormat
|
|
18
|
-
* By default Nx has kept the older style of Jest Snapshot formats
|
|
19
|
-
* to prevent breaking of any existing tests with snapshots.
|
|
20
|
-
* It's recommend you update to the latest format.
|
|
21
|
-
* You can do this by removing snapshotFormat property
|
|
22
|
-
* and running tests with --update-snapshot flag.
|
|
23
|
-
* Example: From within the project directory, run "nx test --update-snapshot"
|
|
24
|
-
* More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
|
|
25
|
-
*/
|
|
26
|
-
snapshotFormat: { escapeString: true, printBasicPrototype: true },
|
|
27
|
-
};
|
package/project.json
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "crud-shell-nestjs",
|
|
3
|
-
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
-
"sourceRoot": "packages/crud/shell/nestjs/src",
|
|
5
|
-
"projectType": "library",
|
|
6
|
-
"tags": ["scope:crud", "type:shell"],
|
|
7
|
-
"generators": {},
|
|
8
|
-
"targets": {
|
|
9
|
-
"lint": {
|
|
10
|
-
"executor": "@nx/eslint:lint",
|
|
11
|
-
"outputs": ["{options.outputFile}"],
|
|
12
|
-
"options": {
|
|
13
|
-
"lintFilePatterns": [
|
|
14
|
-
"packages/crud/shell/nestjs/**/*.{ts,tsx,js,jsx}",
|
|
15
|
-
"packages/crud/shell/nestjs/package.json"
|
|
16
|
-
],
|
|
17
|
-
"tsConfig": [
|
|
18
|
-
"packages/crud/shell/nestjs/tsconfig.lib.json",
|
|
19
|
-
"packages/crud/shell/nestjs/tsconfig.spec.json"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
"test": {
|
|
24
|
-
"executor": "@nx/jest:jest",
|
|
25
|
-
"options": {
|
|
26
|
-
"jestConfig": "packages/crud/shell/nestjs/jest.config.ts",
|
|
27
|
-
"passWithNoTests": true
|
|
28
|
-
},
|
|
29
|
-
"outputs": ["{workspaceRoot}/coverage/packages/crud/shell/nestjs"]
|
|
30
|
-
},
|
|
31
|
-
"build": {
|
|
32
|
-
"executor": "@nx/esbuild:esbuild",
|
|
33
|
-
"options": {
|
|
34
|
-
"outputPath": "dist/packages/crud/shell/nestjs",
|
|
35
|
-
"tsConfig": "packages/crud/shell/nestjs/tsconfig.lib.json",
|
|
36
|
-
"packageJson": "packages/crud/shell/nestjs/package.json",
|
|
37
|
-
"main": "packages/crud/shell/nestjs/src/index.ts",
|
|
38
|
-
"assets": ["packages/crud/shell/nestjs/*.md"]
|
|
39
|
-
},
|
|
40
|
-
"outputs": ["{options.outputPath}"]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Request } from 'express';
|
|
2
|
-
import * as XLSX from 'xlsx';
|
|
3
|
-
|
|
4
|
-
import { CrudService } from '@smartsoft001/crud-shell-app-services';
|
|
5
|
-
|
|
6
|
-
import { CrudController } from './crud.controller';
|
|
7
|
-
import * as q2mModule from './query-to-mongo';
|
|
8
|
-
|
|
9
|
-
jest.mock('xlsx');
|
|
10
|
-
jest.mock('json2csv', () => ({
|
|
11
|
-
Parser: jest.fn().mockImplementation(() => ({ parse: jest.fn(() => 'csv') })),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
describe('crud-nestjs: CrudController', () => {
|
|
15
|
-
let service: jest.Mocked<CrudService<any>>;
|
|
16
|
-
let controller: CrudController<any>;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
service = {
|
|
20
|
-
create: jest.fn(),
|
|
21
|
-
createMany: jest.fn(),
|
|
22
|
-
readById: jest.fn(),
|
|
23
|
-
read: jest.fn(),
|
|
24
|
-
update: jest.fn(),
|
|
25
|
-
updatePartial: jest.fn(),
|
|
26
|
-
delete: jest.fn(),
|
|
27
|
-
uploadAttachment: jest.fn(),
|
|
28
|
-
getAttachmentInfo: jest.fn(),
|
|
29
|
-
getAttachmentStream: jest.fn(),
|
|
30
|
-
deleteAttachment: jest.fn(),
|
|
31
|
-
} as any;
|
|
32
|
-
controller = new CrudController(service);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('constructor', () => {
|
|
36
|
-
it('should assign service', () => {
|
|
37
|
-
expect(controller['service']).toBe(service);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('static getLink', () => {
|
|
42
|
-
it('should return correct link', () => {
|
|
43
|
-
const req = {
|
|
44
|
-
protocol: 'http',
|
|
45
|
-
headers: { host: 'localhost' },
|
|
46
|
-
url: '/api',
|
|
47
|
-
} as Request;
|
|
48
|
-
expect(CrudController.getLink(req)).toBe('http://localhost/api');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('getQueryObject', () => {
|
|
53
|
-
it('should call q2m and return result', () => {
|
|
54
|
-
const query = { a: 1, b: 2 };
|
|
55
|
-
const result = { criteria: {}, options: {}, links: jest.fn() };
|
|
56
|
-
jest.spyOn(q2mModule, 'q2m').mockReturnValue(result);
|
|
57
|
-
expect(controller['getQueryObject'](query)).toBe(result);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('parseToCsv', () => {
|
|
62
|
-
it('should return empty string for empty data', () => {
|
|
63
|
-
expect(controller['parseToCsv']([])).toBe('');
|
|
64
|
-
});
|
|
65
|
-
it('should return csv string for data', () => {
|
|
66
|
-
const data = [{ a: '1', b: '2' }];
|
|
67
|
-
jest
|
|
68
|
-
.spyOn(controller as any, 'getDataWithFields')
|
|
69
|
-
.mockReturnValue({ res: data, fields: ['a', 'b'] });
|
|
70
|
-
expect(controller['parseToCsv'](data)).toBe('csv');
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('parseToXlsx', () => {
|
|
75
|
-
it('should return empty string for empty data', () => {
|
|
76
|
-
expect(controller['parseToXlsx']([])).toBe('');
|
|
77
|
-
});
|
|
78
|
-
it('should call XLSX utils for data', () => {
|
|
79
|
-
const data = [{ a: '1', b: '2' }];
|
|
80
|
-
jest
|
|
81
|
-
.spyOn(controller as any, 'getDataWithFields')
|
|
82
|
-
.mockReturnValue({ res: data, fields: ['a', 'b'] });
|
|
83
|
-
const jsonToSheet = jest
|
|
84
|
-
.spyOn(XLSX.utils, 'json_to_sheet')
|
|
85
|
-
.mockReturnValue({} as any);
|
|
86
|
-
const bookNew = jest
|
|
87
|
-
.spyOn(XLSX.utils, 'book_new')
|
|
88
|
-
.mockReturnValue({} as any);
|
|
89
|
-
const bookAppendSheet = jest
|
|
90
|
-
.spyOn(XLSX.utils, 'book_append_sheet')
|
|
91
|
-
.mockImplementation();
|
|
92
|
-
const write = jest.spyOn(XLSX, 'write').mockReturnValue('buffer' as any);
|
|
93
|
-
expect(controller['parseToXlsx'](data)).toBe('buffer');
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('getDataWithFields', () => {
|
|
98
|
-
it('should flatten fields and remove html', () => {
|
|
99
|
-
const data = [{ a: '<b>1</b>', b: { c: '2' } }];
|
|
100
|
-
const result = controller['getDataWithFields'](data);
|
|
101
|
-
expect(result.fields.includes('a')).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
it('should remove keys not in fields', () => {
|
|
104
|
-
const data = [{ a: '1', b: '2' }];
|
|
105
|
-
const result = controller['getDataWithFields'](data);
|
|
106
|
-
expect(
|
|
107
|
-
Object.keys(result.res[0]).every((k) => result.fields.includes(k)),
|
|
108
|
-
).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Body,
|
|
3
|
-
Controller,
|
|
4
|
-
Delete,
|
|
5
|
-
Get,
|
|
6
|
-
HttpCode,
|
|
7
|
-
NotFoundException,
|
|
8
|
-
Param,
|
|
9
|
-
Patch,
|
|
10
|
-
Post,
|
|
11
|
-
Put,
|
|
12
|
-
Query,
|
|
13
|
-
Req,
|
|
14
|
-
Res,
|
|
15
|
-
UseGuards,
|
|
16
|
-
} from '@nestjs/common';
|
|
17
|
-
import * as Busboy from 'busboy';
|
|
18
|
-
import { Response, Request } from 'express';
|
|
19
|
-
import { Parser } from 'json2csv';
|
|
20
|
-
import * as _ from 'lodash';
|
|
21
|
-
import * as moment from 'moment-timezone';
|
|
22
|
-
import * as XLSX from 'xlsx';
|
|
23
|
-
|
|
24
|
-
import { CreateManyMode } from '@smartsoft001/crud-domain';
|
|
25
|
-
import { CrudService } from '@smartsoft001/crud-shell-app-services';
|
|
26
|
-
import { IEntity } from '@smartsoft001/domain-core';
|
|
27
|
-
import { User } from '@smartsoft001/nestjs';
|
|
28
|
-
import { IUser } from '@smartsoft001/users';
|
|
29
|
-
import { GuidService } from '@smartsoft001/utils';
|
|
30
|
-
|
|
31
|
-
import { Readable } from 'stream';
|
|
32
|
-
|
|
33
|
-
import { q2m } from './query-to-mongo';
|
|
34
|
-
import {
|
|
35
|
-
AuthJwtGuard,
|
|
36
|
-
AuthOrAnonymousJwtGuard,
|
|
37
|
-
} from '../../guards/auth/auth.guard';
|
|
38
|
-
|
|
39
|
-
@Controller('')
|
|
40
|
-
export class CrudController<T extends IEntity<string>> {
|
|
41
|
-
constructor(protected readonly service: CrudService<T>) {}
|
|
42
|
-
|
|
43
|
-
static getLink(req: Request): string {
|
|
44
|
-
return req.protocol + '://' + req.headers.host + req.url;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
@UseGuards(AuthJwtGuard)
|
|
48
|
-
@Post()
|
|
49
|
-
@HttpCode(200)
|
|
50
|
-
async create(
|
|
51
|
-
@Body() data: T,
|
|
52
|
-
@User() user: IUser,
|
|
53
|
-
@Res() res: Response,
|
|
54
|
-
): Promise<Response> {
|
|
55
|
-
const id = await this.service.create(data, user);
|
|
56
|
-
res.set('Location', CrudController.getLink(res.req) + '/' + id);
|
|
57
|
-
return res.send({
|
|
58
|
-
id,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@UseGuards(AuthJwtGuard)
|
|
63
|
-
@Post('bulk')
|
|
64
|
-
async createMany(
|
|
65
|
-
@Body() data: T[],
|
|
66
|
-
@User() user: IUser,
|
|
67
|
-
@Res() res: Response,
|
|
68
|
-
@Query('mode') mode: CreateManyMode,
|
|
69
|
-
): Promise<Response> {
|
|
70
|
-
const result = await this.service.createMany(data, user, { mode });
|
|
71
|
-
return res.send(result);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@UseGuards(AuthOrAnonymousJwtGuard)
|
|
75
|
-
@Get(':id')
|
|
76
|
-
async readById(
|
|
77
|
-
@Param() params: { id: string },
|
|
78
|
-
@User() user: IUser,
|
|
79
|
-
): Promise<T> {
|
|
80
|
-
const result = await this.service.readById(params.id, user);
|
|
81
|
-
|
|
82
|
-
if (!result) {
|
|
83
|
-
throw new NotFoundException('Invalid id');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@UseGuards(AuthOrAnonymousJwtGuard)
|
|
90
|
-
@Get()
|
|
91
|
-
async read(
|
|
92
|
-
@User() user: IUser,
|
|
93
|
-
@Req() req: Request,
|
|
94
|
-
@Res() res: Response,
|
|
95
|
-
): Promise<void> {
|
|
96
|
-
const object = this.getQueryObject(req.query);
|
|
97
|
-
|
|
98
|
-
const { data, totalCount } = await this.service.read(
|
|
99
|
-
object.criteria,
|
|
100
|
-
{
|
|
101
|
-
...object.options,
|
|
102
|
-
allowDiskUse:
|
|
103
|
-
req.headers['content-type'] === 'text/csv' ||
|
|
104
|
-
req.headers['content-type'] ===
|
|
105
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
106
|
-
},
|
|
107
|
-
user,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
if (req.headers['content-type'] === 'text/csv') {
|
|
111
|
-
res.set({
|
|
112
|
-
'Content-Type': 'text/csv',
|
|
113
|
-
});
|
|
114
|
-
res.send(this.parseToCsv(data));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
req.headers['content-type'] ===
|
|
119
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
120
|
-
) {
|
|
121
|
-
res.set({
|
|
122
|
-
'Content-Type':
|
|
123
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
124
|
-
});
|
|
125
|
-
res.send(this.parseToXlsx(data));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
res.send({
|
|
129
|
-
data,
|
|
130
|
-
totalCount,
|
|
131
|
-
links: object.links(
|
|
132
|
-
CrudController.getLink(req).split('?')[0],
|
|
133
|
-
totalCount,
|
|
134
|
-
),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@UseGuards(AuthJwtGuard)
|
|
139
|
-
@Put(':id')
|
|
140
|
-
async update(
|
|
141
|
-
@Param() params: { id: string },
|
|
142
|
-
@Body() data: T,
|
|
143
|
-
@User() user: IUser,
|
|
144
|
-
): Promise<void> {
|
|
145
|
-
await this.service.update(params.id, data, user);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@UseGuards(AuthJwtGuard)
|
|
149
|
-
@Patch(':id')
|
|
150
|
-
async updatePartial(
|
|
151
|
-
@Param() params: { id: string },
|
|
152
|
-
@Body() data: Partial<T>,
|
|
153
|
-
@User() user: IUser,
|
|
154
|
-
): Promise<void> {
|
|
155
|
-
await this.service.updatePartial(params.id, data, user);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@UseGuards(AuthJwtGuard)
|
|
159
|
-
@Delete(':id')
|
|
160
|
-
async delete(
|
|
161
|
-
@Param() params: { id: string },
|
|
162
|
-
@User() user: IUser,
|
|
163
|
-
): Promise<void> {
|
|
164
|
-
await this.service.delete(params.id, user);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
@Post('attachments')
|
|
168
|
-
uploadAttachment(@Req() request: Request, @Res() response: Response) {
|
|
169
|
-
const busboy = new Busboy({
|
|
170
|
-
headers: request.headers,
|
|
171
|
-
});
|
|
172
|
-
const id = GuidService.create();
|
|
173
|
-
const readable = new Readable();
|
|
174
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
175
|
-
readable._read = () => {};
|
|
176
|
-
|
|
177
|
-
let fileName, encoding, mimeType;
|
|
178
|
-
|
|
179
|
-
busboy.on(
|
|
180
|
-
'file',
|
|
181
|
-
(field, file, resultFileName, resultEncoding, resultMimeType) => {
|
|
182
|
-
fileName = resultFileName;
|
|
183
|
-
encoding = resultEncoding;
|
|
184
|
-
mimeType = resultMimeType;
|
|
185
|
-
|
|
186
|
-
this.service.uploadAttachment({
|
|
187
|
-
id,
|
|
188
|
-
stream: readable,
|
|
189
|
-
fileName,
|
|
190
|
-
encoding,
|
|
191
|
-
mimeType,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
file.on('data', (data) => {
|
|
195
|
-
readable.push(data);
|
|
196
|
-
});
|
|
197
|
-
},
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
busboy.on('finish', function () {
|
|
201
|
-
readable.push(null);
|
|
202
|
-
response.set('Location', CrudController.getLink(response.req) + '/' + id);
|
|
203
|
-
response.json({
|
|
204
|
-
id,
|
|
205
|
-
fileName,
|
|
206
|
-
contentType: mimeType,
|
|
207
|
-
length: readable.readableLength,
|
|
208
|
-
});
|
|
209
|
-
response.end();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
return request.pipe(busboy);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
@Get('attachments/:id')
|
|
216
|
-
async downloadAttachment(
|
|
217
|
-
@Param('id') id: string,
|
|
218
|
-
@Req() request: Request,
|
|
219
|
-
@Res() response: Response,
|
|
220
|
-
) {
|
|
221
|
-
const fileInfo = await this.service.getAttachmentInfo(id);
|
|
222
|
-
|
|
223
|
-
if (request.headers.range) {
|
|
224
|
-
const range = request.headers.range.substr(6).split('-');
|
|
225
|
-
const start = parseInt(range[0], 10);
|
|
226
|
-
const end = parseInt(range[1], 10) || null;
|
|
227
|
-
|
|
228
|
-
const readstream = await this.service.getAttachmentStream(id, {
|
|
229
|
-
start,
|
|
230
|
-
end,
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
response.status(206);
|
|
234
|
-
response.set({
|
|
235
|
-
'Accept-Ranges': 'bytes',
|
|
236
|
-
'Content-Type': fileInfo.contentType,
|
|
237
|
-
'Content-Range': `bytes ${start}-${end ? end : fileInfo.length - 1}/${
|
|
238
|
-
fileInfo.length
|
|
239
|
-
}`,
|
|
240
|
-
'Content-Length': (end ? end : fileInfo.length) - start,
|
|
241
|
-
'Content-Disposition': `attachment; filename="${encodeURI(fileInfo.fileName)}"`,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
response.on('close', () => {
|
|
245
|
-
readstream.destroy();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
readstream.pipe(response);
|
|
249
|
-
} else {
|
|
250
|
-
const readstream = await this.service.getAttachmentStream(id);
|
|
251
|
-
|
|
252
|
-
response.on('close', () => {
|
|
253
|
-
readstream.destroy();
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
response.status(200);
|
|
257
|
-
response.set({
|
|
258
|
-
'Accept-Range': 'bytes',
|
|
259
|
-
'Content-Type': fileInfo.contentType,
|
|
260
|
-
'Content-Length': fileInfo.length,
|
|
261
|
-
'Content-Disposition': `attachment; filename="${encodeURI(fileInfo.fileName)}"`,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
readstream.pipe(response);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
@Delete('attachments/:id')
|
|
269
|
-
async deleteAttachment(@Param('id') id: string): Promise<void> {
|
|
270
|
-
await this.service.deleteAttachment(id);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
protected getQueryObject(queryObject: any): { criteria; options; links } {
|
|
274
|
-
let q = '';
|
|
275
|
-
|
|
276
|
-
Object.keys(queryObject).forEach((key) => {
|
|
277
|
-
q += `&${key}=${queryObject[key]}`;
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
const result = q2m(q);
|
|
281
|
-
|
|
282
|
-
return result;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
protected parseToXlsx(data: T[]) {
|
|
286
|
-
if (!data || !data.length) {
|
|
287
|
-
return '';
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const { res } = this.getDataWithFields(data);
|
|
291
|
-
|
|
292
|
-
const ws = XLSX.utils.json_to_sheet(res);
|
|
293
|
-
|
|
294
|
-
const wb = XLSX.utils.book_new();
|
|
295
|
-
XLSX.utils.book_append_sheet(wb, ws, 'data');
|
|
296
|
-
|
|
297
|
-
return XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
protected parseToCsv(data: T[]): string {
|
|
301
|
-
if (!data || !data.length) {
|
|
302
|
-
return '';
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const { res, fields } = this.getDataWithFields(data);
|
|
306
|
-
|
|
307
|
-
return new Parser(fields).parse(data);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
protected getDataWithFields(data: Array<T>): { res; fields } {
|
|
311
|
-
const fields = [];
|
|
312
|
-
|
|
313
|
-
const execute = (item, baseKey, baseItem) => {
|
|
314
|
-
Object.keys(item).forEach((key) => {
|
|
315
|
-
if (item[key] && typeof item[key] === 'string') {
|
|
316
|
-
item[key] = item[key].replace(/<[^>]*>?/gm, '');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (item[key] && item[key] instanceof Date) {
|
|
320
|
-
item[key] = moment(item[key])
|
|
321
|
-
.tz('Europe/Warsaw')
|
|
322
|
-
.format('YYYY-MM-DD HH:mm:ss');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const val = item[key];
|
|
326
|
-
|
|
327
|
-
if (_.isArray(val)) {
|
|
328
|
-
return;
|
|
329
|
-
} else if (_.isObject(val) && Object.keys(val).length) {
|
|
330
|
-
execute(val, baseKey + key + '_', baseItem);
|
|
331
|
-
} else if (baseKey) {
|
|
332
|
-
baseItem[baseKey + key] = val;
|
|
333
|
-
if (!fields.some((f) => f === baseKey + key))
|
|
334
|
-
fields.push(baseKey + key);
|
|
335
|
-
} else {
|
|
336
|
-
if (!fields.some((f) => f === key)) fields.push(key);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
data.forEach((item) => {
|
|
342
|
-
execute(item, '', item);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
data.forEach((item) => {
|
|
346
|
-
Object.keys(item).forEach((key) => {
|
|
347
|
-
if (!fields.some((f) => f === key)) {
|
|
348
|
-
delete item[key];
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
return { res: data, fields };
|
|
354
|
-
}
|
|
355
|
-
}
|