@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)(bucketPrefix, fileType.code, uniqueId, file.originalname);
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.deleteObject(bucketName, file.path);
86
- yield this.fileRepository.softDelete(file.id);
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: bucketPath,
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.fileTypeRepository.findOne({ where: { code: fileTypeCode } }),
115
- this.fileTypeRepository.findOne({ where: { id: fileTypeId } }),
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.fileTypeRepository.findOne({ where: { code: fileTypeCode } });
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.fileTypeRepository.findOne({ where: { id: fileTypeId } });
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.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
+ }