@devstroupe/devkit-nest 1.0.0 → 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/auth/index.d.ts +2 -0
- package/dist/auth/index.js +18 -0
- package/dist/auth/permissions.decorator.d.ts +2 -0
- package/dist/auth/permissions.decorator.js +7 -0
- package/dist/auth/permissions.guard.d.ts +7 -0
- package/dist/auth/permissions.guard.js +46 -0
- package/dist/auth/permissions.guard.test.d.ts +1 -0
- package/dist/auth/permissions.guard.test.js +100 -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 +4 -0
- package/dist/index.js +4 -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/dist/storage/drivers/local.storage.d.ts +11 -0
- package/dist/storage/drivers/local.storage.js +114 -0
- package/dist/storage/drivers/local.storage.test.d.ts +1 -0
- package/dist/storage/drivers/local.storage.test.js +106 -0
- package/dist/storage/drivers/s3.storage.d.ts +12 -0
- package/dist/storage/drivers/s3.storage.js +138 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.js +21 -0
- package/dist/storage/storage.module.d.ts +11 -0
- package/dist/storage/storage.module.js +81 -0
- package/dist/storage/storage.module.test.d.ts +1 -0
- package/dist/storage/storage.module.test.js +113 -0
- package/dist/storage/storage.service.d.ts +7 -0
- package/dist/storage/storage.service.js +6 -0
- package/dist/storage/storage.types.d.ts +42 -0
- package/dist/storage/storage.types.js +2 -0
- package/package.json +12 -2
|
@@ -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);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StorageService } from '../storage.service';
|
|
2
|
+
import { StorageFile, UploadOptions, UploadResult, LocalStorageOptions } from '../storage.types';
|
|
3
|
+
export declare class LocalStorageService implements StorageService {
|
|
4
|
+
private readonly storagePath;
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
constructor(options: LocalStorageOptions);
|
|
7
|
+
upload(file: StorageFile, options?: UploadOptions): Promise<UploadResult>;
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
getUrl(key: string): Promise<string>;
|
|
10
|
+
getSignedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.LocalStorageService = void 0;
|
|
46
|
+
const common_1 = require("@nestjs/common");
|
|
47
|
+
const fs = __importStar(require("fs/promises"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const crypto = __importStar(require("crypto"));
|
|
50
|
+
let LocalStorageService = class LocalStorageService {
|
|
51
|
+
storagePath;
|
|
52
|
+
baseUrl;
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.storagePath = path.resolve(options.storagePath);
|
|
55
|
+
this.baseUrl = options.baseUrl || '';
|
|
56
|
+
}
|
|
57
|
+
async upload(file, options) {
|
|
58
|
+
// 1. Sanitizar a extensão do arquivo
|
|
59
|
+
const origExt = path.extname(file.originalname);
|
|
60
|
+
const sanitizedExt = origExt.replace(/[^a-zA-Z0-9.]/g, '');
|
|
61
|
+
// 2. Determinar nome do arquivo (UUID v4 por padrão)
|
|
62
|
+
const filename = options?.filename
|
|
63
|
+
? options.filename.replace(/[^a-zA-Z0-9._-]/g, '')
|
|
64
|
+
: `${crypto.randomUUID()}${sanitizedExt}`;
|
|
65
|
+
// 3. Resolver caminho da subpasta e mitigar Directory Traversal
|
|
66
|
+
const subPath = options?.path ? path.normalize(options.path).replace(/^(\.\.(\/|\\|$))+/, '') : '';
|
|
67
|
+
const targetDir = path.resolve(this.storagePath, subPath);
|
|
68
|
+
// Adicionamos a barra final para segurança máxima de limite de diretório
|
|
69
|
+
const rootPrefix = this.storagePath.endsWith(path.sep) ? this.storagePath : this.storagePath + path.sep;
|
|
70
|
+
if (!targetDir.startsWith(rootPrefix) && targetDir !== this.storagePath) {
|
|
71
|
+
throw new Error('Directory traversal attempt detected');
|
|
72
|
+
}
|
|
73
|
+
const targetFile = path.resolve(targetDir, filename);
|
|
74
|
+
if (!targetFile.startsWith(rootPrefix)) {
|
|
75
|
+
throw new Error('File path resolves outside of storage boundary');
|
|
76
|
+
}
|
|
77
|
+
// 4. Garantir existência da pasta e escrever o arquivo
|
|
78
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
79
|
+
await fs.writeFile(targetFile, file.buffer);
|
|
80
|
+
const relativeKey = path.relative(this.storagePath, targetFile).replace(/\\/g, '/');
|
|
81
|
+
const url = this.baseUrl ? `${this.baseUrl.replace(/\/$/, '')}/${relativeKey}` : relativeKey;
|
|
82
|
+
return {
|
|
83
|
+
key: relativeKey,
|
|
84
|
+
url,
|
|
85
|
+
size: file.size,
|
|
86
|
+
mimetype: file.mimetype,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async delete(key) {
|
|
90
|
+
const targetFile = path.resolve(this.storagePath, key);
|
|
91
|
+
const rootPrefix = this.storagePath.endsWith(path.sep) ? this.storagePath : this.storagePath + path.sep;
|
|
92
|
+
if (!targetFile.startsWith(rootPrefix)) {
|
|
93
|
+
throw new Error('Directory traversal attempt detected on deletion');
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await fs.unlink(targetFile);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err.code !== 'ENOENT')
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async getUrl(key) {
|
|
104
|
+
return this.baseUrl ? `${this.baseUrl.replace(/\/$/, '')}/${key}` : key;
|
|
105
|
+
}
|
|
106
|
+
async getSignedUrl(key, expiresIn) {
|
|
107
|
+
return this.getUrl(key);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
exports.LocalStorageService = LocalStorageService;
|
|
111
|
+
exports.LocalStorageService = LocalStorageService = __decorate([
|
|
112
|
+
(0, common_1.Injectable)(),
|
|
113
|
+
__metadata("design:paramtypes", [Object])
|
|
114
|
+
], LocalStorageService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
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 __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const local_storage_1 = require("./local.storage");
|
|
41
|
+
(0, node_test_1.describe)('LocalStorageService', () => {
|
|
42
|
+
const tmpDir = path.resolve(__dirname, '../../../../tmp-storage-test');
|
|
43
|
+
let service;
|
|
44
|
+
(0, node_test_1.before)(async () => {
|
|
45
|
+
await fs.mkdir(tmpDir, { recursive: true });
|
|
46
|
+
service = new local_storage_1.LocalStorageService({
|
|
47
|
+
storagePath: tmpDir,
|
|
48
|
+
baseUrl: 'http://localhost:3000/files',
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
(0, node_test_1.after)(async () => {
|
|
52
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
53
|
+
});
|
|
54
|
+
(0, node_test_1.test)('deve realizar upload de arquivo com sucesso', async () => {
|
|
55
|
+
const file = {
|
|
56
|
+
originalname: 'test-image.png',
|
|
57
|
+
mimetype: 'image/png',
|
|
58
|
+
buffer: Buffer.from('dummy content'),
|
|
59
|
+
size: 13,
|
|
60
|
+
};
|
|
61
|
+
const result = await service.upload(file, { path: 'images' });
|
|
62
|
+
assert.ok(result.key);
|
|
63
|
+
assert.ok(result.url.startsWith('http://localhost:3000/files/images/'));
|
|
64
|
+
assert.strictEqual(result.size, 13);
|
|
65
|
+
assert.strictEqual(result.mimetype, 'image/png');
|
|
66
|
+
// Verificar se o arquivo realmente foi criado no sistema de arquivos
|
|
67
|
+
const filePath = path.resolve(tmpDir, result.key);
|
|
68
|
+
const content = await fs.readFile(filePath);
|
|
69
|
+
assert.strictEqual(content.toString(), 'dummy content');
|
|
70
|
+
});
|
|
71
|
+
(0, node_test_1.test)('deve deletar arquivo com sucesso', async () => {
|
|
72
|
+
const file = {
|
|
73
|
+
originalname: 'to-delete.txt',
|
|
74
|
+
mimetype: 'text/plain',
|
|
75
|
+
buffer: Buffer.from('delete me'),
|
|
76
|
+
size: 9,
|
|
77
|
+
};
|
|
78
|
+
const uploadResult = await service.upload(file);
|
|
79
|
+
const filePath = path.resolve(tmpDir, uploadResult.key);
|
|
80
|
+
// Verificar que existe
|
|
81
|
+
await fs.access(filePath);
|
|
82
|
+
// Deletar
|
|
83
|
+
await service.delete(uploadResult.key);
|
|
84
|
+
// Verificar que não existe mais
|
|
85
|
+
await assert.rejects(async () => {
|
|
86
|
+
await fs.access(filePath);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
(0, node_test_1.test)('deve rejeitar upload com Directory Traversal em options.path', async () => {
|
|
90
|
+
const file = {
|
|
91
|
+
originalname: 'hacker.txt',
|
|
92
|
+
mimetype: 'text/plain',
|
|
93
|
+
buffer: Buffer.from('malicious'),
|
|
94
|
+
size: 9,
|
|
95
|
+
};
|
|
96
|
+
// Tenta gravar fora da raiz
|
|
97
|
+
await assert.rejects(async () => {
|
|
98
|
+
await service.upload(file, { path: '../../' });
|
|
99
|
+
}, /Directory traversal attempt detected/);
|
|
100
|
+
});
|
|
101
|
+
(0, node_test_1.test)('deve rejeitar exclusão com Directory Traversal em key', async () => {
|
|
102
|
+
await assert.rejects(async () => {
|
|
103
|
+
await service.delete('../../../etc/passwd');
|
|
104
|
+
}, /Directory traversal attempt detected/);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StorageService } from '../storage.service';
|
|
2
|
+
import { StorageFile, UploadOptions, UploadResult, S3StorageOptions, R2StorageOptions } from '../storage.types';
|
|
3
|
+
export declare class S3StorageService implements StorageService {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly bucket;
|
|
6
|
+
private readonly publicUrl?;
|
|
7
|
+
constructor(options: S3StorageOptions | R2StorageOptions, isR2?: boolean);
|
|
8
|
+
upload(file: StorageFile, options?: UploadOptions): Promise<UploadResult>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
getUrl(key: string): Promise<string>;
|
|
11
|
+
getSignedUrl(key: string, expiresIn?: number): Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.S3StorageService = void 0;
|
|
46
|
+
const common_1 = require("@nestjs/common");
|
|
47
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
48
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
49
|
+
const crypto = __importStar(require("crypto"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
let S3StorageService = class S3StorageService {
|
|
52
|
+
client;
|
|
53
|
+
bucket;
|
|
54
|
+
publicUrl;
|
|
55
|
+
constructor(options, isR2 = false) {
|
|
56
|
+
this.bucket = options.bucket;
|
|
57
|
+
if (isR2) {
|
|
58
|
+
const r2Opts = options;
|
|
59
|
+
this.publicUrl = r2Opts.publicUrl;
|
|
60
|
+
this.client = new client_s3_1.S3Client({
|
|
61
|
+
endpoint: `https://${r2Opts.accountId}.r2.cloudflarestorage.com`,
|
|
62
|
+
region: 'auto',
|
|
63
|
+
credentials: {
|
|
64
|
+
accessKeyId: r2Opts.accessKeyId,
|
|
65
|
+
secretAccessKey: r2Opts.secretAccessKey,
|
|
66
|
+
},
|
|
67
|
+
forcePathStyle: true,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const s3Opts = options;
|
|
72
|
+
this.client = new client_s3_1.S3Client({
|
|
73
|
+
region: s3Opts.region || 'us-east-1',
|
|
74
|
+
credentials: {
|
|
75
|
+
accessKeyId: s3Opts.accessKeyId,
|
|
76
|
+
secretAccessKey: s3Opts.secretAccessKey,
|
|
77
|
+
},
|
|
78
|
+
endpoint: s3Opts.endpoint,
|
|
79
|
+
forcePathStyle: s3Opts.forcePathStyle,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async upload(file, options) {
|
|
84
|
+
const origExt = path.extname(file.originalname);
|
|
85
|
+
const sanitizedExt = origExt.replace(/[^a-zA-Z0-9.]/g, '');
|
|
86
|
+
const filename = options?.filename
|
|
87
|
+
? options.filename.replace(/[^a-zA-Z0-9._-]/g, '')
|
|
88
|
+
: `${crypto.randomUUID()}${sanitizedExt}`;
|
|
89
|
+
const subPath = options?.path ? options.path.replace(/^\/+|\/+$/g, '') : '';
|
|
90
|
+
const key = subPath ? `${subPath}/${filename}` : filename;
|
|
91
|
+
await this.client.send(new client_s3_1.PutObjectCommand({
|
|
92
|
+
Bucket: this.bucket,
|
|
93
|
+
Key: key,
|
|
94
|
+
Body: file.buffer,
|
|
95
|
+
ContentType: file.mimetype,
|
|
96
|
+
}));
|
|
97
|
+
const url = await this.getUrl(key);
|
|
98
|
+
return {
|
|
99
|
+
key,
|
|
100
|
+
url,
|
|
101
|
+
size: file.size,
|
|
102
|
+
mimetype: file.mimetype,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async delete(key) {
|
|
106
|
+
await this.client.send(new client_s3_1.DeleteObjectCommand({
|
|
107
|
+
Bucket: this.bucket,
|
|
108
|
+
Key: key,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
async getUrl(key) {
|
|
112
|
+
if (this.publicUrl) {
|
|
113
|
+
return `${this.publicUrl.replace(/\/$/, '')}/${key}`;
|
|
114
|
+
}
|
|
115
|
+
const endpointFn = this.client.config.endpoint;
|
|
116
|
+
if (endpointFn && typeof endpointFn === 'function') {
|
|
117
|
+
const endpoint = await endpointFn();
|
|
118
|
+
if (endpoint) {
|
|
119
|
+
const portStr = endpoint.port ? `:${endpoint.port}` : '';
|
|
120
|
+
const baseHost = `${endpoint.protocol}//${endpoint.hostname}${portStr}`;
|
|
121
|
+
return `${baseHost}/${this.bucket}/${key}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return `https://${this.bucket}.s3.amazonaws.com/${key}`;
|
|
125
|
+
}
|
|
126
|
+
async getSignedUrl(key, expiresIn = 3600) {
|
|
127
|
+
const command = new client_s3_1.GetObjectCommand({
|
|
128
|
+
Bucket: this.bucket,
|
|
129
|
+
Key: key,
|
|
130
|
+
});
|
|
131
|
+
return (0, s3_request_presigner_1.getSignedUrl)(this.client, command, { expiresIn });
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
exports.S3StorageService = S3StorageService;
|
|
135
|
+
exports.S3StorageService = S3StorageService = __decorate([
|
|
136
|
+
(0, common_1.Injectable)(),
|
|
137
|
+
__metadata("design:paramtypes", [Object, Boolean])
|
|
138
|
+
], S3StorageService);
|
|
@@ -0,0 +1,21 @@
|
|
|
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("./storage.types"), exports);
|
|
18
|
+
__exportStar(require("./storage.service"), exports);
|
|
19
|
+
__exportStar(require("./storage.module"), exports);
|
|
20
|
+
__exportStar(require("./drivers/local.storage"), exports);
|
|
21
|
+
__exportStar(require("./drivers/s3.storage"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import { StorageOptions } from './storage.types';
|
|
3
|
+
export declare const STORAGE_OPTIONS_TOKEN = "STORAGE_OPTIONS_TOKEN";
|
|
4
|
+
export declare class StorageModule {
|
|
5
|
+
static register(options: StorageOptions): DynamicModule;
|
|
6
|
+
static registerAsync(options: {
|
|
7
|
+
useFactory: (...args: any[]) => Promise<StorageOptions> | StorageOptions;
|
|
8
|
+
inject?: any[];
|
|
9
|
+
}): DynamicModule;
|
|
10
|
+
private static createServiceInstance;
|
|
11
|
+
}
|