@uniorganization/uni-lib 4.1.1 → 4.1.2
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.
|
@@ -27,6 +27,6 @@ exports.CommonModule = CommonModule = __decorate([
|
|
|
27
27
|
}),
|
|
28
28
|
],
|
|
29
29
|
providers: [jwt_strategy_1.JwtStrategy, jwt_auth_guard_1.JwtAuthGuard],
|
|
30
|
-
exports: [jwt_auth_guard_1.JwtAuthGuard, passport_1.PassportModule, jwt_1.JwtModule, tenant_1.TenantModule],
|
|
30
|
+
exports: [jwt_auth_guard_1.JwtAuthGuard, jwt_strategy_1.JwtStrategy, passport_1.PassportModule, jwt_1.JwtModule, tenant_1.TenantModule],
|
|
31
31
|
})
|
|
32
32
|
], CommonModule);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Repository } from 'typeorm';
|
|
2
2
|
import { FileEntity } from '../../entities/file.entity';
|
|
3
3
|
import { FileTypeEntity } from '../../entities/file-type.entity';
|
|
4
|
+
import { TenantContext } from '../tenant/tenant-context.service';
|
|
4
5
|
export interface FileStorageUploadOptions {
|
|
5
6
|
fileTypeCode?: string;
|
|
6
7
|
fileTypeId?: number;
|
|
@@ -16,13 +17,16 @@ export interface FileStorageUploadFile {
|
|
|
16
17
|
export declare class FileStorageService {
|
|
17
18
|
private readonly fileRepository;
|
|
18
19
|
private readonly fileTypeRepository;
|
|
20
|
+
private readonly tenantContext?;
|
|
19
21
|
private readonly logger;
|
|
20
22
|
private readonly s3Client;
|
|
21
|
-
constructor(fileRepository: Repository<FileEntity>, fileTypeRepository: Repository<FileTypeEntity
|
|
23
|
+
constructor(fileRepository: Repository<FileEntity>, fileTypeRepository: Repository<FileTypeEntity>, tenantContext?: TenantContext);
|
|
22
24
|
uploadDocument(file: FileStorageUploadFile, options: FileStorageUploadOptions): Promise<FileEntity>;
|
|
23
25
|
deleteDocument(file: Pick<FileEntity, 'id' | 'path'>): Promise<void>;
|
|
24
26
|
downloadDocument(bucketPath: string): Promise<Buffer>;
|
|
25
27
|
resolveFileType(fileTypeCode?: string, fileTypeId?: number): Promise<FileTypeEntity>;
|
|
28
|
+
private findFileType;
|
|
29
|
+
private getClientId;
|
|
26
30
|
private createS3Client;
|
|
27
31
|
private putObject;
|
|
28
32
|
private deleteObject;
|
|
@@ -37,11 +37,13 @@ const typeorm_2 = require("typeorm");
|
|
|
37
37
|
const node_crypto_1 = require("node:crypto");
|
|
38
38
|
const file_entity_1 = require("../../entities/file.entity");
|
|
39
39
|
const file_type_entity_1 = require("../../entities/file-type.entity");
|
|
40
|
+
const tenant_context_service_1 = require("../tenant/tenant-context.service");
|
|
40
41
|
const constants_1 = require("./constants");
|
|
41
42
|
let FileStorageService = FileStorageService_1 = class FileStorageService {
|
|
42
|
-
constructor(fileRepository, fileTypeRepository) {
|
|
43
|
+
constructor(fileRepository, fileTypeRepository, tenantContext) {
|
|
43
44
|
this.fileRepository = fileRepository;
|
|
44
45
|
this.fileTypeRepository = fileTypeRepository;
|
|
46
|
+
this.tenantContext = tenantContext;
|
|
45
47
|
this.logger = new common_1.Logger(FileStorageService_1.name);
|
|
46
48
|
this.s3Client = this.createS3Client();
|
|
47
49
|
}
|
|
@@ -58,11 +60,16 @@ let FileStorageService = FileStorageService_1 = class FileStorageService {
|
|
|
58
60
|
const extension = (0, constants_1.extractFileExtension)(file.originalname);
|
|
59
61
|
const uniqueId = (0, node_crypto_1.randomUUID)();
|
|
60
62
|
const bucketPrefix = process.env[constants_1.FILE_STORAGE_BUCKET_PREFIX_ENV];
|
|
63
|
+
const clientId = this.getClientId();
|
|
64
|
+
const tenantBucketPrefix = [bucketPrefix, `client-${clientId}`]
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join('/');
|
|
61
67
|
const bucketName = this.getRequiredEnv(constants_1.FILE_STORAGE_BUCKET_NAME_ENV);
|
|
62
|
-
const bucketPath = (0, constants_1.buildBucketPath)(
|
|
68
|
+
const bucketPath = (0, constants_1.buildBucketPath)(tenantBucketPrefix, fileType.code, uniqueId, file.originalname);
|
|
63
69
|
yield this.putObject(bucketName, bucketPath, file);
|
|
64
70
|
try {
|
|
65
71
|
const fileRecord = this.fileRepository.create({
|
|
72
|
+
clientId,
|
|
66
73
|
name: file.originalname,
|
|
67
74
|
typeId: fileType.id,
|
|
68
75
|
path: bucketPath,
|
|
@@ -82,17 +89,38 @@ let FileStorageService = FileStorageService_1 = class FileStorageService {
|
|
|
82
89
|
deleteDocument(file) {
|
|
83
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
84
91
|
const bucketName = this.getRequiredEnv(constants_1.FILE_STORAGE_BUCKET_NAME_ENV);
|
|
85
|
-
yield this.
|
|
86
|
-
|
|
92
|
+
const ownedFile = yield this.fileRepository.findOne({
|
|
93
|
+
where: {
|
|
94
|
+
id: file.id,
|
|
95
|
+
clientId: this.getClientId(),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (!ownedFile) {
|
|
99
|
+
throw new Error('File not found');
|
|
100
|
+
}
|
|
101
|
+
yield this.deleteObject(bucketName, ownedFile.path);
|
|
102
|
+
yield this.fileRepository.softDelete({
|
|
103
|
+
id: ownedFile.id,
|
|
104
|
+
clientId: this.getClientId(),
|
|
105
|
+
});
|
|
87
106
|
});
|
|
88
107
|
}
|
|
89
108
|
downloadDocument(bucketPath) {
|
|
90
109
|
return __awaiter(this, void 0, void 0, function* () {
|
|
91
110
|
const bucketName = this.getRequiredEnv(constants_1.FILE_STORAGE_BUCKET_NAME_ENV);
|
|
111
|
+
const ownedFile = yield this.fileRepository.findOne({
|
|
112
|
+
where: {
|
|
113
|
+
path: bucketPath,
|
|
114
|
+
clientId: this.getClientId(),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (!ownedFile) {
|
|
118
|
+
throw new Error('File not found');
|
|
119
|
+
}
|
|
92
120
|
try {
|
|
93
121
|
const response = yield this.s3Client.send(new client_s3_1.GetObjectCommand({
|
|
94
122
|
Bucket: bucketName,
|
|
95
|
-
Key:
|
|
123
|
+
Key: ownedFile.path,
|
|
96
124
|
}));
|
|
97
125
|
if (!response.Body) {
|
|
98
126
|
throw new Error(`Empty body returned for bucket path "${bucketPath}"`);
|
|
@@ -111,8 +139,8 @@ let FileStorageService = FileStorageService_1 = class FileStorageService {
|
|
|
111
139
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
140
|
if (fileTypeCode && typeof fileTypeId === 'number') {
|
|
113
141
|
const [byCode, byId] = yield Promise.all([
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
142
|
+
this.findFileType({ code: fileTypeCode }),
|
|
143
|
+
this.findFileType({ id: fileTypeId }),
|
|
116
144
|
]);
|
|
117
145
|
if (!byCode) {
|
|
118
146
|
throw new Error(`File type not found for code "${fileTypeCode}"`);
|
|
@@ -126,14 +154,14 @@ let FileStorageService = FileStorageService_1 = class FileStorageService {
|
|
|
126
154
|
return byCode;
|
|
127
155
|
}
|
|
128
156
|
if (fileTypeCode) {
|
|
129
|
-
const fileType = yield this.
|
|
157
|
+
const fileType = yield this.findFileType({ code: fileTypeCode });
|
|
130
158
|
if (!fileType) {
|
|
131
159
|
throw new Error(`File type not found for code "${fileTypeCode}"`);
|
|
132
160
|
}
|
|
133
161
|
return fileType;
|
|
134
162
|
}
|
|
135
163
|
if (typeof fileTypeId === 'number') {
|
|
136
|
-
const fileType = yield this.
|
|
164
|
+
const fileType = yield this.findFileType({ id: fileTypeId });
|
|
137
165
|
if (!fileType) {
|
|
138
166
|
throw new Error(`File type not found for id "${fileTypeId}"`);
|
|
139
167
|
}
|
|
@@ -142,6 +170,30 @@ let FileStorageService = FileStorageService_1 = class FileStorageService {
|
|
|
142
170
|
throw new Error('Either fileTypeCode or fileTypeId must be provided');
|
|
143
171
|
});
|
|
144
172
|
}
|
|
173
|
+
findFileType(options) {
|
|
174
|
+
const clientId = this.getClientId();
|
|
175
|
+
const query = this.fileTypeRepository
|
|
176
|
+
.createQueryBuilder('fileType')
|
|
177
|
+
.where('(fileType.client_id = :clientId OR fileType.client_id IS NULL)', {
|
|
178
|
+
clientId,
|
|
179
|
+
})
|
|
180
|
+
.andWhere('fileType.deletedDate IS NULL')
|
|
181
|
+
.orderBy('CASE WHEN fileType.client_id = :clientId THEN 0 ELSE 1 END', 'ASC')
|
|
182
|
+
.setParameter('clientId', clientId);
|
|
183
|
+
if (options.code) {
|
|
184
|
+
query.andWhere('fileType.code = :code', { code: options.code });
|
|
185
|
+
}
|
|
186
|
+
if (typeof options.id === 'number') {
|
|
187
|
+
query.andWhere('fileType.id = :id', { id: options.id });
|
|
188
|
+
}
|
|
189
|
+
return query.getOne();
|
|
190
|
+
}
|
|
191
|
+
getClientId() {
|
|
192
|
+
if (!this.tenantContext) {
|
|
193
|
+
throw new Error('File storage requires an active tenant context');
|
|
194
|
+
}
|
|
195
|
+
return this.tenantContext.getClientId();
|
|
196
|
+
}
|
|
145
197
|
createS3Client() {
|
|
146
198
|
const region = this.getRequiredEnv(constants_1.FILE_STORAGE_BUCKET_REGION_ENV);
|
|
147
199
|
const accessKeyId = this.getRequiredEnv(constants_1.FILE_STORAGE_BUCKET_ACCESS_KEY_ID_ENV);
|
|
@@ -229,6 +281,8 @@ exports.FileStorageService = FileStorageService = FileStorageService_1 = __decor
|
|
|
229
281
|
(0, common_1.Injectable)(),
|
|
230
282
|
__param(0, (0, typeorm_1.InjectRepository)(file_entity_1.FileEntity)),
|
|
231
283
|
__param(1, (0, typeorm_1.InjectRepository)(file_type_entity_1.FileTypeEntity)),
|
|
284
|
+
__param(2, (0, common_1.Optional)()),
|
|
232
285
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
233
|
-
typeorm_2.Repository
|
|
286
|
+
typeorm_2.Repository,
|
|
287
|
+
tenant_context_service_1.TenantContext])
|
|
234
288
|
], FileStorageService);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniorganization/uni-lib",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "UNI Library",
|
|
5
5
|
"author": "Jhomiguel",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
"dist/**/*",
|
|
10
10
|
"*.md"
|
|
11
11
|
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start:dev": "tsc -w",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepublishOnly": "pnpm run build",
|
|
16
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
17
|
+
"lint": "tslint -p tsconfig.json -c tslint.json",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch",
|
|
20
|
+
"test:cov": "jest --coverage",
|
|
21
|
+
"test:e2e": "jest --config ./test/jest-e2e.json"
|
|
22
|
+
},
|
|
12
23
|
"dependencies": {
|
|
13
24
|
"@googlemaps/google-maps-services-js": "^3.4.2",
|
|
14
25
|
"@aws-sdk/client-s3": "^3.833.0",
|
|
@@ -71,15 +82,5 @@
|
|
|
71
82
|
},
|
|
72
83
|
"coverageDirectory": "../coverage",
|
|
73
84
|
"testEnvironment": "node"
|
|
74
|
-
},
|
|
75
|
-
"scripts": {
|
|
76
|
-
"start:dev": "tsc -w",
|
|
77
|
-
"build": "tsc",
|
|
78
|
-
"format": "prettier --write \"src/**/*.ts\"",
|
|
79
|
-
"lint": "tslint -p tsconfig.json -c tslint.json",
|
|
80
|
-
"test": "jest",
|
|
81
|
-
"test:watch": "jest --watch",
|
|
82
|
-
"test:cov": "jest --coverage",
|
|
83
|
-
"test:e2e": "jest --config ./test/jest-e2e.json"
|
|
84
85
|
}
|
|
85
|
-
}
|
|
86
|
+
}
|