@devstroupe/devkit-nest 1.0.1 → 1.1.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/dist/audit/audit.context.d.ts +9 -0
- package/dist/audit/audit.context.js +5 -0
- package/dist/audit/audit.interceptor.d.ts +5 -0
- package/dist/audit/audit.interceptor.js +81 -0
- package/dist/audit/audit.module.d.ts +2 -0
- package/dist/audit/audit.module.js +29 -0
- package/dist/audit/audit.subscriber.d.ts +14 -0
- package/dist/audit/audit.subscriber.js +129 -0
- package/dist/audit/index.d.ts +4 -0
- package/dist/audit/index.js +20 -0
- package/dist/http/base-crud.controller.d.ts +2 -0
- package/dist/http/base-crud.controller.js +24 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/realtime/index.d.ts +3 -0
- package/dist/realtime/index.js +19 -0
- package/dist/realtime/realtime.gateway.d.ts +14 -0
- package/dist/realtime/realtime.gateway.js +134 -0
- package/dist/realtime/realtime.module.d.ts +2 -0
- package/dist/realtime/realtime.module.js +22 -0
- package/dist/realtime/realtime.service.d.ts +10 -0
- package/dist/realtime/realtime.service.js +32 -0
- package/package.json +11 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
export interface AuditRequestContext {
|
|
3
|
+
userId?: string | number;
|
|
4
|
+
ip?: string;
|
|
5
|
+
userAgent?: string;
|
|
6
|
+
correlationId?: string;
|
|
7
|
+
timestamp: Date;
|
|
8
|
+
}
|
|
9
|
+
export declare const auditLocalStorage: AsyncLocalStorage<AuditRequestContext>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.AuditInterceptor = void 0;
|
|
43
|
+
const common_1 = require("@nestjs/common");
|
|
44
|
+
const rxjs_1 = require("rxjs");
|
|
45
|
+
const audit_context_1 = require("./audit.context");
|
|
46
|
+
const crypto = __importStar(require("crypto"));
|
|
47
|
+
let AuditInterceptor = class AuditInterceptor {
|
|
48
|
+
intercept(context, next) {
|
|
49
|
+
const http = context.switchToHttp();
|
|
50
|
+
const req = http.getRequest();
|
|
51
|
+
if (!req) {
|
|
52
|
+
return next.handle();
|
|
53
|
+
}
|
|
54
|
+
const correlationId = req.headers['x-correlation-id'] || crypto.randomUUID();
|
|
55
|
+
const ip = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
|
56
|
+
const userAgent = req.headers['user-agent'];
|
|
57
|
+
// O usuário é extraído do JWT que o AuthGuard decodificou e salvou no request.user
|
|
58
|
+
const userId = req.user?.sub || req.user?.id || undefined;
|
|
59
|
+
const auditContext = {
|
|
60
|
+
userId,
|
|
61
|
+
ip,
|
|
62
|
+
userAgent,
|
|
63
|
+
correlationId,
|
|
64
|
+
timestamp: new Date(),
|
|
65
|
+
};
|
|
66
|
+
// Executa a requisição dentro do escopo do AsyncLocalStorage
|
|
67
|
+
return new rxjs_1.Observable((subscriber) => {
|
|
68
|
+
audit_context_1.auditLocalStorage.run(auditContext, () => {
|
|
69
|
+
next.handle().subscribe({
|
|
70
|
+
next: (val) => subscriber.next(val),
|
|
71
|
+
error: (err) => subscriber.error(err),
|
|
72
|
+
complete: () => subscriber.complete(),
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
exports.AuditInterceptor = AuditInterceptor;
|
|
79
|
+
exports.AuditInterceptor = AuditInterceptor = __decorate([
|
|
80
|
+
(0, common_1.Injectable)()
|
|
81
|
+
], AuditInterceptor);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.AuditModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const core_1 = require("@nestjs/core");
|
|
12
|
+
const audit_interceptor_1 = require("./audit.interceptor");
|
|
13
|
+
const audit_subscriber_1 = require("./audit.subscriber");
|
|
14
|
+
let AuditModule = class AuditModule {
|
|
15
|
+
};
|
|
16
|
+
exports.AuditModule = AuditModule;
|
|
17
|
+
exports.AuditModule = AuditModule = __decorate([
|
|
18
|
+
(0, common_1.Global)(),
|
|
19
|
+
(0, common_1.Module)({
|
|
20
|
+
providers: [
|
|
21
|
+
audit_subscriber_1.AuditSubscriber,
|
|
22
|
+
{
|
|
23
|
+
provide: core_1.APP_INTERCEPTOR,
|
|
24
|
+
useClass: audit_interceptor_1.AuditInterceptor,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
exports: [],
|
|
28
|
+
})
|
|
29
|
+
], AuditModule);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent, DataSource } from 'typeorm';
|
|
2
|
+
export declare class AuditSubscriber implements EntitySubscriberInterface {
|
|
3
|
+
private readonly logger;
|
|
4
|
+
constructor(dataSource: DataSource);
|
|
5
|
+
private redact;
|
|
6
|
+
private getDiff;
|
|
7
|
+
private getEntityId;
|
|
8
|
+
beforeInsert(event: InsertEvent<any>): void;
|
|
9
|
+
beforeUpdate(event: UpdateEvent<any>): void;
|
|
10
|
+
afterInsert(event: InsertEvent<any>): void;
|
|
11
|
+
afterUpdate(event: UpdateEvent<any>): void;
|
|
12
|
+
afterRemove(event: RemoveEvent<any>): void;
|
|
13
|
+
private processEvent;
|
|
14
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AuditSubscriber = void 0;
|
|
13
|
+
const typeorm_1 = require("typeorm");
|
|
14
|
+
const audit_context_1 = require("./audit.context");
|
|
15
|
+
const common_1 = require("@nestjs/common");
|
|
16
|
+
const DEFAULT_REDACT_FIELDS = [
|
|
17
|
+
'password',
|
|
18
|
+
'token',
|
|
19
|
+
'secret',
|
|
20
|
+
'secretaccesskey',
|
|
21
|
+
'accesskey',
|
|
22
|
+
'passwordhash',
|
|
23
|
+
'current_refresh_token',
|
|
24
|
+
'currentrefreshtoken',
|
|
25
|
+
'creditcard',
|
|
26
|
+
'cvv',
|
|
27
|
+
];
|
|
28
|
+
let AuditSubscriber = class AuditSubscriber {
|
|
29
|
+
logger = new common_1.Logger('AuditSystem');
|
|
30
|
+
constructor(dataSource) {
|
|
31
|
+
dataSource.subscribers.push(this);
|
|
32
|
+
}
|
|
33
|
+
redact(data) {
|
|
34
|
+
if (!data || typeof data !== 'object')
|
|
35
|
+
return data;
|
|
36
|
+
const copy = Array.isArray(data) ? [...data] : { ...data };
|
|
37
|
+
for (const key of Object.keys(copy)) {
|
|
38
|
+
const lowerKey = key.toLowerCase();
|
|
39
|
+
if (DEFAULT_REDACT_FIELDS.includes(lowerKey)) {
|
|
40
|
+
copy[key] = '******';
|
|
41
|
+
}
|
|
42
|
+
else if (typeof copy[key] === 'object' && copy[key] !== null) {
|
|
43
|
+
copy[key] = this.redact(copy[key]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return copy;
|
|
47
|
+
}
|
|
48
|
+
getDiff(before, after) {
|
|
49
|
+
const oldValues = {};
|
|
50
|
+
const newValues = {};
|
|
51
|
+
if (!before || !after) {
|
|
52
|
+
return { oldValues: before, newValues: after };
|
|
53
|
+
}
|
|
54
|
+
for (const key of Object.keys(after)) {
|
|
55
|
+
if (key === 'updatedAt' || key === 'createdAt')
|
|
56
|
+
continue;
|
|
57
|
+
const valBefore = before[key];
|
|
58
|
+
const valAfter = after[key];
|
|
59
|
+
// Comparação simples para detectar mudanças
|
|
60
|
+
if (JSON.stringify(valBefore) !== JSON.stringify(valAfter)) {
|
|
61
|
+
oldValues[key] = valBefore;
|
|
62
|
+
newValues[key] = valAfter;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { oldValues, newValues };
|
|
66
|
+
}
|
|
67
|
+
getEntityId(event) {
|
|
68
|
+
return event.entityId || event.entity?.id || event.databaseEntity?.id;
|
|
69
|
+
}
|
|
70
|
+
beforeInsert(event) {
|
|
71
|
+
const store = audit_context_1.auditLocalStorage.getStore();
|
|
72
|
+
if (store && store.userId && event.entity) {
|
|
73
|
+
if ('createdByUserId' in event.entity) {
|
|
74
|
+
event.entity.createdByUserId = store.userId;
|
|
75
|
+
}
|
|
76
|
+
if ('updatedByUserId' in event.entity) {
|
|
77
|
+
event.entity.updatedByUserId = store.userId;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
beforeUpdate(event) {
|
|
82
|
+
const store = audit_context_1.auditLocalStorage.getStore();
|
|
83
|
+
if (store && store.userId && event.entity) {
|
|
84
|
+
if ('updatedByUserId' in event.entity) {
|
|
85
|
+
event.entity.updatedByUserId = store.userId;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
afterInsert(event) {
|
|
90
|
+
this.processEvent('CREATE', event.metadata.tableName, this.getEntityId(event), null, event.entity);
|
|
91
|
+
}
|
|
92
|
+
afterUpdate(event) {
|
|
93
|
+
if (!event.databaseEntity || !event.entity)
|
|
94
|
+
return;
|
|
95
|
+
const { oldValues, newValues } = this.getDiff(event.databaseEntity, event.entity);
|
|
96
|
+
// Só registra se de fato mudou algo além de updatedAt/createdAt
|
|
97
|
+
if (Object.keys(newValues).length > 0) {
|
|
98
|
+
this.processEvent('UPDATE', event.metadata.tableName, this.getEntityId(event), oldValues, newValues);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
afterRemove(event) {
|
|
102
|
+
this.processEvent('DELETE', event.metadata.tableName, this.getEntityId(event), event.databaseEntity, null);
|
|
103
|
+
}
|
|
104
|
+
processEvent(action, entity, id, before, after) {
|
|
105
|
+
const store = audit_context_1.auditLocalStorage.getStore();
|
|
106
|
+
if (!store)
|
|
107
|
+
return; // Se a alteração ocorreu fora de uma requisição HTTP (ex: seeds, scripts), não auditamos por este canal
|
|
108
|
+
const logPayload = {
|
|
109
|
+
action,
|
|
110
|
+
entity,
|
|
111
|
+
entityId: id,
|
|
112
|
+
before: this.redact(before),
|
|
113
|
+
after: this.redact(after),
|
|
114
|
+
userId: store.userId,
|
|
115
|
+
ip: store.ip,
|
|
116
|
+
userAgent: store.userAgent,
|
|
117
|
+
correlationId: store.correlationId,
|
|
118
|
+
timestamp: store.timestamp,
|
|
119
|
+
};
|
|
120
|
+
// Imprimir o log de auditoria no formato JSON estruturado no console
|
|
121
|
+
this.logger.log(`[AUDIT] ${JSON.stringify(logPayload)}`);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
exports.AuditSubscriber = AuditSubscriber;
|
|
125
|
+
exports.AuditSubscriber = AuditSubscriber = __decorate([
|
|
126
|
+
(0, typeorm_1.EventSubscriber)(),
|
|
127
|
+
(0, common_1.Injectable)(),
|
|
128
|
+
__metadata("design:paramtypes", [typeorm_1.DataSource])
|
|
129
|
+
], AuditSubscriber);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./audit.module"), exports);
|
|
18
|
+
__exportStar(require("./audit.interceptor"), exports);
|
|
19
|
+
__exportStar(require("./audit.subscriber"), exports);
|
|
20
|
+
__exportStar(require("./audit.context"), exports);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { RealtimeService } from '../realtime/realtime.service';
|
|
1
2
|
export declare abstract class BaseCrudController<Entity, FilterDto = any> {
|
|
2
3
|
protected readonly service: any;
|
|
3
4
|
constructor(service: any);
|
|
5
|
+
protected readonly realtimeService?: RealtimeService;
|
|
4
6
|
create(body: any): Promise<Entity>;
|
|
5
7
|
update(id: string, body: any): Promise<Entity>;
|
|
6
8
|
findMany(filter: FilterDto): Promise<any>;
|
|
@@ -15,16 +15,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.BaseCrudController = void 0;
|
|
16
16
|
const common_1 = require("@nestjs/common");
|
|
17
17
|
const swagger_1 = require("@nestjs/swagger");
|
|
18
|
+
const realtime_service_1 = require("../realtime/realtime.service");
|
|
18
19
|
class BaseCrudController {
|
|
19
20
|
service;
|
|
20
21
|
constructor(service) {
|
|
21
22
|
this.service = service;
|
|
22
23
|
}
|
|
24
|
+
realtimeService;
|
|
23
25
|
async create(body) {
|
|
24
|
-
|
|
26
|
+
const result = await this.service.create(body);
|
|
27
|
+
if (this.realtimeService) {
|
|
28
|
+
const entityName = this.constructor.name.replace('Controller', '').replace('MsController', '').toLowerCase();
|
|
29
|
+
this.realtimeService.publishEntityChange(entityName, 'create', result, result.tenantId);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
25
32
|
}
|
|
26
33
|
async update(id, body) {
|
|
27
|
-
|
|
34
|
+
const result = await this.service.update(+id, body);
|
|
35
|
+
if (this.realtimeService) {
|
|
36
|
+
const entityName = this.constructor.name.replace('Controller', '').replace('MsController', '').toLowerCase();
|
|
37
|
+
this.realtimeService.publishEntityChange(entityName, 'update', result, result.tenantId);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
28
40
|
}
|
|
29
41
|
async findMany(filter) {
|
|
30
42
|
return await this.service.findMany(filter);
|
|
@@ -33,10 +45,19 @@ class BaseCrudController {
|
|
|
33
45
|
return await this.service.findOne(+id);
|
|
34
46
|
}
|
|
35
47
|
async delete(id) {
|
|
36
|
-
|
|
48
|
+
await this.service.delete(+id);
|
|
49
|
+
if (this.realtimeService) {
|
|
50
|
+
const entityName = this.constructor.name.replace('Controller', '').replace('MsController', '').toLowerCase();
|
|
51
|
+
this.realtimeService.publishEntityChange(entityName, 'delete', { id: +id });
|
|
52
|
+
}
|
|
37
53
|
}
|
|
38
54
|
}
|
|
39
55
|
exports.BaseCrudController = BaseCrudController;
|
|
56
|
+
__decorate([
|
|
57
|
+
(0, common_1.Inject)(realtime_service_1.RealtimeService),
|
|
58
|
+
(0, common_1.Optional)(),
|
|
59
|
+
__metadata("design:type", realtime_service_1.RealtimeService)
|
|
60
|
+
], BaseCrudController.prototype, "realtimeService", void 0);
|
|
40
61
|
__decorate([
|
|
41
62
|
(0, common_1.Post)(),
|
|
42
63
|
(0, swagger_1.ApiOperation)({ summary: 'Create entity' }),
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -20,3 +20,5 @@ __exportStar(require("./database/typeorm-query.builder"), exports);
|
|
|
20
20
|
__exportStar(require("./http/base-crud.controller"), exports);
|
|
21
21
|
__exportStar(require("./storage"), exports);
|
|
22
22
|
__exportStar(require("./auth"), exports);
|
|
23
|
+
__exportStar(require("./realtime"), exports);
|
|
24
|
+
__exportStar(require("./audit"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./realtime.module"), exports);
|
|
18
|
+
__exportStar(require("./realtime.service"), exports);
|
|
19
|
+
__exportStar(require("./realtime.gateway"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
|
|
2
|
+
import { Server, Socket } from 'socket.io';
|
|
3
|
+
export declare class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
|
4
|
+
server: Server;
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private connectedUsers;
|
|
7
|
+
handleConnection(client: Socket): void;
|
|
8
|
+
handleDisconnect(client: Socket): void;
|
|
9
|
+
emitEntityChange(entityName: string, action: string, data: any, tenantId?: string): void;
|
|
10
|
+
handleSubscribeEntity(entity: string, client: Socket): {
|
|
11
|
+
event: string;
|
|
12
|
+
data: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
45
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
46
|
+
};
|
|
47
|
+
var RealtimeGateway_1;
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.RealtimeGateway = void 0;
|
|
50
|
+
const websockets_1 = require("@nestjs/websockets");
|
|
51
|
+
const socket_io_1 = require("socket.io");
|
|
52
|
+
const common_1 = require("@nestjs/common");
|
|
53
|
+
const jwt = __importStar(require("jsonwebtoken"));
|
|
54
|
+
let RealtimeGateway = RealtimeGateway_1 = class RealtimeGateway {
|
|
55
|
+
server;
|
|
56
|
+
logger = new common_1.Logger(RealtimeGateway_1.name);
|
|
57
|
+
connectedUsers = new Map();
|
|
58
|
+
handleConnection(client) {
|
|
59
|
+
try {
|
|
60
|
+
const token = client.handshake.auth?.token || client.handshake.headers?.authorization?.split(' ')[1];
|
|
61
|
+
if (!token) {
|
|
62
|
+
this.logger.warn(`Conexão rejeitada: Token JWT ausente.`);
|
|
63
|
+
client.disconnect();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const secret = process.env.JWT_SECRET || 'devstroupe-secret-key-12345';
|
|
67
|
+
const decoded = jwt.verify(token, secret);
|
|
68
|
+
client.data = {
|
|
69
|
+
userId: decoded.sub,
|
|
70
|
+
email: decoded.email,
|
|
71
|
+
role: decoded.role,
|
|
72
|
+
tenantId: decoded.tenantId,
|
|
73
|
+
permissions: decoded.permissions || [],
|
|
74
|
+
};
|
|
75
|
+
// Registrar o socket associado ao usuário
|
|
76
|
+
this.connectedUsers.set(String(decoded.sub), client);
|
|
77
|
+
// Entrar nas salas padrão do usuário (sala própria, sala do perfil e do tenant)
|
|
78
|
+
client.join(`user:${decoded.sub}`);
|
|
79
|
+
if (decoded.role) {
|
|
80
|
+
client.join(`role:${decoded.role}`);
|
|
81
|
+
}
|
|
82
|
+
if (decoded.tenantId) {
|
|
83
|
+
client.join(`tenant:${decoded.tenantId}`);
|
|
84
|
+
}
|
|
85
|
+
this.logger.log(`Cliente conectado: User ID ${decoded.sub} (Socket ${client.id})`);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
this.logger.warn(`Conexão rejeitada: Token JWT inválido.`);
|
|
89
|
+
client.disconnect();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
handleDisconnect(client) {
|
|
93
|
+
if (client.data?.userId) {
|
|
94
|
+
this.connectedUsers.delete(String(client.data.userId));
|
|
95
|
+
this.logger.log(`Cliente desconectado: User ID ${client.data.userId} (Socket ${client.id})`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Notifica uma mudança de entidade para todos os inscritos (respeitando o tenant se houver)
|
|
99
|
+
emitEntityChange(entityName, action, data, tenantId) {
|
|
100
|
+
const payload = { entity: entityName, action, data };
|
|
101
|
+
if (tenantId) {
|
|
102
|
+
this.server.to(`tenant:${tenantId}`).emit('entity_changed', payload);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.server.emit('entity_changed', payload);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
handleSubscribeEntity(entity, client) {
|
|
109
|
+
client.join(`entity:${entity}`);
|
|
110
|
+
return { event: 'subscribed', data: entity };
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
exports.RealtimeGateway = RealtimeGateway;
|
|
114
|
+
__decorate([
|
|
115
|
+
(0, websockets_1.WebSocketServer)(),
|
|
116
|
+
__metadata("design:type", socket_io_1.Server)
|
|
117
|
+
], RealtimeGateway.prototype, "server", void 0);
|
|
118
|
+
__decorate([
|
|
119
|
+
(0, websockets_1.SubscribeMessage)('subscribe_entity'),
|
|
120
|
+
__param(0, (0, websockets_1.MessageBody)()),
|
|
121
|
+
__param(1, (0, websockets_1.ConnectedSocket)()),
|
|
122
|
+
__metadata("design:type", Function),
|
|
123
|
+
__metadata("design:paramtypes", [String, socket_io_1.Socket]),
|
|
124
|
+
__metadata("design:returntype", void 0)
|
|
125
|
+
], RealtimeGateway.prototype, "handleSubscribeEntity", null);
|
|
126
|
+
exports.RealtimeGateway = RealtimeGateway = RealtimeGateway_1 = __decorate([
|
|
127
|
+
(0, websockets_1.WebSocketGateway)({
|
|
128
|
+
cors: {
|
|
129
|
+
origin: '*',
|
|
130
|
+
},
|
|
131
|
+
transports: ['websocket'],
|
|
132
|
+
}),
|
|
133
|
+
(0, common_1.Injectable)()
|
|
134
|
+
], RealtimeGateway);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.RealtimeModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const realtime_gateway_1 = require("./realtime.gateway");
|
|
12
|
+
const realtime_service_1 = require("./realtime.service");
|
|
13
|
+
let RealtimeModule = class RealtimeModule {
|
|
14
|
+
};
|
|
15
|
+
exports.RealtimeModule = RealtimeModule;
|
|
16
|
+
exports.RealtimeModule = RealtimeModule = __decorate([
|
|
17
|
+
(0, common_1.Global)(),
|
|
18
|
+
(0, common_1.Module)({
|
|
19
|
+
providers: [realtime_gateway_1.RealtimeGateway, realtime_service_1.RealtimeService],
|
|
20
|
+
exports: [realtime_service_1.RealtimeService],
|
|
21
|
+
})
|
|
22
|
+
], RealtimeModule);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RealtimeGateway } from './realtime.gateway';
|
|
2
|
+
export declare class RealtimeService {
|
|
3
|
+
private readonly gateway;
|
|
4
|
+
constructor(gateway: RealtimeGateway);
|
|
5
|
+
/**
|
|
6
|
+
* Publica uma mudança de entidade no WebSocket.
|
|
7
|
+
* Se chamado a partir de um Controller, isso garante que o evento seja publicado pós-commit da transação.
|
|
8
|
+
*/
|
|
9
|
+
publishEntityChange(entityName: string, action: 'create' | 'update' | 'delete', data: any, tenantId?: string): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.RealtimeService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const realtime_gateway_1 = require("./realtime.gateway");
|
|
15
|
+
let RealtimeService = class RealtimeService {
|
|
16
|
+
gateway;
|
|
17
|
+
constructor(gateway) {
|
|
18
|
+
this.gateway = gateway;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Publica uma mudança de entidade no WebSocket.
|
|
22
|
+
* Se chamado a partir de um Controller, isso garante que o evento seja publicado pós-commit da transação.
|
|
23
|
+
*/
|
|
24
|
+
publishEntityChange(entityName, action, data, tenantId) {
|
|
25
|
+
this.gateway.emitEntityChange(entityName, action, data, tenantId);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.RealtimeService = RealtimeService;
|
|
29
|
+
exports.RealtimeService = RealtimeService = __decorate([
|
|
30
|
+
(0, common_1.Injectable)(),
|
|
31
|
+
__metadata("design:paramtypes", [realtime_gateway_1.RealtimeGateway])
|
|
32
|
+
], RealtimeService);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devstroupe/devkit-nest",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "DevsTroupe Development Kit — NestJS base classes: BaseRepository, BaseCrudController and query parsers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -29,14 +29,18 @@
|
|
|
29
29
|
"main": "dist/index.js",
|
|
30
30
|
"types": "dist/index.d.ts",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@devstroupe/devkit-core": "^1.0.0",
|
|
33
32
|
"@aws-sdk/client-s3": "^3.500.0",
|
|
34
|
-
"@aws-sdk/s3-request-presigner": "^3.500.0"
|
|
33
|
+
"@aws-sdk/s3-request-presigner": "^3.500.0",
|
|
34
|
+
"jsonwebtoken": "^9.0.2",
|
|
35
|
+
"@devstroupe/devkit-core": "1.1.0"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
38
39
|
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
39
40
|
"@nestjs/swagger": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
41
|
+
"@nestjs/websockets": "^10.0.0 || ^11.0.0",
|
|
42
|
+
"socket.io": "^4.7.0",
|
|
43
|
+
"rxjs": "^7.0.0",
|
|
40
44
|
"typeorm": "^0.3.0",
|
|
41
45
|
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
|
42
46
|
},
|
|
@@ -44,7 +48,11 @@
|
|
|
44
48
|
"@nestjs/common": "^10.3.8",
|
|
45
49
|
"@nestjs/core": "^10.3.8",
|
|
46
50
|
"@nestjs/swagger": "^7.3.1",
|
|
51
|
+
"@nestjs/websockets": "^10.3.8",
|
|
52
|
+
"@types/jsonwebtoken": "^9.0.6",
|
|
47
53
|
"@types/node": "^20.11.0",
|
|
54
|
+
"rxjs": "^7.8.1",
|
|
55
|
+
"socket.io": "^4.7.5",
|
|
48
56
|
"typeorm": "^0.3.17",
|
|
49
57
|
"reflect-metadata": "^0.1.13",
|
|
50
58
|
"typescript": "^5.4.5"
|