Skip to content

Commit e59e754

Browse files
authored
Merge pull request #186 from AlvaroDavi5/develop
Develop
2 parents 9f5ae37 + a09945c commit e59e754

File tree

13 files changed

+190
-125
lines changed

13 files changed

+190
-125
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "node_backend_boilerplate",
4-
"version": "7.1.0",
4+
"version": "7.2.0",
55
"description": "Node.js Boilerplate for Back-End using TypeScript",
66
"license": "MIT",
77
"private": false,

src/dev/mocks/mockedModules.ts

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -47,48 +47,41 @@ export const loggerProviderMock: LoggerInterface & {
4747
};
4848

4949
export const dataParserHelperMock = {
50-
toString: (data: unknown): string => {
51-
let result = '';
52-
53-
switch (typeof data) {
54-
case 'bigint':
55-
result = data.toString();
56-
break;
57-
case 'number':
58-
result = data.toString();
59-
break;
60-
case 'boolean':
61-
result = data.toString();
62-
break;
63-
case 'string':
64-
result = data;
65-
break;
66-
case 'object':
67-
if (!data)
68-
break;
69-
else if (Array.isArray(data)) {
70-
result = `${data.join(', ')}`;
71-
} else if (data instanceof Error)
72-
result = `${data?.name}: ${data?.message}`;
73-
else {
74-
try {
75-
result = JSON.stringify(data);
76-
} catch (_error) {
77-
result = data?.toString() ?? '';
50+
toString: (data: unknown, returnUndefined = false): string => {
51+
const defaultParse = String(data);
52+
const circularReference = '[Circular]';
53+
54+
if (typeof data === 'string')
55+
return data;
56+
if (typeof data === 'undefined')
57+
return returnUndefined ? 'undefined' : '';
58+
if (typeof data === 'object') {
59+
if (!data) {
60+
return defaultParse;
61+
}
62+
63+
if (Array.isArray(data)) {
64+
const parsedData = data.map((element) => element === data ? circularReference : String(element));
65+
return parsedData.join(', ');
66+
}
67+
68+
if (data instanceof Error) {
69+
return `${data?.name}: ${data?.message}`;
70+
}
71+
72+
try {
73+
for (const key of Object.keys(data)) {
74+
if ((data as Record<string, unknown>)[String(key)] === data) {
75+
(data as Record<string, unknown>)[String(key)] = circularReference;
7876
}
7977
}
80-
break;
81-
case 'symbol':
82-
result = data.toString();
83-
break;
84-
case 'function':
85-
result = data.toString();
86-
break;
87-
default:
88-
break;
78+
return JSON.stringify(data);
79+
} catch (_error) {
80+
return defaultParse;
81+
}
8982
}
9083

91-
return result;
84+
return defaultParse;
9285
},
9386

9487
toObject: <OT = object>(data: string): OT | null => {

src/modules/api/controllers/Health.controller.ts

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import {
2-
Controller, Req, Res, Version,
3-
Sse, Get, Headers, Param, Query, Body,
4-
UseGuards, UseFilters, UseInterceptors,
2+
Controller, Req, Res, Version, Get, Headers, Param, Query, Body,
3+
UseGuards, UseFilters, UseInterceptors
54
} from '@nestjs/common';
65
import { ApiOperation, ApiTags, ApiProduces, ApiConsumes, ApiOkResponse } from '@nestjs/swagger';
7-
import { interval, map, Observable } from 'rxjs';
86
import exceptionsResponseDecorator from '@api/decorators/exceptionsResponse.decorator';
97
import HttpExceptionsFilter from '@api/filters/HttpExceptions.filter';
108
import ResponseInterceptor from '@api/interceptors/Response.interceptor';
@@ -98,24 +96,4 @@ export default class HealthController {
9896
public healthCheckV1(): string {
9997
return 'OK';
10098
}
101-
102-
@ApiOperation({
103-
summary: 'Server-Sent Events',
104-
description: 'Send to Client the Server events',
105-
deprecated: false,
106-
})
107-
@Sse('sse')
108-
@Version(ApiVersionsEnum.DEFAULT)
109-
@ApiOkResponse({
110-
schema: {
111-
example: { number: 1, text: 'OK' },
112-
}
113-
})
114-
@ApiConsumes('text/plain')
115-
@ApiProduces('text/event-stream')
116-
sse(): Observable<Partial<MessageEvent<{ number: number, text: string }>>> {
117-
return interval(1000).pipe(map<number, Partial<MessageEvent<{ number: number, text: string }>>>((n) => ({
118-
data: { number: n, text: 'OK' },
119-
})));
120-
}
12199
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
Controller, Version,
3+
Sse, UseGuards, UseFilters
4+
} from '@nestjs/common';
5+
import { ApiOperation, ApiTags, ApiProduces, ApiConsumes, ApiOkResponse } from '@nestjs/swagger';
6+
import { interval, map, Observable } from 'rxjs';
7+
import exceptionsResponseDecorator from '@api/decorators/exceptionsResponse.decorator';
8+
import HttpExceptionsFilter from '@api/filters/HttpExceptions.filter';
9+
import CustomThrottlerGuard from '@common/guards/CustomThrottler.guard';
10+
import { ApiVersionsEnum } from '@common/enums/apiVersions.enum';
11+
12+
13+
@ApiTags('Server-Sent Events')
14+
@Controller()
15+
@UseGuards(CustomThrottlerGuard)
16+
@UseFilters(HttpExceptionsFilter)
17+
@exceptionsResponseDecorator()
18+
export default class ServerEvents {
19+
20+
@ApiOperation({
21+
summary: 'Server-Sent Events',
22+
description: 'Send to Client the Server events',
23+
deprecated: false,
24+
})
25+
@Sse('sse')
26+
@Version(ApiVersionsEnum.DEFAULT)
27+
@ApiOkResponse({
28+
schema: {
29+
example: { number: 1, text: 'OK' },
30+
}
31+
})
32+
@ApiConsumes('text/plain')
33+
@ApiProduces('text/event-stream')
34+
sse(): Observable<Partial<MessageEvent<{ number: number, text: string }>>> {
35+
return interval(1000).pipe(map<number, Partial<MessageEvent<{ number: number, text: string }>>>((n) => ({
36+
data: { number: n, text: 'OK' },
37+
})));
38+
}
39+
}

src/modules/app/app.module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
22
import RequestMiddleware from '@api/middlewares/Request.middleware';
33
import HealthController from '@api/controllers/Health.controller';
4+
import ServerEvents from '@api/controllers/ServerEvents.controller';
45
import UserModule from './user/user.module';
56
import SubscriptionModule from './subscription/subscription.module';
67
import HookModule from './hook/hook.module';
@@ -16,6 +17,7 @@ import FileModule from './file/file.module';
1617
],
1718
controllers: [
1819
HealthController,
20+
ServerEvents,
1921
],
2022
providers: [],
2123
exports: [],
@@ -26,6 +28,7 @@ export default class AppModule implements NestModule {
2628
.apply(RequestMiddleware)
2729
.forRoutes(
2830
HealthController,
31+
ServerEvents,
2932
);
3033
}
3134
}

src/modules/common/utils/helpers/DataParser.helper.ts

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,60 @@ import { Injectable } from '@nestjs/common';
44

55
@Injectable()
66
export default class DataParserHelper {
7-
public toString(data: unknown): string {
8-
let result = '';
9-
10-
switch (typeof data) {
11-
case 'bigint':
12-
result = data.toString();
13-
break;
14-
case 'number':
15-
result = data.toString();
16-
break;
17-
case 'boolean':
18-
result = data.toString();
19-
break;
20-
case 'string':
21-
result = data;
22-
break;
23-
case 'object':
24-
if (!data)
25-
break;
26-
else if (Array.isArray(data)) {
27-
const parsedData = data.map((d) => this.toString(d));
28-
result = `${parsedData.join(', ')}`;
29-
} else if (data instanceof Error)
30-
result = `${data?.name}: ${data?.message}`;
31-
else {
32-
try {
33-
result = JSON.stringify(data);
34-
} catch (_error) {
35-
result = data?.toString() ?? '';
7+
public toString(data: unknown, returnUndefined = false): string {
8+
const circularReference = '[Circular Reference]';
9+
10+
if (typeof data === 'string')
11+
return data;
12+
if (typeof data === 'symbol')
13+
return data.toString();
14+
if (typeof data === 'undefined')
15+
return returnUndefined ? 'undefined' : '';
16+
17+
if (typeof data === 'object') {
18+
const fallback = String(data);
19+
20+
if (!data) {
21+
return fallback;
22+
}
23+
if (data instanceof Error) {
24+
return `${data?.name}: ${data?.message}`;
25+
}
26+
27+
if (Array.isArray(data)) {
28+
const seen = new WeakSet<unknown[]>();
29+
const visit = (array: unknown[]): string => {
30+
if (seen.has(array)) {
31+
return circularReference;
3632
}
33+
seen.add(array);
34+
const parsedData = array.map((element) => Array.isArray(element) ? visit(element) : this.toString(element, returnUndefined));
35+
return parsedData.join(', ');
36+
};
37+
return visit(data);
38+
}
39+
try {
40+
return JSON.stringify(data);
41+
} catch (error) {
42+
if (error instanceof Error && error.message.includes('circular')) {
43+
const seen = new WeakSet();
44+
const stringified = JSON.stringify(data, (_key, value) => {
45+
if (typeof value === 'object' && value !== null) {
46+
if (seen.has(value)) {
47+
return circularReference;
48+
}
49+
seen.add(value);
50+
}
51+
return value;
52+
});
53+
return stringified;
3754
}
38-
break;
39-
case 'symbol':
40-
result = data.toString();
41-
break;
42-
case 'function':
43-
result = data.toString();
44-
break;
45-
default:
46-
break;
55+
56+
return fallback;
57+
}
4758
}
4859

49-
return result;
60+
return String(data);
5061
}
5162

5263
public toObject<OT = object>(data: string): OT | null {

src/modules/common/utils/sentryCalls.util.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@ import { ExceptionMetadataInterface } from '@shared/internal/interfaces/errorInt
99

1010

1111
function parseLogLevelToSentrySeverity(logLevel: LogLevelEnum): SeverityLevel {
12-
switch (logLevel) {
13-
case LogLevelEnum.DEBUG:
14-
return 'debug';
15-
case LogLevelEnum.HTTP:
16-
return 'info';
17-
case LogLevelEnum.INFO:
18-
return 'info';
19-
case LogLevelEnum.WARN:
20-
return 'warning';
21-
case LogLevelEnum.ERROR:
22-
return 'error';
23-
default:
24-
return 'log';
12+
if (logLevel === LogLevelEnum.WARN) {
13+
return 'warning';
14+
}
15+
if (logLevel === LogLevelEnum.HTTP) {
16+
return 'info';
2517
}
18+
if (logLevel === LogLevelEnum.VERBOSE || !Object.values(LogLevelEnum).includes(logLevel)) {
19+
return 'log';
20+
}
21+
22+
return logLevel;
2623
}
2724

2825
function parseExceptionStatusCodeToSentrySeverity(statusCode: number): SeverityLevel {

src/modules/core/logging/Logger.service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { LoggerInterface, MetadataInterface, getLoggerOptions } from './logger';
1313
export default class LoggerService implements LoggerInterface {
1414
private contextName!: string;
1515
private requestId?: string;
16+
private messageId?: string;
1617
private socketId?: string;
1718
private clientIp?: string;
1819
private readonly logger: Logger;
@@ -53,6 +54,14 @@ export default class LoggerService implements LoggerInterface {
5354
this.requestId = requestId;
5455
}
5556

57+
public getMessageId(): string | undefined {
58+
return this.messageId;
59+
}
60+
61+
public setMessageId(messageId: string | undefined): void {
62+
this.messageId = messageId;
63+
}
64+
5665
public getSocketId(): string | undefined {
5766
return this.socketId;
5867
}
@@ -82,6 +91,7 @@ export default class LoggerService implements LoggerInterface {
8291
const metadata: MetadataInterface = {
8392
context: this.getContextName(),
8493
requestId: this.getRequestId(),
94+
messageId: this.getMessageId(),
8595
socketId: this.getSocketId(),
8696
ip: this.getClientIp(),
8797
};

0 commit comments

Comments
 (0)