@devstroupe/devkit-nest 1.0.0 → 1.0.1

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.
@@ -0,0 +1,2 @@
1
+ export * from './permissions.decorator';
2
+ export * from './permissions.guard';
@@ -0,0 +1,18 @@
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("./permissions.decorator"), exports);
18
+ __exportStar(require("./permissions.guard"), exports);
@@ -0,0 +1,2 @@
1
+ export declare const PERMISSIONS_KEY = "devkit-permissions";
2
+ export declare const Permissions: (...permissions: string[]) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Permissions = exports.PERMISSIONS_KEY = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.PERMISSIONS_KEY = 'devkit-permissions';
6
+ const Permissions = (...permissions) => (0, common_1.SetMetadata)(exports.PERMISSIONS_KEY, permissions);
7
+ exports.Permissions = Permissions;
@@ -0,0 +1,7 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ export declare class PermissionsGuard implements CanActivate {
4
+ private reflector;
5
+ constructor(reflector: Reflector);
6
+ canActivate(context: ExecutionContext): boolean;
7
+ }
@@ -0,0 +1,46 @@
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.PermissionsGuard = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const core_1 = require("@nestjs/core");
15
+ const permissions_decorator_1 = require("./permissions.decorator");
16
+ let PermissionsGuard = class PermissionsGuard {
17
+ reflector;
18
+ constructor(reflector) {
19
+ this.reflector = reflector;
20
+ }
21
+ canActivate(context) {
22
+ const requiredPermissions = this.reflector.getAllAndOverride(permissions_decorator_1.PERMISSIONS_KEY, [
23
+ context.getHandler(),
24
+ context.getClass(),
25
+ ]);
26
+ if (!requiredPermissions || requiredPermissions.length === 0) {
27
+ return true;
28
+ }
29
+ const request = context.switchToHttp().getRequest();
30
+ const user = request.user;
31
+ if (!user) {
32
+ return false; // Bloqueia acesso caso não esteja autenticado
33
+ }
34
+ // Atalho para super admin
35
+ if (user.role === 'ADMIN') {
36
+ return true;
37
+ }
38
+ const userPermissions = user.permissions || [];
39
+ return requiredPermissions.every((permission) => userPermissions.includes(permission));
40
+ }
41
+ };
42
+ exports.PermissionsGuard = PermissionsGuard;
43
+ exports.PermissionsGuard = PermissionsGuard = __decorate([
44
+ (0, common_1.Injectable)(),
45
+ __metadata("design:paramtypes", [core_1.Reflector])
46
+ ], PermissionsGuard);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
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 permissions_guard_1 = require("./permissions.guard");
39
+ (0, node_test_1.describe)('PermissionsGuard', () => {
40
+ const createMockContext = (user) => {
41
+ return {
42
+ switchToHttp: () => ({
43
+ getRequest: () => ({ user }),
44
+ }),
45
+ getHandler: () => ({}),
46
+ getClass: () => ({}),
47
+ };
48
+ };
49
+ (0, node_test_1.test)('deve permitir acesso se não houver permissões exigidas', () => {
50
+ const mockReflector = {
51
+ getAllAndOverride: () => undefined,
52
+ };
53
+ const guard = new permissions_guard_1.PermissionsGuard(mockReflector);
54
+ const context = createMockContext({ role: 'USER', permissions: [] });
55
+ const result = guard.canActivate(context);
56
+ assert.strictEqual(result, true);
57
+ });
58
+ (0, node_test_1.test)('deve permitir acesso se o usuário for ADMIN (super admin)', () => {
59
+ const mockReflector = {
60
+ getAllAndOverride: () => ['create:cabin'],
61
+ };
62
+ const guard = new permissions_guard_1.PermissionsGuard(mockReflector);
63
+ const context = createMockContext({ role: 'ADMIN', permissions: [] });
64
+ const result = guard.canActivate(context);
65
+ assert.strictEqual(result, true);
66
+ });
67
+ (0, node_test_1.test)('deve permitir acesso se o usuário tiver todas as permissões exigidas', () => {
68
+ const mockReflector = {
69
+ getAllAndOverride: () => ['read:cabin', 'update:cabin'],
70
+ };
71
+ const guard = new permissions_guard_1.PermissionsGuard(mockReflector);
72
+ const context = createMockContext({
73
+ role: 'USER',
74
+ permissions: ['read:cabin', 'update:cabin', 'delete:cabin'],
75
+ });
76
+ const result = guard.canActivate(context);
77
+ assert.strictEqual(result, true);
78
+ });
79
+ (0, node_test_1.test)('deve negar acesso se o usuário não tiver todas as permissões exigidas', () => {
80
+ const mockReflector = {
81
+ getAllAndOverride: () => ['read:cabin', 'update:cabin'],
82
+ };
83
+ const guard = new permissions_guard_1.PermissionsGuard(mockReflector);
84
+ const context = createMockContext({
85
+ role: 'USER',
86
+ permissions: ['read:cabin'], // Falta 'update:cabin'
87
+ });
88
+ const result = guard.canActivate(context);
89
+ assert.strictEqual(result, false);
90
+ });
91
+ (0, node_test_1.test)('deve negar acesso se não houver usuário autenticado na request', () => {
92
+ const mockReflector = {
93
+ getAllAndOverride: () => ['read:cabin'],
94
+ };
95
+ const guard = new permissions_guard_1.PermissionsGuard(mockReflector);
96
+ const context = createMockContext(undefined);
97
+ const result = guard.canActivate(context);
98
+ assert.strictEqual(result, false);
99
+ });
100
+ });
package/dist/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export * from './database/factory-where';
2
2
  export * from './database/base.repository';
3
3
  export * from './database/typeorm-query.builder';
4
4
  export * from './http/base-crud.controller';
5
+ export * from './storage';
6
+ export * from './auth';
package/dist/index.js CHANGED
@@ -18,3 +18,5 @@ __exportStar(require("./database/factory-where"), exports);
18
18
  __exportStar(require("./database/base.repository"), exports);
19
19
  __exportStar(require("./database/typeorm-query.builder"), exports);
20
20
  __exportStar(require("./http/base-crud.controller"), exports);
21
+ __exportStar(require("./storage"), exports);
22
+ __exportStar(require("./auth"), exports);
@@ -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,5 @@
1
+ export * from './storage.types';
2
+ export * from './storage.service';
3
+ export * from './storage.module';
4
+ export * from './drivers/local.storage';
5
+ export * from './drivers/s3.storage';
@@ -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
+ }
@@ -0,0 +1,81 @@
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 StorageModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.StorageModule = exports.STORAGE_OPTIONS_TOKEN = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const storage_service_1 = require("./storage.service");
13
+ const local_storage_1 = require("./drivers/local.storage");
14
+ const s3_storage_1 = require("./drivers/s3.storage");
15
+ exports.STORAGE_OPTIONS_TOKEN = 'STORAGE_OPTIONS_TOKEN';
16
+ let StorageModule = StorageModule_1 = class StorageModule {
17
+ static register(options) {
18
+ const storageServiceProvider = {
19
+ provide: storage_service_1.StorageService,
20
+ useFactory: () => {
21
+ return this.createServiceInstance(options);
22
+ },
23
+ };
24
+ return {
25
+ module: StorageModule_1,
26
+ providers: [
27
+ { provide: exports.STORAGE_OPTIONS_TOKEN, useValue: options },
28
+ storageServiceProvider,
29
+ ],
30
+ exports: [storage_service_1.StorageService],
31
+ };
32
+ }
33
+ static registerAsync(options) {
34
+ const storageServiceProvider = {
35
+ provide: storage_service_1.StorageService,
36
+ useFactory: async (opts) => {
37
+ return this.createServiceInstance(opts);
38
+ },
39
+ inject: [exports.STORAGE_OPTIONS_TOKEN],
40
+ };
41
+ return {
42
+ module: StorageModule_1,
43
+ imports: [],
44
+ providers: [
45
+ {
46
+ provide: exports.STORAGE_OPTIONS_TOKEN,
47
+ useFactory: options.useFactory,
48
+ inject: options.inject || [],
49
+ },
50
+ storageServiceProvider,
51
+ ],
52
+ exports: [storage_service_1.StorageService],
53
+ };
54
+ }
55
+ static createServiceInstance(options) {
56
+ switch (options.driver) {
57
+ case 'local':
58
+ if (!options.local) {
59
+ throw new Error('Configuração de "local" não fornecida para o driver local.');
60
+ }
61
+ return new local_storage_1.LocalStorageService(options.local);
62
+ case 's3':
63
+ if (!options.s3) {
64
+ throw new Error('Configuração de "s3" não fornecida para o driver s3.');
65
+ }
66
+ return new s3_storage_1.S3StorageService(options.s3, false);
67
+ case 'r2':
68
+ if (!options.r2) {
69
+ throw new Error('Configuração de "r2" não fornecida para o driver r2.');
70
+ }
71
+ return new s3_storage_1.S3StorageService(options.r2, true);
72
+ default:
73
+ throw new Error(`Driver de storage não suportado: ${options.driver}`);
74
+ }
75
+ }
76
+ };
77
+ exports.StorageModule = StorageModule;
78
+ exports.StorageModule = StorageModule = StorageModule_1 = __decorate([
79
+ (0, common_1.Global)(),
80
+ (0, common_1.Module)({})
81
+ ], StorageModule);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,113 @@
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 storage_module_1 = require("./storage.module");
39
+ const storage_service_1 = require("./storage.service");
40
+ const local_storage_1 = require("./drivers/local.storage");
41
+ const s3_storage_1 = require("./drivers/s3.storage");
42
+ (0, node_test_1.describe)('StorageModule', () => {
43
+ (0, node_test_1.test)('deve instanciar LocalStorageService quando configurado driver local', () => {
44
+ const dynamicModule = storage_module_1.StorageModule.register({
45
+ driver: 'local',
46
+ local: {
47
+ storagePath: './tmp-test-module',
48
+ },
49
+ });
50
+ assert.ok(dynamicModule.providers);
51
+ const serviceProvider = dynamicModule.exports?.find(e => e === storage_service_1.StorageService);
52
+ assert.ok(serviceProvider);
53
+ const factoryProvider = dynamicModule.providers?.find((p) => p.provide === storage_service_1.StorageService);
54
+ assert.ok(factoryProvider);
55
+ assert.ok(factoryProvider.useFactory);
56
+ const instance = factoryProvider.useFactory();
57
+ assert.ok(instance instanceof local_storage_1.LocalStorageService);
58
+ });
59
+ (0, node_test_1.test)('deve instanciar S3StorageService quando configurado driver s3', () => {
60
+ const dynamicModule = storage_module_1.StorageModule.register({
61
+ driver: 's3',
62
+ s3: {
63
+ bucket: 'test-bucket',
64
+ accessKeyId: 'key',
65
+ secretAccessKey: 'secret',
66
+ region: 'us-east-1',
67
+ },
68
+ });
69
+ const factoryProvider = dynamicModule.providers?.find((p) => p.provide === storage_service_1.StorageService);
70
+ assert.ok(factoryProvider);
71
+ const instance = factoryProvider.useFactory();
72
+ assert.ok(instance instanceof s3_storage_1.S3StorageService);
73
+ });
74
+ (0, node_test_1.test)('deve instanciar S3StorageService quando configurado driver r2', () => {
75
+ const dynamicModule = storage_module_1.StorageModule.register({
76
+ driver: 'r2',
77
+ r2: {
78
+ bucket: 'test-bucket',
79
+ accountId: 'account-id',
80
+ accessKeyId: 'key',
81
+ secretAccessKey: 'secret',
82
+ },
83
+ });
84
+ const factoryProvider = dynamicModule.providers?.find((p) => p.provide === storage_service_1.StorageService);
85
+ assert.ok(factoryProvider);
86
+ const instance = factoryProvider.useFactory();
87
+ assert.ok(instance instanceof s3_storage_1.S3StorageService);
88
+ });
89
+ (0, node_test_1.test)('deve lançar erro se configurações do driver local não forem fornecidas', () => {
90
+ const dynamicModule = storage_module_1.StorageModule.register({ driver: 'local' });
91
+ const factoryProvider = dynamicModule.providers?.find((p) => p.provide === storage_service_1.StorageService);
92
+ assert.ok(factoryProvider);
93
+ assert.throws(() => {
94
+ factoryProvider.useFactory();
95
+ }, /Configuração de "local" não fornecida/);
96
+ });
97
+ (0, node_test_1.test)('deve lançar erro se configurações do driver s3 não forem fornecidas', () => {
98
+ const dynamicModule = storage_module_1.StorageModule.register({ driver: 's3' });
99
+ const factoryProvider = dynamicModule.providers?.find((p) => p.provide === storage_service_1.StorageService);
100
+ assert.ok(factoryProvider);
101
+ assert.throws(() => {
102
+ factoryProvider.useFactory();
103
+ }, /Configuração de "s3" não fornecida/);
104
+ });
105
+ (0, node_test_1.test)('deve lançar erro se configurações do driver r2 não forem fornecidas', () => {
106
+ const dynamicModule = storage_module_1.StorageModule.register({ driver: 'r2' });
107
+ const factoryProvider = dynamicModule.providers?.find((p) => p.provide === storage_service_1.StorageService);
108
+ assert.ok(factoryProvider);
109
+ assert.throws(() => {
110
+ factoryProvider.useFactory();
111
+ }, /Configuração de "r2" não fornecida/);
112
+ });
113
+ });
@@ -0,0 +1,7 @@
1
+ import { StorageFile, UploadOptions, UploadResult } from './storage.types';
2
+ export declare abstract class StorageService {
3
+ abstract upload(file: StorageFile, options?: UploadOptions): Promise<UploadResult>;
4
+ abstract delete(key: string): Promise<void>;
5
+ abstract getUrl(key: string): Promise<string>;
6
+ abstract getSignedUrl(key: string, expiresIn?: number): Promise<string>;
7
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageService = void 0;
4
+ class StorageService {
5
+ }
6
+ exports.StorageService = StorageService;
@@ -0,0 +1,42 @@
1
+ export type StorageDriver = 'local' | 's3' | 'r2';
2
+ export interface LocalStorageOptions {
3
+ storagePath: string;
4
+ baseUrl?: string;
5
+ }
6
+ export interface S3StorageOptions {
7
+ bucket: string;
8
+ region?: string;
9
+ accessKeyId: string;
10
+ secretAccessKey: string;
11
+ endpoint?: string;
12
+ forcePathStyle?: boolean;
13
+ }
14
+ export interface R2StorageOptions {
15
+ bucket: string;
16
+ accountId: string;
17
+ accessKeyId: string;
18
+ secretAccessKey: string;
19
+ publicUrl?: string;
20
+ }
21
+ export interface StorageOptions {
22
+ driver: StorageDriver;
23
+ local?: LocalStorageOptions;
24
+ s3?: S3StorageOptions;
25
+ r2?: R2StorageOptions;
26
+ }
27
+ export interface StorageFile {
28
+ originalname: string;
29
+ mimetype: string;
30
+ buffer: Buffer;
31
+ size: number;
32
+ }
33
+ export interface UploadOptions {
34
+ path?: string;
35
+ filename?: string;
36
+ }
37
+ export interface UploadResult {
38
+ key: string;
39
+ url: string;
40
+ size: number;
41
+ mimetype: string;
42
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devstroupe/devkit-nest",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "DevsTroupe Development Kit — NestJS base classes: BaseRepository, BaseCrudController and query parsers",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -29,7 +29,9 @@
29
29
  "main": "dist/index.js",
30
30
  "types": "dist/index.d.ts",
31
31
  "dependencies": {
32
- "@devstroupe/devkit-core": "^1.0.0"
32
+ "@devstroupe/devkit-core": "^1.0.0",
33
+ "@aws-sdk/client-s3": "^3.500.0",
34
+ "@aws-sdk/s3-request-presigner": "^3.500.0"
33
35
  },
34
36
  "peerDependencies": {
35
37
  "@nestjs/common": "^10.0.0 || ^11.0.0",