nestjs-r2-storage 1.0.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/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # nestjs-r2-storage
2
+
3
+ Production-ready NestJS module for Cloudflare R2 object storage management.
4
+
5
+ ## Features
6
+
7
+ - **Signed Upload URLs** - Generate presigned URLs for direct file uploads
8
+ - **Signed Download URLs** - Generate presigned URLs for secure file downloads
9
+ - **File Deletion** - Delete files from R2 storage
10
+ - **Nested Field Support** - Handle paths like `shop.logo`, `profile.avatar`
11
+ - **Array Field Support** - Handle paths like `products[].image`, `gallery[].photo`
12
+ - **Storage Usage Tracking** - Track storage used, increased, and decreased
13
+ - **Full CRUD Lifecycle** - Create, Update, Delete file operations
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install nestjs-r2-storage
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Configure the Module
24
+
25
+ ```typescript
26
+ // app.module.ts
27
+ import { Module } from '@nestjs/common';
28
+ import { R2StorageModule } from 'nestjs-r2-storage';
29
+
30
+ @Module({
31
+ imports: [
32
+ R2StorageModule.forRoot({
33
+ endpoint: process.env.R2_ENDPOINT,
34
+ accessKeyId: process.env.R2_ACCESS_KEY,
35
+ secretAccessKey: process.env.R2_SECRET_KEY,
36
+ bucketName: process.env.R2_BUCKET,
37
+ region: 'auto',
38
+ publicUrlBase: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}`,
39
+ signedUrlExpiry: 3600,
40
+ }),
41
+ ],
42
+ })
43
+ export class AppModule {}
44
+ ```
45
+
46
+ ### 2. Use in Your Service
47
+
48
+ ```typescript
49
+ import { Injectable } from '@nestjs/common';
50
+ import { PhotoManagerService, PhotoField, CloudflareService } from 'nestjs-r2-storage';
51
+
52
+ @Injectable()
53
+ export class ProductService {
54
+ constructor(
55
+ private readonly photoManager: PhotoManagerService,
56
+ private readonly cloudflare: CloudflareService,
57
+ ) {}
58
+
59
+ async createProduct(payload: any) {
60
+ const photoFields: PhotoField[] = [
61
+ { field: 'image', urlField: 'image_url', sizeField: 'image_size' },
62
+ { field: 'gallery[].photo', urlField: 'photo_url', sizeField: 'photo_size' },
63
+ ];
64
+
65
+ const result = await this.photoManager.createObjectWithPhotos(payload, photoFields);
66
+
67
+ // Return upload URLs to client for direct upload
68
+ return {
69
+ product: result.updatedPayload,
70
+ uploadUrls: result.uploadUrls,
71
+ totalStorageUsed: result.totalStorageUsed,
72
+ };
73
+ }
74
+
75
+ async getProduct(id: string) {
76
+ const product = await this.findProduct(id);
77
+
78
+ const photoFields: PhotoField[] = [
79
+ { field: 'image', urlField: 'image_url' },
80
+ { field: 'gallery[].photo', urlField: 'photo_url' },
81
+ ];
82
+
83
+ return this.photoManager.appendPhotoUrls(product, photoFields);
84
+ }
85
+
86
+ async updateProduct(id: string, payload: any) {
87
+ const existing = await this.findProduct(id);
88
+
89
+ const photoFields: PhotoField[] = [
90
+ { field: 'image', urlField: 'image_url', sizeField: 'image_size' },
91
+ ];
92
+
93
+ const result = await this.photoManager.updateObjectWithPhotos(payload, existing, photoFields);
94
+
95
+ return {
96
+ product: result.updatedPayload,
97
+ uploadUrls: result.uploadUrls,
98
+ storageIncrease: result.storageIncrease,
99
+ storageDecrease: result.storageDecrease,
100
+ };
101
+ }
102
+
103
+ async deleteProduct(id: string) {
104
+ const product = await this.findProduct(id);
105
+
106
+ const photoFields: PhotoField[] = [
107
+ { field: 'image', urlField: 'image_url' },
108
+ ];
109
+
110
+ await this.photoManager.deletePhotosFromObject(product, photoFields);
111
+ await this.removeProduct(id);
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### CloudflareService
119
+
120
+ Direct R2 operations.
121
+
122
+ ```typescript
123
+ // Generate upload URL
124
+ const uploadUrl = await cloudflare.getUploadUrl('avatar.png', 1024000);
125
+
126
+ // Generate download URL
127
+ const downloadUrl = await cloudflare.getDownloadUrl('uploads/avatar_123.png');
128
+
129
+ // Delete file
130
+ await cloudflare.deleteFile('uploads/avatar.png');
131
+
132
+ // Check if file exists
133
+ const exists = await cloudflare.fileExists('uploads/avatar.png');
134
+ ```
135
+
136
+ ### PhotoManagerService
137
+
138
+ High-level photo management.
139
+
140
+ #### appendPhotoUrls()
141
+
142
+ Adds signed URLs to response objects.
143
+
144
+ ```typescript
145
+ const photoFields: PhotoField[] = [
146
+ { field: 'avatar', urlField: 'avatar_url' },
147
+ { field: 'shop.logo', urlField: 'logo_url' },
148
+ { field: 'products[].image', urlField: 'image_url' },
149
+ { field: 'gallery[].photo', urlField: 'photo_url' },
150
+ ];
151
+
152
+ const result = await photoManager.appendPhotoUrls(product, photoFields);
153
+ ```
154
+
155
+ Input:
156
+ ```json
157
+ {
158
+ "name": "Laptop",
159
+ "image": "laptop.png",
160
+ "gallery": [
161
+ { "photo": "photo1.jpg" },
162
+ { "photo": "photo2.jpg" }
163
+ ]
164
+ }
165
+ ```
166
+
167
+ Output:
168
+ ```json
169
+ {
170
+ "name": "Laptop",
171
+ "image": "laptop.png",
172
+ "image_url": "https://signed-url...",
173
+ "gallery": [
174
+ { "photo": "photo1.jpg", "photo_url": "https://signed-url..." },
175
+ { "photo": "photo2.jpg", "photo_url": "https://signed-url..." }
176
+ ]
177
+ }
178
+ ```
179
+
180
+ #### createObjectWithPhotos()
181
+
182
+ Creates object with photo upload URLs.
183
+
184
+ ```typescript
185
+ const payload = {
186
+ name: "Laptop",
187
+ image: "laptop.png",
188
+ image_size: 42000,
189
+ gallery: [
190
+ { photo: "photo1.jpg", photo_size: 10000 },
191
+ { photo: "photo2.jpg", photo_size: 15000 }
192
+ ]
193
+ };
194
+
195
+ const photoFields: PhotoField[] = [
196
+ { field: 'image', sizeField: 'image_size' },
197
+ { field: 'gallery[].photo', sizeField: 'gallery[].photo_size' },
198
+ ];
199
+
200
+ const result = await photoManager.createObjectWithPhotos(payload, photoFields);
201
+
202
+ // result = {
203
+ // updatedPayload: { ...with generated file keys... },
204
+ // uploadUrls: [{ field, fileKey, uploadUrl, publicUrl }],
205
+ // totalStorageUsed: 67000
206
+ // }
207
+ ```
208
+
209
+ #### updateObjectWithPhotos()
210
+
211
+ Updates object with new photos, deletes old files.
212
+
213
+ ```typescript
214
+ const result = await photoManager.updateObjectWithPhotos(
215
+ newPayload,
216
+ existingObject,
217
+ photoFields,
218
+ );
219
+
220
+ // result = {
221
+ // updatedPayload: { ... },
222
+ // uploadUrls: [{ field, fileKey, uploadUrl, publicUrl }],
223
+ // storageIncrease: 1000,
224
+ // storageDecrease: 500,
225
+ // deletedFiles: ['old-file.png']
226
+ // }
227
+ ```
228
+
229
+ #### deletePhotosFromObject()
230
+
231
+ Deletes all photos from object.
232
+
233
+ ```typescript
234
+ const result = await photoManager.deletePhotosFromObject(product, photoFields);
235
+
236
+ // result = {
237
+ // deletedFiles: ['file1.png', 'file2.jpg'],
238
+ // totalStorageFreed: 25000
239
+ // }
240
+ ```
241
+
242
+ ## Field Path Syntax
243
+
244
+ ### Simple Nested Fields
245
+
246
+ ```
247
+ shop.logo
248
+ profile.avatar
249
+ products[].image
250
+ ```
251
+
252
+ ### Array Fields
253
+
254
+ ```
255
+ gallery[].photo -> gallery[0].photo, gallery[1].photo, ...
256
+ products[].images[] -> products[0].images[0], products[0].images[1], ...
257
+ ```
258
+
259
+ ## Configuration Options
260
+
261
+ | Option | Type | Required | Description |
262
+ |--------|------|----------|-------------|
263
+ | `endpoint` | string | Yes | R2 endpoint URL |
264
+ | `accessKeyId` | string | Yes | R2 access key ID |
265
+ | `secretAccessKey` | string | Yes | R2 secret access key |
266
+ | `bucketName` | string | Yes | R2 bucket name |
267
+ | `region` | string | No | AWS region (default: 'auto') |
268
+ | `publicUrlBase` | string | No | Base URL for public access |
269
+ | `signedUrlExpiry` | number | No | Signed URL expiry in seconds (default: 3600) |
270
+
271
+ ## Async Configuration
272
+
273
+ ```typescript
274
+ R2StorageModule.forRootAsync({
275
+ useFactory: () => ({
276
+ endpoint: process.env.R2_ENDPOINT,
277
+ accessKeyId: process.env.R2_ACCESS_KEY,
278
+ secretAccessKey: process.env.R2_SECRET_KEY,
279
+ bucketName: process.env.R2_BUCKET,
280
+ }),
281
+ })
282
+ ```
283
+
284
+ ## License
285
+
286
+ MIT
@@ -0,0 +1,6 @@
1
+ export { StorageOptions, FileFieldConfig, PhotoFieldConfig, StorageModuleOptions, } from './r2-storage/interfaces/storage-options.interface';
2
+ export { CloudflareService, UploadUrlResult, DownloadUrlResult, FileInfo, } from './r2-storage/cloudflare.service';
3
+ export { PhotoManagerService, PhotoField, PhotoUploadRequest, PhotoUploadResponse, CreatePhotosResult, UpdatePhotosResult, DeletePhotosResult, AppendUrlsOptions, } from './r2-storage/photo-manager.service';
4
+ export { R2StorageModule, } from './r2-storage/r2-storage.module';
5
+ export { PhotoFields, UploadUrls, StorageInfo, } from './r2-storage/decorators/photo-fields.decorator';
6
+ export { parseFieldPath, getNestedValue, setNestedValue, collectNestedValues, isArrayPath, getArrayBasePath, getArrayElementPath, getAllArrayItemPaths, ParsedPath, PathSegment, } from './r2-storage/utils/nested-value.util';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAllArrayItemPaths = exports.getArrayElementPath = exports.getArrayBasePath = exports.isArrayPath = exports.collectNestedValues = exports.setNestedValue = exports.getNestedValue = exports.parseFieldPath = exports.StorageInfo = exports.UploadUrls = exports.PhotoFields = exports.R2StorageModule = exports.PhotoManagerService = exports.CloudflareService = void 0;
4
+ var cloudflare_service_1 = require("./r2-storage/cloudflare.service");
5
+ Object.defineProperty(exports, "CloudflareService", { enumerable: true, get: function () { return cloudflare_service_1.CloudflareService; } });
6
+ var photo_manager_service_1 = require("./r2-storage/photo-manager.service");
7
+ Object.defineProperty(exports, "PhotoManagerService", { enumerable: true, get: function () { return photo_manager_service_1.PhotoManagerService; } });
8
+ var r2_storage_module_1 = require("./r2-storage/r2-storage.module");
9
+ Object.defineProperty(exports, "R2StorageModule", { enumerable: true, get: function () { return r2_storage_module_1.R2StorageModule; } });
10
+ var photo_fields_decorator_1 = require("./r2-storage/decorators/photo-fields.decorator");
11
+ Object.defineProperty(exports, "PhotoFields", { enumerable: true, get: function () { return photo_fields_decorator_1.PhotoFields; } });
12
+ Object.defineProperty(exports, "UploadUrls", { enumerable: true, get: function () { return photo_fields_decorator_1.UploadUrls; } });
13
+ Object.defineProperty(exports, "StorageInfo", { enumerable: true, get: function () { return photo_fields_decorator_1.StorageInfo; } });
14
+ var nested_value_util_1 = require("./r2-storage/utils/nested-value.util");
15
+ Object.defineProperty(exports, "parseFieldPath", { enumerable: true, get: function () { return nested_value_util_1.parseFieldPath; } });
16
+ Object.defineProperty(exports, "getNestedValue", { enumerable: true, get: function () { return nested_value_util_1.getNestedValue; } });
17
+ Object.defineProperty(exports, "setNestedValue", { enumerable: true, get: function () { return nested_value_util_1.setNestedValue; } });
18
+ Object.defineProperty(exports, "collectNestedValues", { enumerable: true, get: function () { return nested_value_util_1.collectNestedValues; } });
19
+ Object.defineProperty(exports, "isArrayPath", { enumerable: true, get: function () { return nested_value_util_1.isArrayPath; } });
20
+ Object.defineProperty(exports, "getArrayBasePath", { enumerable: true, get: function () { return nested_value_util_1.getArrayBasePath; } });
21
+ Object.defineProperty(exports, "getArrayElementPath", { enumerable: true, get: function () { return nested_value_util_1.getArrayElementPath; } });
22
+ Object.defineProperty(exports, "getAllArrayItemPaths", { enumerable: true, get: function () { return nested_value_util_1.getAllArrayItemPaths; } });
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAOA,sEAKyC;AAJvC,uHAAA,iBAAiB,OAAA;AAMnB,4EAS4C;AAR1C,4HAAA,mBAAmB,OAAA;AAUrB,oEAEwC;AADtC,oHAAA,eAAe,OAAA;AAGjB,yFAIwD;AAHtD,qHAAA,WAAW,OAAA;AACX,oHAAA,UAAU,OAAA;AACV,qHAAA,WAAW,OAAA;AAGb,0EAW8C;AAV5C,mHAAA,cAAc,OAAA;AACd,mHAAA,cAAc,OAAA;AACd,mHAAA,cAAc,OAAA;AACd,wHAAA,mBAAmB,OAAA;AACnB,gHAAA,WAAW,OAAA;AACX,qHAAA,gBAAgB,OAAA;AAChB,wHAAA,mBAAmB,OAAA;AACnB,yHAAA,oBAAoB,OAAA"}
@@ -0,0 +1,41 @@
1
+ import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
2
+ import { StorageOptions } from './interfaces/storage-options.interface';
3
+ export interface UploadUrlResult {
4
+ uploadUrl: string;
5
+ fileKey: string;
6
+ publicUrl?: string;
7
+ mimeType: string;
8
+ }
9
+ export interface DownloadUrlResult {
10
+ downloadUrl: string;
11
+ publicUrl?: string;
12
+ }
13
+ export interface FileInfo {
14
+ size: number;
15
+ lastModified?: Date;
16
+ contentType?: string;
17
+ }
18
+ export declare class CloudflareService implements OnModuleInit, OnModuleDestroy {
19
+ private readonly storageOptions;
20
+ private s3Client;
21
+ private options;
22
+ private readonly defaultExpiry;
23
+ constructor(storageOptions: StorageOptions);
24
+ onModuleInit(): void;
25
+ onModuleDestroy(): void;
26
+ private initializeClient;
27
+ setOptions(options: StorageOptions): void;
28
+ getOptions(): StorageOptions;
29
+ private sanitizeFilename;
30
+ private detectMimeType;
31
+ getUploadUrl(fileKey: string, fileSize: number, customFilename?: string, contentType?: string): Promise<UploadUrlResult>;
32
+ getDownloadUrl(fileKey: string): Promise<DownloadUrlResult>;
33
+ deleteFile(fileKey: string): Promise<boolean>;
34
+ deleteFiles(fileKeys: string[]): Promise<{
35
+ success: string[];
36
+ failed: string[];
37
+ }>;
38
+ getFileInfo(fileKey: string): Promise<FileInfo | null>;
39
+ fileExists(fileKey: string): Promise<boolean>;
40
+ generateFileKey(prefix: string, filename: string): string;
41
+ }
@@ -0,0 +1,197 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.CloudflareService = void 0;
49
+ const common_1 = require("@nestjs/common");
50
+ const client_s3_1 = require("@aws-sdk/client-s3");
51
+ const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
52
+ const path = __importStar(require("path"));
53
+ const mime = __importStar(require("mime-types"));
54
+ const constants_1 = require("./constants");
55
+ let CloudflareService = class CloudflareService {
56
+ constructor(storageOptions) {
57
+ this.storageOptions = storageOptions;
58
+ this.defaultExpiry = 3600;
59
+ this.options = storageOptions;
60
+ }
61
+ onModuleInit() {
62
+ this.initializeClient();
63
+ }
64
+ onModuleDestroy() {
65
+ if (this.s3Client) {
66
+ this.s3Client.destroy();
67
+ }
68
+ }
69
+ initializeClient() {
70
+ this.s3Client = new client_s3_1.S3Client({
71
+ endpoint: this.options.endpoint,
72
+ region: this.options.region || 'auto',
73
+ credentials: {
74
+ accessKeyId: this.options.accessKeyId,
75
+ secretAccessKey: this.options.secretAccessKey,
76
+ },
77
+ forcePathStyle: true,
78
+ });
79
+ }
80
+ setOptions(options) {
81
+ this.options = options;
82
+ this.initializeClient();
83
+ }
84
+ getOptions() {
85
+ return this.options;
86
+ }
87
+ sanitizeFilename(filename) {
88
+ const sanitized = filename.replace(/[^a-zA-Z0-9._-]/g, '_');
89
+ const timestamp = Date.now();
90
+ const ext = path.extname(sanitized);
91
+ const basename = path.basename(sanitized, ext);
92
+ return `${basename}_${timestamp}${ext}`;
93
+ }
94
+ detectMimeType(filename, fallbackContentType = 'application/octet-stream') {
95
+ const mimeType = mime.lookup(filename);
96
+ return mimeType || fallbackContentType;
97
+ }
98
+ async getUploadUrl(fileKey, fileSize, customFilename, contentType) {
99
+ const filename = customFilename || path.basename(fileKey);
100
+ const sanitizedFilename = this.sanitizeFilename(filename);
101
+ const finalFileKey = fileKey.includes('/')
102
+ ? `${path.dirname(fileKey)}/${sanitizedFilename}`
103
+ : sanitizedFilename;
104
+ const mimeType = contentType || this.detectMimeType(sanitizedFilename);
105
+ const command = new client_s3_1.PutObjectCommand({
106
+ Bucket: this.options.bucketName,
107
+ Key: finalFileKey,
108
+ ContentType: mimeType,
109
+ ContentLength: fileSize,
110
+ });
111
+ const expiry = this.options.signedUrlExpiry || this.defaultExpiry;
112
+ const uploadUrl = await (0, s3_request_presigner_1.getSignedUrl)(this.s3Client, command, { expiresIn: expiry });
113
+ let publicUrl;
114
+ if (this.options.publicUrlBase) {
115
+ publicUrl = `${this.options.publicUrlBase}/${finalFileKey}`;
116
+ }
117
+ return {
118
+ uploadUrl,
119
+ fileKey: finalFileKey,
120
+ publicUrl,
121
+ mimeType,
122
+ };
123
+ }
124
+ async getDownloadUrl(fileKey) {
125
+ const command = new client_s3_1.GetObjectCommand({
126
+ Bucket: this.options.bucketName,
127
+ Key: fileKey,
128
+ });
129
+ const expiry = this.options.signedUrlExpiry || this.defaultExpiry;
130
+ const downloadUrl = await (0, s3_request_presigner_1.getSignedUrl)(this.s3Client, command, { expiresIn: expiry });
131
+ let publicUrl;
132
+ if (this.options.publicUrlBase) {
133
+ publicUrl = `${this.options.publicUrlBase}/${fileKey}`;
134
+ }
135
+ return {
136
+ downloadUrl,
137
+ publicUrl,
138
+ };
139
+ }
140
+ async deleteFile(fileKey) {
141
+ try {
142
+ const command = new client_s3_1.DeleteObjectCommand({
143
+ Bucket: this.options.bucketName,
144
+ Key: fileKey,
145
+ });
146
+ await this.s3Client.send(command);
147
+ return true;
148
+ }
149
+ catch (error) {
150
+ console.error(`Failed to delete file ${fileKey}:`, error);
151
+ return false;
152
+ }
153
+ }
154
+ async deleteFiles(fileKeys) {
155
+ const results = await Promise.all(fileKeys.map(async (fileKey) => {
156
+ const success = await this.deleteFile(fileKey);
157
+ return { fileKey, success };
158
+ }));
159
+ return {
160
+ success: results.filter((r) => r.success).map((r) => r.fileKey),
161
+ failed: results.filter((r) => !r.success).map((r) => r.fileKey),
162
+ };
163
+ }
164
+ async getFileInfo(fileKey) {
165
+ try {
166
+ const command = new client_s3_1.HeadObjectCommand({
167
+ Bucket: this.options.bucketName,
168
+ Key: fileKey,
169
+ });
170
+ const response = await this.s3Client.send(command);
171
+ return {
172
+ size: response.ContentLength || 0,
173
+ lastModified: response.LastModified,
174
+ contentType: response.ContentType,
175
+ };
176
+ }
177
+ catch (error) {
178
+ return null;
179
+ }
180
+ }
181
+ async fileExists(fileKey) {
182
+ const fileInfo = await this.getFileInfo(fileKey);
183
+ return fileInfo !== null;
184
+ }
185
+ generateFileKey(prefix, filename) {
186
+ const timestamp = Date.now();
187
+ const sanitizedFilename = this.sanitizeFilename(filename);
188
+ return `${prefix}/${timestamp}_${sanitizedFilename}`;
189
+ }
190
+ };
191
+ exports.CloudflareService = CloudflareService;
192
+ exports.CloudflareService = CloudflareService = __decorate([
193
+ (0, common_1.Injectable)(),
194
+ __param(0, (0, common_1.Inject)(constants_1.STORAGE_OPTIONS)),
195
+ __metadata("design:paramtypes", [Object])
196
+ ], CloudflareService);
197
+ //# sourceMappingURL=cloudflare.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.service.js","sourceRoot":"","sources":["../../src/r2-storage/cloudflare.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const STORAGE_OPTIONS = "STORAGE_OPTIONS";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STORAGE_OPTIONS = void 0;
4
+ exports.STORAGE_OPTIONS = 'STORAGE_OPTIONS';
5
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/r2-storage/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,eAAe,GAAG,iBAAiB,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface PhotoFieldsContext {
2
+ photoFields: Array<{
3
+ field: string;
4
+ urlField?: string;
5
+ sizeField?: string;
6
+ }>;
7
+ }
8
+ export declare const PhotoFields: (...dataOrPipes: (PhotoFieldsContext | import("@nestjs/common").PipeTransform<any, any> | import("@nestjs/common").Type<import("@nestjs/common").PipeTransform<any, any>>)[]) => ParameterDecorator;
9
+ export interface UploadUrlsContext {
10
+ uploadUrls: Array<{
11
+ field: string;
12
+ fileKey: string;
13
+ uploadUrl: string;
14
+ publicUrl?: string;
15
+ }>;
16
+ }
17
+ export declare const UploadUrls: (...dataOrPipes: (UploadUrlsContext | import("@nestjs/common").PipeTransform<any, any> | import("@nestjs/common").Type<import("@nestjs/common").PipeTransform<any, any>>)[]) => ParameterDecorator;
18
+ export interface StorageContext {
19
+ totalStorageUsed?: number;
20
+ storageIncrease?: number;
21
+ storageDecrease?: number;
22
+ totalStorageFreed?: number;
23
+ }
24
+ export declare const StorageInfo: (...dataOrPipes: (StorageContext | import("@nestjs/common").PipeTransform<any, any> | import("@nestjs/common").Type<import("@nestjs/common").PipeTransform<any, any>>)[]) => ParameterDecorator;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageInfo = exports.UploadUrls = exports.PhotoFields = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.PhotoFields = (0, common_1.createParamDecorator)((data, ctx) => {
6
+ return data.photoFields;
7
+ });
8
+ exports.UploadUrls = (0, common_1.createParamDecorator)((data, ctx) => {
9
+ return data.uploadUrls;
10
+ });
11
+ exports.StorageInfo = (0, common_1.createParamDecorator)((data, ctx) => {
12
+ return data;
13
+ });
14
+ //# sourceMappingURL=photo-fields.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"photo-fields.decorator.js","sourceRoot":"","sources":["../../../src/r2-storage/decorators/photo-fields.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAAwE;AAU3D,QAAA,WAAW,GAAG,IAAA,6BAAoB,EAC7C,CAAC,IAAwB,EAAE,GAAqB,EAAE,EAAE;IAClD,OAAO,IAAI,CAAC,WAAW,CAAC;AAC1B,CAAC,CACF,CAAC;AAWW,QAAA,UAAU,GAAG,IAAA,6BAAoB,EAC5C,CAAC,IAAuB,EAAE,GAAqB,EAAE,EAAE;IACjD,OAAO,IAAI,CAAC,UAAU,CAAC;AACzB,CAAC,CACF,CAAC;AASW,QAAA,WAAW,GAAG,IAAA,6BAAoB,EAC7C,CAAC,IAAoB,EAAE,GAAqB,EAAE,EAAE;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC,CACF,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface StorageOptions {
2
+ endpoint: string;
3
+ accessKeyId: string;
4
+ secretAccessKey: string;
5
+ bucketName: string;
6
+ region?: string;
7
+ publicUrlBase?: string;
8
+ signedUrlExpiry?: number;
9
+ }
10
+ export interface FileFieldConfig {
11
+ field: string;
12
+ sizeField?: string;
13
+ }
14
+ export interface PhotoFieldConfig {
15
+ field: string;
16
+ urlField?: string;
17
+ sizeField?: string;
18
+ }
19
+ export interface StorageModuleOptions {
20
+ isGlobal?: boolean;
21
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=storage-options.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-options.interface.js","sourceRoot":"","sources":["../../../src/r2-storage/interfaces/storage-options.interface.ts"],"names":[],"mappings":""}