nestjs-r2-storage 1.3.4 → 1.4.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.
@@ -1,54 +1,54 @@
1
- import { CloudflareService } from "./cloudflare.service";
2
- export interface PhotoField {
3
- field: string;
4
- urlField?: string;
5
- sizeField?: string;
6
- }
7
- export interface PhotoUploadRequest {
8
- field: string;
9
- filename: string;
10
- size: number;
11
- prefix?: string;
12
- }
13
- export interface PhotoUploadResponse {
14
- field: string;
15
- fileKey: string;
16
- uploadUrl: string;
17
- publicUrl: string | null;
18
- filename?: string;
19
- }
20
- export interface CreatePhotosResult<T extends Record<string, any>> {
21
- updatedPayload: T;
22
- uploadUrls: PhotoUploadResponse[];
23
- totalStorageUsed: number;
24
- }
25
- export interface UpdatePhotosResult<T extends Record<string, any>> {
26
- updatedPayload: T;
27
- uploadUrls: PhotoUploadResponse[];
28
- storageIncrease: number;
29
- storageDecrease: number;
30
- deletedFiles: string[];
31
- }
32
- export interface DeletePhotosResult {
33
- deletedFiles: string[];
34
- totalStorageFreed: number;
35
- }
36
- export interface AppendUrlsOptions {
37
- urlField?: (field: string) => string;
38
- }
39
- export declare class PhotoManagerService {
40
- private readonly cloudflareService;
41
- constructor(cloudflareService: CloudflareService);
42
- appendPhotoUrls<T extends Record<string, any>>(payload: T[], photoFields: PhotoField[]): Promise<T[]>;
43
- private handleSingleFieldUrl;
44
- private handleArrayFieldUrls;
45
- createObjectWithPhotos<T extends Record<string, any>>(payload: T, photoFields: PhotoField[], filePrefix?: string): Promise<CreatePhotosResult<T>>;
46
- updateObjectWithPhotos<T extends Record<string, any>>(payload: T, existingObject: T, photoFields: PhotoField[], filePrefix?: string): Promise<UpdatePhotosResult<T>>;
47
- deletePhotosFromObject<T extends Record<string, any>>(object: T, photoFields: PhotoField[]): Promise<DeletePhotosResult>;
48
- private extractPhotoUploadRequests;
49
- private extractArrayFieldUploadRequests;
50
- private extractExistingFiles;
51
- private determineFilesToDelete;
52
- private normalizeFilename;
53
- private updateArrayFieldWithNewFileKey;
54
- }
1
+ import { CloudflareService } from "./cloudflare.service";
2
+ export interface PhotoField {
3
+ field: string;
4
+ urlField?: string;
5
+ sizeField?: string;
6
+ }
7
+ export interface PhotoUploadRequest {
8
+ field: string;
9
+ filename: string;
10
+ size: number;
11
+ prefix?: string;
12
+ }
13
+ export interface PhotoUploadResponse {
14
+ field: string;
15
+ fileKey: string;
16
+ uploadUrl: string;
17
+ publicUrl: string | null;
18
+ filename?: string;
19
+ }
20
+ export interface CreatePhotosResult<T extends Record<string, any>> {
21
+ updatedPayload: T;
22
+ uploadUrls: PhotoUploadResponse[];
23
+ totalStorageUsed: number;
24
+ }
25
+ export interface UpdatePhotosResult<T extends Record<string, any>> {
26
+ updatedPayload: T;
27
+ uploadUrls: PhotoUploadResponse[];
28
+ storageIncrease: number;
29
+ storageDecrease: number;
30
+ deletedFiles: string[];
31
+ }
32
+ export interface DeletePhotosResult {
33
+ deletedFiles: string[];
34
+ totalStorageFreed: number;
35
+ }
36
+ export interface AppendUrlsOptions {
37
+ urlField?: (field: string) => string;
38
+ }
39
+ export declare class PhotoManagerService {
40
+ private readonly cloudflareService;
41
+ constructor(cloudflareService: CloudflareService);
42
+ appendPhotoUrls<T extends Record<string, any>>(payload: T[], photoFields: PhotoField[]): Promise<T[]>;
43
+ private handleSingleFieldUrl;
44
+ private handleArrayFieldUrls;
45
+ createObjectWithPhotos<T extends Record<string, any>>(payload: T, photoFields: PhotoField[], filePrefix?: string): Promise<CreatePhotosResult<T>>;
46
+ updateObjectWithPhotos<T extends Record<string, any>>(payload: T, existingObject: T, photoFields: PhotoField[], filePrefix?: string): Promise<UpdatePhotosResult<T>>;
47
+ deletePhotosFromObject<T extends Record<string, any>>(object: T, photoFields: PhotoField[]): Promise<DeletePhotosResult>;
48
+ private extractPhotoUploadRequests;
49
+ private extractArrayFieldUploadRequests;
50
+ private extractExistingFiles;
51
+ private determineFilesToDelete;
52
+ private normalizeFilename;
53
+ private updateArrayFieldWithNewFileKey;
54
+ }
@@ -1,334 +1,334 @@
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.PhotoManagerService = void 0;
13
- const common_1 = require("@nestjs/common");
14
- const cloudflare_service_1 = require("./cloudflare.service");
15
- const nested_value_util_1 = require("./utils/nested-value.util");
16
- let PhotoManagerService = class PhotoManagerService {
17
- constructor(cloudflareService) {
18
- this.cloudflareService = cloudflareService;
19
- }
20
- async appendPhotoUrls(payload, photoFields, options) {
21
- const isArray = Array.isArray(payload);
22
- const items = isArray ? payload : [payload];
23
- const urlFieldFn = options?.urlField || ((field) => `${field}_url`);
24
- const result = await Promise.all(items.map(async (item) => {
25
- let updatedItem = { ...item };
26
- for (const photoField of photoFields) {
27
- const fieldValue = (0, nested_value_util_1.getNestedValue)(updatedItem, photoField.field);
28
- if (!fieldValue) {
29
- continue;
30
- }
31
- if ((0, nested_value_util_1.isArrayPath)(photoField.field)) {
32
- updatedItem = await this.handleArrayFieldUrls(updatedItem, photoField, urlFieldFn);
33
- }
34
- else {
35
- updatedItem = await this.handleSingleFieldUrl(updatedItem, photoField, urlFieldFn);
36
- }
37
- }
38
- return updatedItem;
39
- }));
40
- return isArray ? result : result[0];
41
- }
42
- async handleSingleFieldUrl(item, photoField, urlFieldFn) {
43
- const fieldValue = (0, nested_value_util_1.getNestedValue)(item, photoField.field);
44
- if (!fieldValue) {
45
- return item;
46
- }
47
- const urlField = photoField.urlField || urlFieldFn(photoField.field);
48
- try {
49
- const { downloadUrl, publicUrl } = await this.cloudflareService.getDownloadUrl(fieldValue);
50
- const finalUrl = publicUrl || downloadUrl;
51
- return (0, nested_value_util_1.setNestedValue)(item, urlField, finalUrl);
52
- }
53
- catch (error) {
54
- console.error(`Failed to generate download URL for ${fieldValue}:`, error);
55
- return (0, nested_value_util_1.setNestedValue)(item, urlField, null);
56
- }
57
- }
58
- async handleArrayFieldUrls(item, photoField, urlFieldFn) {
59
- const { segments } = (0, nested_value_util_1.parseFieldPath)(photoField.field);
60
- const lastArrayIndex = segments.findIndex((s) => s.isArray);
61
- if (lastArrayIndex === -1) {
62
- return item;
63
- }
64
- const arrayPath = segments
65
- .slice(0, lastArrayIndex + 1)
66
- .map((s) => s.key)
67
- .join(".");
68
- const arrayValue = (0, nested_value_util_1.getNestedValue)(item, arrayPath);
69
- if (!arrayValue || !Array.isArray(arrayValue)) {
70
- return item;
71
- }
72
- const urlField = photoField.urlField || urlFieldFn(photoField.field);
73
- const { key: photoKey } = segments[segments.length - 1];
74
- const updatedArray = await Promise.all(arrayValue.map(async (arrayItem) => {
75
- const photoValue = arrayItem[photoKey];
76
- if (!photoValue) {
77
- return { ...arrayItem, [urlField]: null };
78
- }
79
- try {
80
- const { downloadUrl, publicUrl } = await this.cloudflareService.getDownloadUrl(photoValue);
81
- const finalUrl = publicUrl || downloadUrl;
82
- return { ...arrayItem, [urlField]: finalUrl };
83
- }
84
- catch (error) {
85
- console.error(`Failed to generate download URL for ${photoValue}:`, error);
86
- return { ...arrayItem, [urlField]: null };
87
- }
88
- }));
89
- return (0, nested_value_util_1.setNestedValue)(item, arrayPath, updatedArray);
90
- }
91
- async createObjectWithPhotos(payload, photoFields, filePrefix = "uploads") {
92
- let updatedPayload = { ...payload };
93
- const uploadUrls = [];
94
- let totalStorageUsed = 0;
95
- const photoUploadRequests = this.extractPhotoUploadRequests(payload, photoFields, filePrefix);
96
- const uploadResults = await Promise.all(photoUploadRequests.map(async (request) => {
97
- const result = await this.cloudflareService.getUploadUrl(request.filename, request.size, request.filename);
98
- const storageUsed = request.size || 0;
99
- totalStorageUsed += storageUsed;
100
- return {
101
- request,
102
- response: result,
103
- storageUsed,
104
- };
105
- }));
106
- for (const { request, response, storageUsed } of uploadResults) {
107
- uploadUrls.push({
108
- field: request.field,
109
- fileKey: response.fileKey,
110
- uploadUrl: response.uploadUrl,
111
- publicUrl: response.publicUrl,
112
- filename: request.filename,
113
- });
114
- if ((0, nested_value_util_1.isArrayPath)(request.field)) {
115
- updatedPayload = await this.updateArrayFieldWithNewFileKey(updatedPayload, request.field, request.filename, response.fileKey);
116
- }
117
- else {
118
- updatedPayload = (0, nested_value_util_1.setNestedValue)(updatedPayload, request.field, response.fileKey);
119
- }
120
- }
121
- return {
122
- updatedPayload,
123
- uploadUrls,
124
- totalStorageUsed,
125
- };
126
- }
127
- async updateObjectWithPhotos(payload, existingObject, photoFields, filePrefix = "uploads") {
128
- let updatedPayload = { ...payload };
129
- const uploadUrls = [];
130
- let storageIncrease = 0;
131
- let storageDecrease = 0;
132
- const deletedFiles = [];
133
- const existingFiles = this.extractExistingFiles(existingObject, photoFields);
134
- const newPhotoRequests = this.extractPhotoUploadRequests(payload, photoFields, filePrefix);
135
- const filesToDelete = this.determineFilesToDelete(existingFiles, newPhotoRequests);
136
- if (filesToDelete.length > 0) {
137
- const deleteResults = await this.cloudflareService.deleteFiles(filesToDelete);
138
- deletedFiles.push(...deleteResults.success);
139
- for (const fileKey of deleteResults.success) {
140
- const fileInfo = await this.cloudflareService.getFileInfo(fileKey);
141
- if (fileInfo) {
142
- storageDecrease += fileInfo.size;
143
- }
144
- }
145
- }
146
- const uploadResults = await Promise.all(newPhotoRequests.map(async (request) => {
147
- const response = await this.cloudflareService.getUploadUrl(request.filename, request.size, request.filename);
148
- const storageUsed = request.size || 0;
149
- storageIncrease += storageUsed;
150
- return { request, response };
151
- }));
152
- for (const { request, response } of uploadResults) {
153
- uploadUrls.push({
154
- field: request.field,
155
- fileKey: response.fileKey,
156
- uploadUrl: response.uploadUrl,
157
- publicUrl: response.publicUrl,
158
- filename: request.filename,
159
- });
160
- if ((0, nested_value_util_1.isArrayPath)(request.field)) {
161
- updatedPayload = await this.updateArrayFieldWithNewFileKey(updatedPayload, request.field, request.filename, response.fileKey);
162
- }
163
- else {
164
- updatedPayload = (0, nested_value_util_1.setNestedValue)(updatedPayload, request.field, response.fileKey);
165
- }
166
- }
167
- return {
168
- updatedPayload,
169
- uploadUrls,
170
- storageIncrease,
171
- storageDecrease,
172
- deletedFiles,
173
- };
174
- }
175
- async deletePhotosFromObject(object, photoFields) {
176
- const fileKeys = this.extractExistingFiles(object, photoFields);
177
- const deletedFiles = [];
178
- let totalStorageFreed = 0;
179
- if (fileKeys.length === 0) {
180
- return { deletedFiles: [], totalStorageFreed: 0 };
181
- }
182
- const results = await this.cloudflareService.deleteFiles(fileKeys);
183
- deletedFiles.push(...results.success);
184
- for (const fileKey of results.success) {
185
- const fileInfo = await this.cloudflareService.getFileInfo(fileKey);
186
- if (fileInfo) {
187
- totalStorageFreed += fileInfo.size;
188
- }
189
- }
190
- return {
191
- deletedFiles,
192
- totalStorageFreed,
193
- };
194
- }
195
- extractPhotoUploadRequests(payload, photoFields, filePrefix) {
196
- const requests = [];
197
- for (const photoField of photoFields) {
198
- const fieldValue = (0, nested_value_util_1.getNestedValue)(payload, photoField.field);
199
- if (!fieldValue) {
200
- continue;
201
- }
202
- if ((0, nested_value_util_1.isArrayPath)(photoField.field)) {
203
- const arrayRequests = this.extractArrayFieldUploadRequests(payload, photoField, filePrefix);
204
- requests.push(...arrayRequests);
205
- }
206
- else {
207
- const sizeValue = photoField.sizeField
208
- ? (0, nested_value_util_1.getNestedValue)(payload, photoField.sizeField)
209
- : 0;
210
- if (typeof fieldValue === "string" && fieldValue.length > 0) {
211
- requests.push({
212
- field: photoField.field,
213
- filename: fieldValue,
214
- size: sizeValue || 0,
215
- prefix: filePrefix,
216
- });
217
- }
218
- }
219
- }
220
- return requests;
221
- }
222
- extractArrayFieldUploadRequests(payload, photoField, filePrefix) {
223
- const requests = [];
224
- const { segments } = (0, nested_value_util_1.parseFieldPath)(photoField.field);
225
- const lastArrayIndex = segments.findIndex((s) => s.isArray);
226
- if (lastArrayIndex === -1) {
227
- return requests;
228
- }
229
- const arrayPath = segments
230
- .slice(0, lastArrayIndex + 1)
231
- .map((s) => s.key)
232
- .join(".");
233
- const arrayValue = (0, nested_value_util_1.getNestedValue)(payload, arrayPath);
234
- if (!arrayValue || !Array.isArray(arrayValue)) {
235
- return requests;
236
- }
237
- const { key: photoKey } = segments[segments.length - 1];
238
- const sizeKey = photoField.sizeField
239
- ? photoField.sizeField.split(".").pop()
240
- : null;
241
- for (let i = 0; i < arrayValue.length; i++) {
242
- const item = arrayValue[i];
243
- const photoValue = item[photoKey];
244
- if (!photoValue || typeof photoValue !== "string") {
245
- continue;
246
- }
247
- const sizeValue = sizeKey ? item[sizeKey] || 0 : 0;
248
- const fieldPath = `${arrayPath}[${i}].${photoKey}`;
249
- requests.push({
250
- field: fieldPath,
251
- filename: photoValue,
252
- size: sizeValue,
253
- prefix: filePrefix,
254
- });
255
- }
256
- return requests;
257
- }
258
- extractExistingFiles(object, photoFields) {
259
- const fileKeys = [];
260
- for (const photoField of photoFields) {
261
- const fieldValue = (0, nested_value_util_1.getNestedValue)(object, photoField.field);
262
- if (!fieldValue) {
263
- continue;
264
- }
265
- if ((0, nested_value_util_1.isArrayPath)(photoField.field)) {
266
- const { segments } = (0, nested_value_util_1.parseFieldPath)(photoField.field);
267
- const lastArrayIndex = segments.findIndex((s) => s.isArray);
268
- if (lastArrayIndex === -1) {
269
- continue;
270
- }
271
- const arrayPath = segments
272
- .slice(0, lastArrayIndex + 1)
273
- .map((s) => s.key)
274
- .join(".");
275
- const arrayValue = (0, nested_value_util_1.getNestedValue)(object, arrayPath);
276
- if (!arrayValue || !Array.isArray(arrayValue)) {
277
- continue;
278
- }
279
- const { key } = segments[segments.length - 1];
280
- for (const item of arrayValue) {
281
- if (item && item[key] && typeof item[key] === "string") {
282
- fileKeys.push(item[key]);
283
- }
284
- }
285
- }
286
- else if (typeof fieldValue === "string" && fieldValue.length > 0) {
287
- fileKeys.push(fieldValue);
288
- }
289
- }
290
- return fileKeys;
291
- }
292
- determineFilesToDelete(existingFiles, newRequests) {
293
- const newFilenames = new Set(newRequests.map((r) => this.normalizeFilename(r.filename)));
294
- return existingFiles.filter((file) => !newFilenames.has(this.normalizeFilename(file)));
295
- }
296
- normalizeFilename(filename) {
297
- return filename.split("/").pop() || filename;
298
- }
299
- async updateArrayFieldWithNewFileKey(payload, fieldPath, oldFilename, newFileKey) {
300
- const { segments } = (0, nested_value_util_1.parseFieldPath)(fieldPath);
301
- const lastArrayIndex = segments.findIndex((s) => s.isArray);
302
- if (lastArrayIndex === -1) {
303
- return payload;
304
- }
305
- const arrayPath = segments
306
- .slice(0, lastArrayIndex + 1)
307
- .map((s) => s.key)
308
- .join(".");
309
- const { key: photoKey } = segments[segments.length - 1];
310
- const arrayValue = (0, nested_value_util_1.getNestedValue)(payload, arrayPath);
311
- if (!arrayValue || !Array.isArray(arrayValue)) {
312
- return payload;
313
- }
314
- const oldNormalized = this.normalizeFilename(oldFilename);
315
- const updatedArray = arrayValue.map((item) => {
316
- const itemPhotoValue = item[photoKey];
317
- if (!itemPhotoValue) {
318
- return item;
319
- }
320
- const itemNormalized = this.normalizeFilename(itemPhotoValue);
321
- if (itemNormalized === oldNormalized) {
322
- return { ...item, [photoKey]: newFileKey };
323
- }
324
- return item;
325
- });
326
- return (0, nested_value_util_1.setNestedValue)(payload, arrayPath, updatedArray);
327
- }
328
- };
329
- exports.PhotoManagerService = PhotoManagerService;
330
- exports.PhotoManagerService = PhotoManagerService = __decorate([
331
- (0, common_1.Injectable)(),
332
- __metadata("design:paramtypes", [cloudflare_service_1.CloudflareService])
333
- ], PhotoManagerService);
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.PhotoManagerService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const cloudflare_service_1 = require("./cloudflare.service");
15
+ const nested_value_util_1 = require("./utils/nested-value.util");
16
+ let PhotoManagerService = class PhotoManagerService {
17
+ constructor(cloudflareService) {
18
+ this.cloudflareService = cloudflareService;
19
+ }
20
+ async appendPhotoUrls(payload, photoFields, options) {
21
+ const isArray = Array.isArray(payload);
22
+ const items = isArray ? payload : [payload];
23
+ const urlFieldFn = options?.urlField || ((field) => `${field}_url`);
24
+ const result = await Promise.all(items.map(async (item) => {
25
+ let updatedItem = { ...item };
26
+ for (const photoField of photoFields) {
27
+ const fieldValue = (0, nested_value_util_1.getNestedValue)(updatedItem, photoField.field);
28
+ if (!fieldValue) {
29
+ continue;
30
+ }
31
+ if ((0, nested_value_util_1.isArrayPath)(photoField.field)) {
32
+ updatedItem = await this.handleArrayFieldUrls(updatedItem, photoField, urlFieldFn);
33
+ }
34
+ else {
35
+ updatedItem = await this.handleSingleFieldUrl(updatedItem, photoField, urlFieldFn);
36
+ }
37
+ }
38
+ return updatedItem;
39
+ }));
40
+ return isArray ? result : result[0];
41
+ }
42
+ async handleSingleFieldUrl(item, photoField, urlFieldFn) {
43
+ const fieldValue = (0, nested_value_util_1.getNestedValue)(item, photoField.field);
44
+ if (!fieldValue) {
45
+ return item;
46
+ }
47
+ const urlField = photoField.urlField || urlFieldFn(photoField.field);
48
+ try {
49
+ const { downloadUrl, publicUrl } = await this.cloudflareService.getDownloadUrl(fieldValue);
50
+ const finalUrl = publicUrl || downloadUrl;
51
+ return (0, nested_value_util_1.setNestedValue)(item, urlField, finalUrl);
52
+ }
53
+ catch (error) {
54
+ console.error(`Failed to generate download URL for ${fieldValue}:`, error);
55
+ return (0, nested_value_util_1.setNestedValue)(item, urlField, null);
56
+ }
57
+ }
58
+ async handleArrayFieldUrls(item, photoField, urlFieldFn) {
59
+ const { segments } = (0, nested_value_util_1.parseFieldPath)(photoField.field);
60
+ const lastArrayIndex = segments.findIndex((s) => s.isArray);
61
+ if (lastArrayIndex === -1) {
62
+ return item;
63
+ }
64
+ const arrayPath = segments
65
+ .slice(0, lastArrayIndex + 1)
66
+ .map((s) => s.key)
67
+ .join(".");
68
+ const arrayValue = (0, nested_value_util_1.getNestedValue)(item, arrayPath);
69
+ if (!arrayValue || !Array.isArray(arrayValue)) {
70
+ return item;
71
+ }
72
+ const urlField = photoField.urlField || urlFieldFn(photoField.field);
73
+ const { key: photoKey } = segments[segments.length - 1];
74
+ const updatedArray = await Promise.all(arrayValue.map(async (arrayItem) => {
75
+ const photoValue = arrayItem[photoKey];
76
+ if (!photoValue) {
77
+ return { ...arrayItem, [urlField]: null };
78
+ }
79
+ try {
80
+ const { downloadUrl, publicUrl } = await this.cloudflareService.getDownloadUrl(photoValue);
81
+ const finalUrl = publicUrl || downloadUrl;
82
+ return { ...arrayItem, [urlField]: finalUrl };
83
+ }
84
+ catch (error) {
85
+ console.error(`Failed to generate download URL for ${photoValue}:`, error);
86
+ return { ...arrayItem, [urlField]: null };
87
+ }
88
+ }));
89
+ return (0, nested_value_util_1.setNestedValue)(item, arrayPath, updatedArray);
90
+ }
91
+ async createObjectWithPhotos(payload, photoFields, filePrefix = "uploads") {
92
+ let updatedPayload = { ...payload };
93
+ const uploadUrls = [];
94
+ let totalStorageUsed = 0;
95
+ const photoUploadRequests = this.extractPhotoUploadRequests(payload, photoFields, filePrefix);
96
+ const uploadResults = await Promise.all(photoUploadRequests.map(async (request) => {
97
+ const result = await this.cloudflareService.getUploadUrl(request.filename, request.size, request.filename);
98
+ const storageUsed = request.size || 0;
99
+ totalStorageUsed += storageUsed;
100
+ return {
101
+ request,
102
+ response: result,
103
+ storageUsed,
104
+ };
105
+ }));
106
+ for (const { request, response, storageUsed } of uploadResults) {
107
+ uploadUrls.push({
108
+ field: request.field,
109
+ fileKey: response.fileKey,
110
+ uploadUrl: response.uploadUrl,
111
+ publicUrl: response.publicUrl,
112
+ filename: request.filename,
113
+ });
114
+ if ((0, nested_value_util_1.isArrayPath)(request.field)) {
115
+ updatedPayload = await this.updateArrayFieldWithNewFileKey(updatedPayload, request.field, request.filename, response.fileKey);
116
+ }
117
+ else {
118
+ updatedPayload = (0, nested_value_util_1.setNestedValue)(updatedPayload, request.field, response.fileKey);
119
+ }
120
+ }
121
+ return {
122
+ updatedPayload,
123
+ uploadUrls,
124
+ totalStorageUsed,
125
+ };
126
+ }
127
+ async updateObjectWithPhotos(payload, existingObject, photoFields, filePrefix = "uploads") {
128
+ let updatedPayload = { ...payload };
129
+ const uploadUrls = [];
130
+ let storageIncrease = 0;
131
+ let storageDecrease = 0;
132
+ const deletedFiles = [];
133
+ const existingFiles = this.extractExistingFiles(existingObject, photoFields);
134
+ const newPhotoRequests = this.extractPhotoUploadRequests(payload, photoFields, filePrefix);
135
+ const filesToDelete = this.determineFilesToDelete(existingFiles, newPhotoRequests);
136
+ if (filesToDelete.length > 0) {
137
+ const deleteResults = await this.cloudflareService.deleteFiles(filesToDelete);
138
+ deletedFiles.push(...deleteResults.success);
139
+ for (const fileKey of deleteResults.success) {
140
+ const fileInfo = await this.cloudflareService.getFileInfo(fileKey);
141
+ if (fileInfo) {
142
+ storageDecrease += fileInfo.size;
143
+ }
144
+ }
145
+ }
146
+ const uploadResults = await Promise.all(newPhotoRequests.map(async (request) => {
147
+ const response = await this.cloudflareService.getUploadUrl(request.filename, request.size, request.filename);
148
+ const storageUsed = request.size || 0;
149
+ storageIncrease += storageUsed;
150
+ return { request, response };
151
+ }));
152
+ for (const { request, response } of uploadResults) {
153
+ uploadUrls.push({
154
+ field: request.field,
155
+ fileKey: response.fileKey,
156
+ uploadUrl: response.uploadUrl,
157
+ publicUrl: response.publicUrl,
158
+ filename: request.filename,
159
+ });
160
+ if ((0, nested_value_util_1.isArrayPath)(request.field)) {
161
+ updatedPayload = await this.updateArrayFieldWithNewFileKey(updatedPayload, request.field, request.filename, response.fileKey);
162
+ }
163
+ else {
164
+ updatedPayload = (0, nested_value_util_1.setNestedValue)(updatedPayload, request.field, response.fileKey);
165
+ }
166
+ }
167
+ return {
168
+ updatedPayload,
169
+ uploadUrls,
170
+ storageIncrease,
171
+ storageDecrease,
172
+ deletedFiles,
173
+ };
174
+ }
175
+ async deletePhotosFromObject(object, photoFields) {
176
+ const fileKeys = this.extractExistingFiles(object, photoFields);
177
+ const deletedFiles = [];
178
+ let totalStorageFreed = 0;
179
+ if (fileKeys.length === 0) {
180
+ return { deletedFiles: [], totalStorageFreed: 0 };
181
+ }
182
+ const results = await this.cloudflareService.deleteFiles(fileKeys);
183
+ deletedFiles.push(...results.success);
184
+ for (const fileKey of results.success) {
185
+ const fileInfo = await this.cloudflareService.getFileInfo(fileKey);
186
+ if (fileInfo) {
187
+ totalStorageFreed += fileInfo.size;
188
+ }
189
+ }
190
+ return {
191
+ deletedFiles,
192
+ totalStorageFreed,
193
+ };
194
+ }
195
+ extractPhotoUploadRequests(payload, photoFields, filePrefix) {
196
+ const requests = [];
197
+ for (const photoField of photoFields) {
198
+ const fieldValue = (0, nested_value_util_1.getNestedValue)(payload, photoField.field);
199
+ if (!fieldValue) {
200
+ continue;
201
+ }
202
+ if ((0, nested_value_util_1.isArrayPath)(photoField.field)) {
203
+ const arrayRequests = this.extractArrayFieldUploadRequests(payload, photoField, filePrefix);
204
+ requests.push(...arrayRequests);
205
+ }
206
+ else {
207
+ const sizeValue = photoField.sizeField
208
+ ? (0, nested_value_util_1.getNestedValue)(payload, photoField.sizeField)
209
+ : 0;
210
+ if (typeof fieldValue === "string" && fieldValue.length > 0) {
211
+ requests.push({
212
+ field: photoField.field,
213
+ filename: fieldValue,
214
+ size: sizeValue || 0,
215
+ prefix: filePrefix,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ return requests;
221
+ }
222
+ extractArrayFieldUploadRequests(payload, photoField, filePrefix) {
223
+ const requests = [];
224
+ const { segments } = (0, nested_value_util_1.parseFieldPath)(photoField.field);
225
+ const lastArrayIndex = segments.findIndex((s) => s.isArray);
226
+ if (lastArrayIndex === -1) {
227
+ return requests;
228
+ }
229
+ const arrayPath = segments
230
+ .slice(0, lastArrayIndex + 1)
231
+ .map((s) => s.key)
232
+ .join(".");
233
+ const arrayValue = (0, nested_value_util_1.getNestedValue)(payload, arrayPath);
234
+ if (!arrayValue || !Array.isArray(arrayValue)) {
235
+ return requests;
236
+ }
237
+ const { key: photoKey } = segments[segments.length - 1];
238
+ const sizeKey = photoField.sizeField
239
+ ? photoField.sizeField.split(".").pop()
240
+ : null;
241
+ for (let i = 0; i < arrayValue.length; i++) {
242
+ const item = arrayValue[i];
243
+ const photoValue = item[photoKey];
244
+ if (!photoValue || typeof photoValue !== "string") {
245
+ continue;
246
+ }
247
+ const sizeValue = sizeKey ? item[sizeKey] || 0 : 0;
248
+ const fieldPath = `${arrayPath}[${i}].${photoKey}`;
249
+ requests.push({
250
+ field: fieldPath,
251
+ filename: photoValue,
252
+ size: sizeValue,
253
+ prefix: filePrefix,
254
+ });
255
+ }
256
+ return requests;
257
+ }
258
+ extractExistingFiles(object, photoFields) {
259
+ const fileKeys = [];
260
+ for (const photoField of photoFields) {
261
+ const fieldValue = (0, nested_value_util_1.getNestedValue)(object, photoField.field);
262
+ if (!fieldValue) {
263
+ continue;
264
+ }
265
+ if ((0, nested_value_util_1.isArrayPath)(photoField.field)) {
266
+ const { segments } = (0, nested_value_util_1.parseFieldPath)(photoField.field);
267
+ const lastArrayIndex = segments.findIndex((s) => s.isArray);
268
+ if (lastArrayIndex === -1) {
269
+ continue;
270
+ }
271
+ const arrayPath = segments
272
+ .slice(0, lastArrayIndex + 1)
273
+ .map((s) => s.key)
274
+ .join(".");
275
+ const arrayValue = (0, nested_value_util_1.getNestedValue)(object, arrayPath);
276
+ if (!arrayValue || !Array.isArray(arrayValue)) {
277
+ continue;
278
+ }
279
+ const { key } = segments[segments.length - 1];
280
+ for (const item of arrayValue) {
281
+ if (item && item[key] && typeof item[key] === "string") {
282
+ fileKeys.push(item[key]);
283
+ }
284
+ }
285
+ }
286
+ else if (typeof fieldValue === "string" && fieldValue.length > 0) {
287
+ fileKeys.push(fieldValue);
288
+ }
289
+ }
290
+ return fileKeys;
291
+ }
292
+ determineFilesToDelete(existingFiles, newRequests) {
293
+ const newFilenames = new Set(newRequests.map((r) => this.normalizeFilename(r.filename)));
294
+ return existingFiles.filter((file) => !newFilenames.has(this.normalizeFilename(file)));
295
+ }
296
+ normalizeFilename(filename) {
297
+ return filename.split("/").pop() || filename;
298
+ }
299
+ async updateArrayFieldWithNewFileKey(payload, fieldPath, oldFilename, newFileKey) {
300
+ const { segments } = (0, nested_value_util_1.parseFieldPath)(fieldPath);
301
+ const lastArrayIndex = segments.findIndex((s) => s.isArray);
302
+ if (lastArrayIndex === -1) {
303
+ return payload;
304
+ }
305
+ const arrayPath = segments
306
+ .slice(0, lastArrayIndex + 1)
307
+ .map((s) => s.key)
308
+ .join(".");
309
+ const { key: photoKey } = segments[segments.length - 1];
310
+ const arrayValue = (0, nested_value_util_1.getNestedValue)(payload, arrayPath);
311
+ if (!arrayValue || !Array.isArray(arrayValue)) {
312
+ return payload;
313
+ }
314
+ const oldNormalized = this.normalizeFilename(oldFilename);
315
+ const updatedArray = arrayValue.map((item) => {
316
+ const itemPhotoValue = item[photoKey];
317
+ if (!itemPhotoValue) {
318
+ return item;
319
+ }
320
+ const itemNormalized = this.normalizeFilename(itemPhotoValue);
321
+ if (itemNormalized === oldNormalized) {
322
+ return { ...item, [photoKey]: newFileKey };
323
+ }
324
+ return item;
325
+ });
326
+ return (0, nested_value_util_1.setNestedValue)(payload, arrayPath, updatedArray);
327
+ }
328
+ };
329
+ exports.PhotoManagerService = PhotoManagerService;
330
+ exports.PhotoManagerService = PhotoManagerService = __decorate([
331
+ (0, common_1.Injectable)(),
332
+ __metadata("design:paramtypes", [cloudflare_service_1.CloudflareService])
333
+ ], PhotoManagerService);
334
334
  //# sourceMappingURL=photo-manager.service.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-r2-storage",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "Production-ready NestJS module for Cloudflare R2 object storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",