oblien 1.2.4 → 1.2.6

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,744 @@
1
+ /**
2
+ * Oblien CDN Module
3
+ * Upload and manage files on Oblien CDN with automatic token management
4
+ */
5
+
6
+ export class OblienCDN {
7
+ constructor(client, config = {}) {
8
+ if (!client) throw new Error('Oblien client is required');
9
+ this.client = client;
10
+ this.cdnURL = config.cdnURL || 'https://cdn.oblien.com/api';
11
+
12
+ // Token cache for reuse within expiration window
13
+ this._tokenCache = {
14
+ user: null,
15
+ admin: null
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Generate a user CDN token (upload/process permissions)
21
+ * Tokens expire in 1 minute for security
22
+ *
23
+ * @returns {Promise<Object>} Token response with token, scope, and permissions
24
+ *
25
+ * @example
26
+ * const tokenData = await cdn.generateUserToken();
27
+ * // Returns:
28
+ * // {
29
+ * // success: true,
30
+ * // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
31
+ * // scope: 'user',
32
+ * // permissions: ['upload', 'process'],
33
+ * // expiresIn: '1m'
34
+ * // }
35
+ */
36
+ async generateUserToken() {
37
+ const response = await this.client.post('cdn/token');
38
+
39
+ // Cache token for reuse
40
+ if (response.success && response.token) {
41
+ this._tokenCache.user = {
42
+ token: response.token,
43
+ expiresAt: Date.now() + 50000 // 50 seconds (buffer before 1m expiry)
44
+ };
45
+ }
46
+
47
+ return response;
48
+ }
49
+
50
+ /**
51
+ * Generate an admin CDN token (full access permissions)
52
+ * Requires admin privileges on the API account
53
+ * Tokens expire in 1 minute for security
54
+ *
55
+ * @returns {Promise<Object>} Token response with token, scope, and permissions
56
+ * @throws {Error} If user doesn't have admin privileges
57
+ *
58
+ * @example
59
+ * const tokenData = await cdn.generateAdminToken();
60
+ * // Returns:
61
+ * // {
62
+ * // success: true,
63
+ * // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
64
+ * // scope: 'admin',
65
+ * // permissions: ['upload', 'process', 'read', 'list', 'info', 'delete'],
66
+ * // expiresIn: '1m'
67
+ * // }
68
+ */
69
+ async generateAdminToken() {
70
+ const response = await this.client.post('cdn/token/admin');
71
+
72
+ // Cache token for reuse
73
+ if (response.success && response.token) {
74
+ this._tokenCache.admin = {
75
+ token: response.token,
76
+ expiresAt: Date.now() + 50000 // 50 seconds (buffer before 1m expiry)
77
+ };
78
+ }
79
+
80
+ return response;
81
+ }
82
+
83
+ /**
84
+ * Get a valid token (user or admin)
85
+ * Automatically generates new token if cache is empty or expired
86
+ * @private
87
+ */
88
+ async _getToken(scope = 'user') {
89
+ const cached = this._tokenCache[scope];
90
+
91
+ // Use cached token if still valid
92
+ if (cached && cached.expiresAt > Date.now()) {
93
+ return cached.token;
94
+ }
95
+
96
+ // Generate new token
97
+ const tokenData = scope === 'admin'
98
+ ? await this.generateAdminToken()
99
+ : await this.generateUserToken();
100
+
101
+ return tokenData.token;
102
+ }
103
+
104
+ /**
105
+ * Normalize file input to buffer and filename
106
+ * Supports: Buffer, Base64, Multer files, Stream
107
+ * @private
108
+ */
109
+ _normalizeFile(file, providedFilename) {
110
+ let buffer;
111
+ let filename = providedFilename;
112
+ let mimetype;
113
+
114
+ // Handle Multer file object (from Express.js file uploads)
115
+ if (file && file.buffer && file.originalname) {
116
+ buffer = file.buffer;
117
+ filename = filename || file.originalname;
118
+ mimetype = file.mimetype;
119
+ }
120
+ // Handle Base64 string
121
+ else if (typeof file === 'string') {
122
+ // Check if it's a data URL (data:image/jpeg;base64,...)
123
+ const base64Match = file.match(/^data:([^;]+);base64,(.+)$/);
124
+ if (base64Match) {
125
+ mimetype = base64Match[1];
126
+ buffer = Buffer.from(base64Match[2], 'base64');
127
+ // Extract extension from mimetype if no filename
128
+ if (!filename) {
129
+ const ext = mimetype.split('/')[1] || 'bin';
130
+ filename = `file.${ext}`;
131
+ }
132
+ } else {
133
+ // Plain base64 string
134
+ buffer = Buffer.from(file, 'base64');
135
+ filename = filename || 'file.bin';
136
+ }
137
+ }
138
+ // Handle Buffer or Uint8Array
139
+ else if (file instanceof Buffer || file instanceof Uint8Array) {
140
+ buffer = file;
141
+ filename = filename || 'file.bin';
142
+ }
143
+ // Handle Node.js ReadableStream
144
+ else if (file && typeof file.pipe === 'function') {
145
+ throw new Error('Streams are not directly supported. Please convert to Buffer first using stream.toArray() or similar.');
146
+ }
147
+ // Handle other types (File, Blob, etc.) - pass through
148
+ else {
149
+ return { file, filename: filename || 'file' };
150
+ }
151
+
152
+ return { buffer, filename, mimetype };
153
+ }
154
+
155
+ /**
156
+ * Upload a single file to CDN
157
+ * Automatically handles token generation if not provided
158
+ * Supports: Buffer, Base64 strings, Multer file objects
159
+ *
160
+ * @param {Buffer|string|Object} file - File to upload
161
+ * - Buffer: Raw file buffer
162
+ * - string: Base64 encoded file or data URL (data:image/jpeg;base64,...)
163
+ * - Object: Multer file object ({ buffer, originalname, mimetype })
164
+ * @param {Object} options - Upload options
165
+ * @param {string} options.token - CDN token (optional, will auto-generate if not provided)
166
+ * @param {string} options.filename - Custom filename (optional)
167
+ * @param {string} options.mimetype - File mimetype (optional)
168
+ * @param {string} options.scope - Token scope to use: 'user' or 'admin' (default: 'user')
169
+ * @returns {Promise<Object>} Upload result with file URL and metadata
170
+ *
171
+ * @example
172
+ * // Upload from Buffer
173
+ * const fs = require('fs');
174
+ * const fileBuffer = fs.readFileSync('image.jpg');
175
+ * const result = await cdn.upload(fileBuffer, { filename: 'image.jpg' });
176
+ *
177
+ * @example
178
+ * // Upload from Base64
179
+ * const base64 = 'data:image/jpeg;base64,/9j/4AAQSkZJRg...';
180
+ * const result = await cdn.upload(base64, { filename: 'image.jpg' });
181
+ *
182
+ * @example
183
+ * // Upload from Base64 (plain)
184
+ * const base64 = '/9j/4AAQSkZJRg...';
185
+ * const result = await cdn.upload(base64, { filename: 'image.jpg' });
186
+ *
187
+ * @example
188
+ * // Upload from Multer (Express.js)
189
+ * app.post('/upload', upload.single('file'), async (req, res) => {
190
+ * const result = await cdn.upload(req.file);
191
+ * res.json(result);
192
+ * });
193
+ *
194
+ * // Returns:
195
+ * // {
196
+ * // success: true,
197
+ * // file: {
198
+ * // url: 'https://cdn.oblien.com/abc123/image.jpg',
199
+ * // filename: 'image.jpg',
200
+ * // size: 102400,
201
+ * // mimetype: 'image/jpeg'
202
+ * // }
203
+ * // }
204
+ */
205
+ async upload(file, options = {}) {
206
+ const { token, filename, mimetype, scope = 'user' } = options;
207
+
208
+ // Get token (use provided or generate new one)
209
+ const cdnToken = token || await this._getToken(scope);
210
+
211
+ // Normalize file input
212
+ const normalized = this._normalizeFile(file, filename);
213
+
214
+ // Create FormData
215
+ const FormData = globalThis.FormData || (await import('form-data')).default;
216
+ const formData = new FormData();
217
+
218
+ // Add file to form data
219
+ if (normalized.buffer) {
220
+ const Blob = globalThis.Blob || (await import('buffer')).Blob;
221
+ const fileBlob = new Blob([normalized.buffer], {
222
+ type: mimetype || normalized.mimetype || 'application/octet-stream'
223
+ });
224
+ formData.append('file', fileBlob, normalized.filename);
225
+ } else {
226
+ formData.append('file', normalized.file, normalized.filename);
227
+ }
228
+
229
+ // Upload to CDN
230
+ const response = await fetch(`${this.cdnURL}/`, {
231
+ method: 'POST',
232
+ headers: {
233
+ 'Authorization': `Bearer ${cdnToken}`
234
+ },
235
+ body: formData
236
+ });
237
+
238
+ if (!response.ok) {
239
+ const error = await response.json().catch(() => ({}));
240
+ throw new Error(error.message || `Upload failed: ${response.status}`);
241
+ }
242
+
243
+ return response.json();
244
+ }
245
+
246
+ /**
247
+ * Upload multiple files to CDN
248
+ * Automatically handles token generation if not provided
249
+ * Supports: Buffers, Base64 strings, Multer file objects
250
+ *
251
+ * @param {Array<Buffer|string|Object>} files - Array of files to upload
252
+ * - Buffer: Raw file buffers
253
+ * - string: Base64 encoded files
254
+ * - Object: Multer file objects
255
+ * @param {Object} options - Upload options
256
+ * @param {string} options.token - CDN token (optional, will auto-generate if not provided)
257
+ * @param {Array<string>} options.filenames - Array of custom filenames (optional)
258
+ * @param {string} options.scope - Token scope to use: 'user' or 'admin' (default: 'user')
259
+ * @returns {Promise<Object>} Upload result with array of file URLs and metadata
260
+ *
261
+ * @example
262
+ * // Upload multiple buffers
263
+ * const files = [
264
+ * fs.readFileSync('image1.jpg'),
265
+ * fs.readFileSync('image2.jpg')
266
+ * ];
267
+ * const result = await cdn.uploadMultiple(files, {
268
+ * filenames: ['image1.jpg', 'image2.jpg']
269
+ * });
270
+ *
271
+ * @example
272
+ * // Upload multiple base64 strings
273
+ * const files = [
274
+ * 'data:image/jpeg;base64,/9j/4AAQ...',
275
+ * 'data:image/png;base64,iVBORw0KGgo...'
276
+ * ];
277
+ * const result = await cdn.uploadMultiple(files);
278
+ *
279
+ * @example
280
+ * // Upload from Multer (Express.js)
281
+ * app.post('/upload', upload.array('files'), async (req, res) => {
282
+ * const result = await cdn.uploadMultiple(req.files);
283
+ * res.json(result);
284
+ * });
285
+ *
286
+ * // Returns:
287
+ * // {
288
+ * // success: true,
289
+ * // files: [
290
+ * // { url: 'https://cdn.oblien.com/abc123/image1.jpg', ... },
291
+ * // { url: 'https://cdn.oblien.com/def456/image2.jpg', ... }
292
+ * // ]
293
+ * // }
294
+ */
295
+ async uploadMultiple(files, options = {}) {
296
+ if (!Array.isArray(files) || files.length === 0) {
297
+ throw new Error('Files must be a non-empty array');
298
+ }
299
+
300
+ const { token, filenames = [], scope = 'user' } = options;
301
+
302
+ // Get token (use provided or generate new one)
303
+ const cdnToken = token || await this._getToken(scope);
304
+
305
+ // Create FormData
306
+ const FormData = globalThis.FormData || (await import('form-data')).default;
307
+ const formData = new FormData();
308
+
309
+ // Add all files
310
+ for (let i = 0; i < files.length; i++) {
311
+ const file = files[i];
312
+ const filename = filenames[i];
313
+
314
+ // Normalize file input
315
+ const normalized = this._normalizeFile(file, filename);
316
+
317
+ if (normalized.buffer) {
318
+ const Blob = globalThis.Blob || (await import('buffer')).Blob;
319
+ const fileBlob = new Blob([normalized.buffer], {
320
+ type: normalized.mimetype || 'application/octet-stream'
321
+ });
322
+ formData.append('files', fileBlob, normalized.filename);
323
+ } else {
324
+ formData.append('files', normalized.file, normalized.filename);
325
+ }
326
+ }
327
+
328
+ // Upload to CDN
329
+ const response = await fetch(`${this.cdnURL}/multiple`, {
330
+ method: 'POST',
331
+ headers: {
332
+ 'Authorization': `Bearer ${cdnToken}`
333
+ },
334
+ body: formData
335
+ });
336
+
337
+ if (!response.ok) {
338
+ const error = await response.json().catch(() => ({}));
339
+ throw new Error(error.message || `Upload failed: ${response.status}`);
340
+ }
341
+
342
+ return response.json();
343
+ }
344
+
345
+ /**
346
+ * Upload files from URLs (download and upload to CDN)
347
+ * CDN will download the images from URLs and process them
348
+ * Automatically handles token generation if not provided
349
+ *
350
+ * @param {Array<string>} urls - Array of image URLs to download and upload
351
+ * @param {Object} options - Upload options
352
+ * @param {string} options.token - CDN token (optional, will auto-generate if not provided)
353
+ * @param {string} options.scope - Token scope to use: 'user' or 'admin' (default: 'user')
354
+ * @returns {Promise<Object>} Upload result with processed file URLs
355
+ *
356
+ * @example
357
+ * const result = await cdn.uploadFromUrls([
358
+ * 'https://example.com/image1.jpg',
359
+ * 'https://example.com/image2.png'
360
+ * ]);
361
+ *
362
+ * // Returns:
363
+ * // {
364
+ * // success: true,
365
+ * // files: [
366
+ * // {
367
+ * // url: 'https://cdn.oblien.com/abc123/image1.jpg',
368
+ * // originalUrl: 'https://example.com/image1.jpg',
369
+ * // filename: 'image1.jpg',
370
+ * // size: 102400,
371
+ * // mimetype: 'image/jpeg',
372
+ * // variants: { ... }
373
+ * // },
374
+ * // {
375
+ * // url: 'https://cdn.oblien.com/def456/image2.png',
376
+ * // originalUrl: 'https://example.com/image2.png',
377
+ * // filename: 'image2.png',
378
+ * // size: 204800,
379
+ * // mimetype: 'image/png',
380
+ * // variants: { ... }
381
+ * // }
382
+ * // ]
383
+ * // }
384
+ */
385
+ async uploadFromUrls(urls, options = {}) {
386
+ if (!Array.isArray(urls) || urls.length === 0) {
387
+ throw new Error('URLs must be a non-empty array');
388
+ }
389
+
390
+ const { token, scope = 'user' } = options;
391
+
392
+ // Get token (use provided or generate new one)
393
+ const cdnToken = token || await this._getToken(scope);
394
+
395
+ // Upload from URLs
396
+ const response = await fetch(`${this.cdnURL}/process-urls`, {
397
+ method: 'POST',
398
+ headers: {
399
+ 'Authorization': `Bearer ${cdnToken}`,
400
+ 'Content-Type': 'application/json'
401
+ },
402
+ body: JSON.stringify({ urls })
403
+ });
404
+
405
+ if (!response.ok) {
406
+ const error = await response.json().catch(() => ({}));
407
+ throw new Error(error.message || `Upload from URLs failed: ${response.status}`);
408
+ }
409
+
410
+ return response.json();
411
+ }
412
+
413
+ /**
414
+ * @deprecated Use uploadFromUrls() instead
415
+ * Process URLs (download and process images from URLs)
416
+ */
417
+ async processUrls(urls, options = {}) {
418
+ console.warn('cdn.processUrls() is deprecated. Use cdn.uploadFromUrls() instead.');
419
+ return this.uploadFromUrls(urls, options);
420
+ }
421
+
422
+ /**
423
+ * Get file information (ADMIN ONLY)
424
+ * Requires admin token
425
+ *
426
+ * @param {string} filePath - File path on CDN
427
+ * @param {Object} options - Options
428
+ * @param {string} options.token - Admin CDN token (optional, will auto-generate if not provided)
429
+ * @returns {Promise<Object>} File information
430
+ *
431
+ * @example
432
+ * const info = await cdn.getFileInfo('abc123/image.jpg');
433
+ *
434
+ * // Returns:
435
+ * // {
436
+ * // success: true,
437
+ * // file: {
438
+ * // path: 'abc123/image.jpg',
439
+ * // size: 102400,
440
+ * // mimetype: 'image/jpeg',
441
+ * // created: '2024-01-01T00:00:00.000Z'
442
+ * // }
443
+ * // }
444
+ */
445
+ async getFileInfo(filePath, options = {}) {
446
+ if (!filePath) {
447
+ throw new Error('File path is required');
448
+ }
449
+
450
+ const { token } = options;
451
+
452
+ // Get admin token (use provided or generate new one)
453
+ const cdnToken = token || await this._getToken('admin');
454
+
455
+ // Get file info
456
+ const response = await fetch(`${this.cdnURL}/info/${filePath}`, {
457
+ method: 'GET',
458
+ headers: {
459
+ 'Authorization': `Bearer ${cdnToken}`
460
+ }
461
+ });
462
+
463
+ if (!response.ok) {
464
+ const error = await response.json().catch(() => ({}));
465
+ throw new Error(error.message || `Failed to get file info: ${response.status}`);
466
+ }
467
+
468
+ return response.json();
469
+ }
470
+
471
+ /**
472
+ * Get available image variant options
473
+ *
474
+ * @param {Object} options - Options
475
+ * @param {string} options.token - CDN token (optional, will auto-generate if not provided)
476
+ * @returns {Promise<Object>} Available variants
477
+ *
478
+ * @example
479
+ * const variants = await cdn.getVariants();
480
+ *
481
+ * // Returns:
482
+ * // {
483
+ * // variants: {
484
+ * // thumb: { width: 150, height: 150, fit: 'inside' },
485
+ * // medium: { width: 800, height: 600, fit: 'inside' },
486
+ * // blur: { width: 75, height: 75, fit: 'cover', blur: 5 }
487
+ * // }
488
+ * // }
489
+ */
490
+ async getVariants(options = {}) {
491
+ const { token } = options;
492
+
493
+ // Get token (use provided or generate new one)
494
+ const cdnToken = token || await this._getToken('user');
495
+
496
+ const response = await fetch(`${this.cdnURL}/variants`, {
497
+ method: 'GET',
498
+ headers: {
499
+ 'Authorization': `Bearer ${cdnToken}`
500
+ }
501
+ });
502
+
503
+ if (!response.ok) {
504
+ const error = await response.json().catch(() => ({}));
505
+ throw new Error(error.message || `Failed to get variants: ${response.status}`);
506
+ }
507
+
508
+ return response.json();
509
+ }
510
+
511
+ /**
512
+ * Get file size limits
513
+ *
514
+ * @param {Object} options - Options
515
+ * @param {string} options.token - CDN token (optional, will auto-generate if not provided)
516
+ * @returns {Promise<Object>} File size limits
517
+ *
518
+ * @example
519
+ * const limits = await cdn.getLimits();
520
+ *
521
+ * // Returns:
522
+ * // {
523
+ * // limits: {
524
+ * // 'image/jpeg': 5242880,
525
+ * // 'image/png': 8388608,
526
+ * // 'default': 10485760
527
+ * // }
528
+ * // }
529
+ */
530
+ async getLimits(options = {}) {
531
+ const { token } = options;
532
+
533
+ // Get token (use provided or generate new one)
534
+ const cdnToken = token || await this._getToken('user');
535
+
536
+ const response = await fetch(`${this.cdnURL}/limits`, {
537
+ method: 'GET',
538
+ headers: {
539
+ 'Authorization': `Bearer ${cdnToken}`
540
+ }
541
+ });
542
+
543
+ if (!response.ok) {
544
+ const error = await response.json().catch(() => ({}));
545
+ throw new Error(error.message || `Failed to get limits: ${response.status}`);
546
+ }
547
+
548
+ return response.json();
549
+ }
550
+
551
+ /**
552
+ * Clear token cache
553
+ * Useful when you want to force fresh token generation
554
+ *
555
+ * @param {string} scope - Scope to clear: 'user', 'admin', or 'all' (default: 'all')
556
+ *
557
+ * @example
558
+ * cdn.clearTokenCache(); // Clear all
559
+ * cdn.clearTokenCache('user'); // Clear user token only
560
+ */
561
+ clearTokenCache(scope = 'all') {
562
+ if (scope === 'all') {
563
+ this._tokenCache.user = null;
564
+ this._tokenCache.admin = null;
565
+ } else if (scope === 'user' || scope === 'admin') {
566
+ this._tokenCache[scope] = null;
567
+ }
568
+ }
569
+
570
+ // ===== FILE MANAGEMENT ENDPOINTS =====
571
+ // These endpoints use session authentication (not CDN tokens)
572
+
573
+ /**
574
+ * List user's uploaded files
575
+ * Uses session authentication (requires logged in user)
576
+ *
577
+ * @param {Object} options - Query options
578
+ * @param {number} options.page - Page number (default: 1)
579
+ * @param {number} options.limit - Items per page (default: 50, max: 100)
580
+ * @param {boolean} options.includeDeleted - Include soft-deleted files (default: false)
581
+ * @param {string} options.uploadType - Filter by upload type: 'upload' or 'url'
582
+ * @param {string} options.sortBy - Sort column: 'created_at', 'size', 'filename' (default: 'created_at')
583
+ * @param {string} options.sortOrder - Sort order: 'ASC' or 'DESC' (default: 'DESC')
584
+ * @returns {Promise<Object>} Paginated list of files
585
+ *
586
+ * @example
587
+ * const result = await cdn.listFiles({ page: 1, limit: 50 });
588
+ *
589
+ * // Returns:
590
+ * // {
591
+ * // success: true,
592
+ * // data: {
593
+ * // files: [
594
+ * // {
595
+ * // id: 123,
596
+ * // filename: 'image.jpg',
597
+ * // cdn_url: 'https://cdn.oblien.com/...',
598
+ * // size: 102400,
599
+ * // mime_type: 'image/jpeg',
600
+ * // variants: [...],
601
+ * // upload_type: 'upload',
602
+ * // created_at: '2024-01-01T00:00:00.000Z'
603
+ * // }
604
+ * // ],
605
+ * // pagination: {
606
+ * // page: 1,
607
+ * // limit: 50,
608
+ * // total: 150,
609
+ * // totalPages: 3,
610
+ * // hasMore: true
611
+ * // }
612
+ * // }
613
+ * // }
614
+ */
615
+ async listFiles(options = {}) {
616
+ const {
617
+ page = 1,
618
+ limit = 50,
619
+ includeDeleted = false,
620
+ uploadType = null,
621
+ sortBy = 'created_at',
622
+ sortOrder = 'DESC'
623
+ } = options;
624
+
625
+ const queryParams = new URLSearchParams({
626
+ page: page.toString(),
627
+ limit: limit.toString(),
628
+ include_deleted: includeDeleted.toString(),
629
+ sort_by: sortBy,
630
+ sort_order: sortOrder
631
+ });
632
+
633
+ if (uploadType) {
634
+ queryParams.append('upload_type', uploadType);
635
+ }
636
+
637
+ return this.client.get(`cdn/files?${queryParams.toString()}`);
638
+ }
639
+
640
+ /**
641
+ * Get user storage statistics
642
+ * Uses session authentication (requires logged in user)
643
+ *
644
+ * @returns {Promise<Object>} Storage statistics
645
+ *
646
+ * @example
647
+ * const stats = await cdn.getStats();
648
+ *
649
+ * // Returns:
650
+ * // {
651
+ * // success: true,
652
+ * // data: {
653
+ * // totalFiles: 150,
654
+ * // totalSize: 157286400,
655
+ * // activeFiles: 145,
656
+ * // activeSize: 152166400,
657
+ * // uploadedFiles: 100,
658
+ * // urlFiles: 50
659
+ * // }
660
+ * // }
661
+ */
662
+ async getStats() {
663
+ return this.client.get('cdn/stats');
664
+ }
665
+
666
+ /**
667
+ * Get file by ID
668
+ * Uses session authentication (requires logged in user)
669
+ *
670
+ * @param {number|string} fileId - File ID
671
+ * @returns {Promise<Object>} File details
672
+ *
673
+ * @example
674
+ * const file = await cdn.getFile(123);
675
+ *
676
+ * // Returns:
677
+ * // {
678
+ * // success: true,
679
+ * // data: {
680
+ * // id: 123,
681
+ * // filename: 'image.jpg',
682
+ * // cdn_url: 'https://cdn.oblien.com/...',
683
+ * // size: 102400,
684
+ * // mime_type: 'image/jpeg',
685
+ * // variants: [...],
686
+ * // is_deleted: false,
687
+ * // created_at: '2024-01-01T00:00:00.000Z'
688
+ * // }
689
+ * // }
690
+ */
691
+ async getFile(fileId) {
692
+ if (!fileId) {
693
+ throw new Error('File ID is required');
694
+ }
695
+ return this.client.get(`cdn/files/${fileId}`);
696
+ }
697
+
698
+ /**
699
+ * Delete file (soft delete)
700
+ * Uses session authentication (requires logged in user)
701
+ *
702
+ * @param {number|string} fileId - File ID to delete
703
+ * @returns {Promise<Object>} Delete result
704
+ *
705
+ * @example
706
+ * const result = await cdn.deleteFile(123);
707
+ *
708
+ * // Returns:
709
+ * // {
710
+ * // success: true,
711
+ * // message: 'File marked as deleted'
712
+ * // }
713
+ */
714
+ async deleteFile(fileId) {
715
+ if (!fileId) {
716
+ throw new Error('File ID is required');
717
+ }
718
+ return this.client.delete(`cdn/files/${fileId}`);
719
+ }
720
+
721
+ /**
722
+ * Restore a soft-deleted file
723
+ * Uses session authentication (requires logged in user)
724
+ *
725
+ * @param {number|string} fileId - File ID to restore
726
+ * @returns {Promise<Object>} Restore result
727
+ *
728
+ * @example
729
+ * const result = await cdn.restoreFile(123);
730
+ *
731
+ * // Returns:
732
+ * // {
733
+ * // success: true,
734
+ * // message: 'File restored successfully'
735
+ * // }
736
+ */
737
+ async restoreFile(fileId) {
738
+ if (!fileId) {
739
+ throw new Error('File ID is required');
740
+ }
741
+ return this.client.post(`cdn/files/${fileId}/restore`);
742
+ }
743
+ }
744
+