@rovela-ai/sdk 0.1.26 → 0.1.28
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/dist/admin/components/CategoryForm.d.ts.map +1 -1
- package/dist/admin/components/CategoryForm.js +9 -2
- package/dist/admin/components/CategoryForm.js.map +1 -1
- package/dist/admin/components/ProductForm.d.ts.map +1 -1
- package/dist/admin/components/ProductForm.js +6 -35
- package/dist/admin/components/ProductForm.js.map +1 -1
- package/dist/core/db/queries.d.ts +7 -7
- package/dist/media/api/delete.d.ts +44 -0
- package/dist/media/api/delete.d.ts.map +1 -0
- package/dist/media/api/delete.js +134 -0
- package/dist/media/api/delete.js.map +1 -0
- package/dist/media/api/index.d.ts +17 -0
- package/dist/media/api/index.d.ts.map +1 -0
- package/dist/media/api/index.js +17 -0
- package/dist/media/api/index.js.map +1 -0
- package/dist/media/api/presign.d.ts +39 -0
- package/dist/media/api/presign.d.ts.map +1 -0
- package/dist/media/api/presign.js +138 -0
- package/dist/media/api/presign.js.map +1 -0
- package/dist/media/components/DropZone.d.ts +18 -0
- package/dist/media/components/DropZone.d.ts.map +1 -0
- package/dist/media/components/DropZone.js +112 -0
- package/dist/media/components/DropZone.js.map +1 -0
- package/dist/media/components/ImageGalleryUpload.d.ts +18 -0
- package/dist/media/components/ImageGalleryUpload.d.ts.map +1 -0
- package/dist/media/components/ImageGalleryUpload.js +166 -0
- package/dist/media/components/ImageGalleryUpload.js.map +1 -0
- package/dist/media/components/ImageUpload.d.ts +17 -0
- package/dist/media/components/ImageUpload.d.ts.map +1 -0
- package/dist/media/components/ImageUpload.js +103 -0
- package/dist/media/components/ImageUpload.js.map +1 -0
- package/dist/media/components/index.d.ts +10 -0
- package/dist/media/components/index.d.ts.map +1 -0
- package/dist/media/components/index.js +9 -0
- package/dist/media/components/index.js.map +1 -0
- package/dist/media/config.d.ts +57 -0
- package/dist/media/config.d.ts.map +1 -0
- package/dist/media/config.js +142 -0
- package/dist/media/config.js.map +1 -0
- package/dist/media/hooks/index.d.ts +8 -0
- package/dist/media/hooks/index.d.ts.map +1 -0
- package/dist/media/hooks/index.js +7 -0
- package/dist/media/hooks/index.js.map +1 -0
- package/dist/media/hooks/useUpload.d.ts +32 -0
- package/dist/media/hooks/useUpload.d.ts.map +1 -0
- package/dist/media/hooks/useUpload.js +258 -0
- package/dist/media/hooks/useUpload.js.map +1 -0
- package/dist/media/index.d.ts +57 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +68 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/server/delete.d.ts +59 -0
- package/dist/media/server/delete.d.ts.map +1 -0
- package/dist/media/server/delete.js +176 -0
- package/dist/media/server/delete.js.map +1 -0
- package/dist/media/server/index.d.ts +10 -0
- package/dist/media/server/index.d.ts.map +1 -0
- package/dist/media/server/index.js +13 -0
- package/dist/media/server/index.js.map +1 -0
- package/dist/media/server/presign.d.ts +57 -0
- package/dist/media/server/presign.d.ts.map +1 -0
- package/dist/media/server/presign.js +112 -0
- package/dist/media/server/presign.js.map +1 -0
- package/dist/media/server/r2-client.d.ts +30 -0
- package/dist/media/server/r2-client.d.ts.map +1 -0
- package/dist/media/server/r2-client.js +76 -0
- package/dist/media/server/r2-client.js.map +1 -0
- package/dist/media/types.d.ts +275 -0
- package/dist/media/types.d.ts.map +1 -0
- package/dist/media/types.js +52 -0
- package/dist/media/types.js.map +1 -0
- package/package.json +15 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* @rovela-ai/sdk/media/hooks/useUpload
|
|
4
|
+
*
|
|
5
|
+
* React hook for uploading files to R2 storage via presigned URLs.
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useCallback, useRef } from 'react';
|
|
8
|
+
import { validateFile, DEFAULT_UPLOAD_CONFIG } from '../types';
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Hook Implementation
|
|
11
|
+
// =============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* React hook for uploading files to R2 storage.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Upload configuration options
|
|
16
|
+
* @returns Upload functions and state
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function ImageUploader() {
|
|
21
|
+
* const { upload, isUploading, progress, error } = useUpload({
|
|
22
|
+
* folder: 'products',
|
|
23
|
+
* entityId: 'prod_123',
|
|
24
|
+
* onSuccess: (result) => console.log('Uploaded:', result.url),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* const handleFile = async (file: File) => {
|
|
28
|
+
* const result = await upload(file);
|
|
29
|
+
* if (result.success) {
|
|
30
|
+
* // Use result.url
|
|
31
|
+
* }
|
|
32
|
+
* };
|
|
33
|
+
*
|
|
34
|
+
* return (
|
|
35
|
+
* <input type="file" onChange={(e) => handleFile(e.target.files[0])} />
|
|
36
|
+
* );
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function useUpload(options) {
|
|
41
|
+
const { folder, entityId, config, onSuccess, onError, onProgress } = options;
|
|
42
|
+
// Merge config with defaults
|
|
43
|
+
const uploadConfig = {
|
|
44
|
+
...DEFAULT_UPLOAD_CONFIG,
|
|
45
|
+
...config,
|
|
46
|
+
};
|
|
47
|
+
// State
|
|
48
|
+
const [status, setStatus] = useState('idle');
|
|
49
|
+
const [progress, setProgress] = useState(null);
|
|
50
|
+
const [error, setError] = useState(null);
|
|
51
|
+
// Abort controller ref for cancellation
|
|
52
|
+
const abortControllerRef = useRef(null);
|
|
53
|
+
/**
|
|
54
|
+
* Upload a single file
|
|
55
|
+
*/
|
|
56
|
+
const upload = useCallback(async (file) => {
|
|
57
|
+
// Validate file
|
|
58
|
+
const validation = validateFile(file, uploadConfig);
|
|
59
|
+
if (!validation.valid) {
|
|
60
|
+
const errorMsg = validation.error || 'File validation failed';
|
|
61
|
+
setError(errorMsg);
|
|
62
|
+
setStatus('error');
|
|
63
|
+
onError?.(errorMsg);
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
filename: file.name,
|
|
67
|
+
size: file.size,
|
|
68
|
+
contentType: file.type,
|
|
69
|
+
error: errorMsg,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Reset state
|
|
73
|
+
setError(null);
|
|
74
|
+
setStatus('preparing');
|
|
75
|
+
setProgress(null);
|
|
76
|
+
// Create abort controller
|
|
77
|
+
abortControllerRef.current = new AbortController();
|
|
78
|
+
const signal = abortControllerRef.current.signal;
|
|
79
|
+
try {
|
|
80
|
+
// Step 1: Get presigned URL from API
|
|
81
|
+
const presignResponse = await fetch('/api/media/presign', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
filename: file.name,
|
|
86
|
+
contentType: file.type,
|
|
87
|
+
folder,
|
|
88
|
+
entityId,
|
|
89
|
+
}),
|
|
90
|
+
signal,
|
|
91
|
+
});
|
|
92
|
+
if (!presignResponse.ok) {
|
|
93
|
+
const errorData = await presignResponse.json().catch(() => ({}));
|
|
94
|
+
throw new Error(errorData.error || 'Failed to get upload URL');
|
|
95
|
+
}
|
|
96
|
+
const presignData = await presignResponse.json();
|
|
97
|
+
if (!presignData.success || !presignData.data) {
|
|
98
|
+
throw new Error(presignData.error || 'Failed to get upload URL');
|
|
99
|
+
}
|
|
100
|
+
// Step 2: Upload file directly to R2
|
|
101
|
+
setStatus('uploading');
|
|
102
|
+
// Use XMLHttpRequest for progress tracking
|
|
103
|
+
const uploadResult = await uploadWithProgress(presignData.data.uploadUrl, file, (prog) => {
|
|
104
|
+
setProgress(prog);
|
|
105
|
+
onProgress?.(prog);
|
|
106
|
+
}, signal);
|
|
107
|
+
if (!uploadResult.success) {
|
|
108
|
+
throw new Error(uploadResult.error || 'Upload failed');
|
|
109
|
+
}
|
|
110
|
+
// Success
|
|
111
|
+
setStatus('success');
|
|
112
|
+
setProgress({ loaded: file.size, total: file.size, percentage: 100 });
|
|
113
|
+
const result = {
|
|
114
|
+
success: true,
|
|
115
|
+
url: presignData.data.publicUrl,
|
|
116
|
+
filename: file.name,
|
|
117
|
+
size: file.size,
|
|
118
|
+
contentType: file.type,
|
|
119
|
+
};
|
|
120
|
+
onSuccess?.(result);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// Handle abort
|
|
125
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
126
|
+
setStatus('idle');
|
|
127
|
+
setError(null);
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
filename: file.name,
|
|
131
|
+
size: file.size,
|
|
132
|
+
contentType: file.type,
|
|
133
|
+
error: 'Upload cancelled',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Handle error
|
|
137
|
+
const errorMsg = err instanceof Error ? err.message : 'Upload failed';
|
|
138
|
+
setError(errorMsg);
|
|
139
|
+
setStatus('error');
|
|
140
|
+
onError?.(errorMsg);
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
filename: file.name,
|
|
144
|
+
size: file.size,
|
|
145
|
+
contentType: file.type,
|
|
146
|
+
error: errorMsg,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
abortControllerRef.current = null;
|
|
151
|
+
}
|
|
152
|
+
}, [folder, entityId, uploadConfig, onSuccess, onError, onProgress]);
|
|
153
|
+
/**
|
|
154
|
+
* Upload multiple files
|
|
155
|
+
*/
|
|
156
|
+
const uploadMultiple = useCallback(async (files) => {
|
|
157
|
+
// Validate max files
|
|
158
|
+
if (files.length > uploadConfig.maxFiles) {
|
|
159
|
+
const errorMsg = `Maximum ${uploadConfig.maxFiles} files allowed`;
|
|
160
|
+
setError(errorMsg);
|
|
161
|
+
onError?.(errorMsg);
|
|
162
|
+
return files.map((f) => ({
|
|
163
|
+
success: false,
|
|
164
|
+
filename: f.name,
|
|
165
|
+
size: f.size,
|
|
166
|
+
contentType: f.type,
|
|
167
|
+
error: errorMsg,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
// Upload sequentially to avoid overwhelming the server
|
|
171
|
+
const results = [];
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
const result = await upload(file);
|
|
174
|
+
results.push(result);
|
|
175
|
+
// Stop if there's an error (optional - could continue)
|
|
176
|
+
if (!result.success) {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return results;
|
|
181
|
+
}, [upload, uploadConfig.maxFiles, onError]);
|
|
182
|
+
/**
|
|
183
|
+
* Reset state to idle
|
|
184
|
+
*/
|
|
185
|
+
const reset = useCallback(() => {
|
|
186
|
+
// Cancel any in-progress upload
|
|
187
|
+
if (abortControllerRef.current) {
|
|
188
|
+
abortControllerRef.current.abort();
|
|
189
|
+
abortControllerRef.current = null;
|
|
190
|
+
}
|
|
191
|
+
setStatus('idle');
|
|
192
|
+
setProgress(null);
|
|
193
|
+
setError(null);
|
|
194
|
+
}, []);
|
|
195
|
+
return {
|
|
196
|
+
upload,
|
|
197
|
+
uploadMultiple,
|
|
198
|
+
status,
|
|
199
|
+
progress,
|
|
200
|
+
isUploading: status === 'preparing' || status === 'uploading',
|
|
201
|
+
error,
|
|
202
|
+
reset,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Helper Functions
|
|
207
|
+
// =============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Upload file with progress tracking using XMLHttpRequest
|
|
210
|
+
*/
|
|
211
|
+
function uploadWithProgress(url, file, onProgress, signal) {
|
|
212
|
+
return new Promise((resolve) => {
|
|
213
|
+
const xhr = new XMLHttpRequest();
|
|
214
|
+
// Handle abort
|
|
215
|
+
if (signal) {
|
|
216
|
+
signal.addEventListener('abort', () => {
|
|
217
|
+
xhr.abort();
|
|
218
|
+
resolve({ success: false, error: 'Upload cancelled' });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Track progress
|
|
222
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
223
|
+
if (event.lengthComputable) {
|
|
224
|
+
onProgress({
|
|
225
|
+
loaded: event.loaded,
|
|
226
|
+
total: event.total,
|
|
227
|
+
percentage: Math.round((event.loaded / event.total) * 100),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// Handle completion
|
|
232
|
+
xhr.addEventListener('load', () => {
|
|
233
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
234
|
+
resolve({ success: true });
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
resolve({ success: false, error: `Upload failed with status ${xhr.status}` });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Handle error
|
|
241
|
+
xhr.addEventListener('error', () => {
|
|
242
|
+
resolve({ success: false, error: 'Network error during upload' });
|
|
243
|
+
});
|
|
244
|
+
// Handle abort
|
|
245
|
+
xhr.addEventListener('abort', () => {
|
|
246
|
+
resolve({ success: false, error: 'Upload cancelled' });
|
|
247
|
+
});
|
|
248
|
+
// Send request
|
|
249
|
+
xhr.open('PUT', url);
|
|
250
|
+
xhr.setRequestHeader('Content-Type', file.type);
|
|
251
|
+
xhr.send(file);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// Exports
|
|
256
|
+
// =============================================================================
|
|
257
|
+
export default useUpload;
|
|
258
|
+
//# sourceMappingURL=useUpload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useUpload.js","sourceRoot":"","sources":["../../../src/media/hooks/useUpload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media
|
|
3
|
+
*
|
|
4
|
+
* Media upload module for R2 storage.
|
|
5
|
+
*
|
|
6
|
+
* This module provides:
|
|
7
|
+
* - Server-side presigned URL generation
|
|
8
|
+
* - Client-side upload hooks and components
|
|
9
|
+
* - API route handlers for Next.js
|
|
10
|
+
*
|
|
11
|
+
* ## Quick Start
|
|
12
|
+
*
|
|
13
|
+
* 1. Add environment variables:
|
|
14
|
+
* ```env
|
|
15
|
+
* R2_ACCOUNT_ID=xxx
|
|
16
|
+
* R2_ACCESS_KEY_ID=xxx
|
|
17
|
+
* R2_SECRET_ACCESS_KEY=xxx
|
|
18
|
+
* R2_BUCKET_NAME=xxx
|
|
19
|
+
* R2_PUBLIC_URL=https://pub-xxx.r2.dev
|
|
20
|
+
* STORE_ID=xxx
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 2. Create API routes:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // app/api/media/presign/route.ts
|
|
26
|
+
* export { POST } from '@rovela-ai/sdk/media/api'
|
|
27
|
+
*
|
|
28
|
+
* // app/api/media/route.ts
|
|
29
|
+
* export { DELETE } from '@rovela-ai/sdk/media/api'
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* 3. Use components:
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { ImageGalleryUpload } from '@rovela-ai/sdk/media'
|
|
35
|
+
*
|
|
36
|
+
* <ImageGalleryUpload
|
|
37
|
+
* value={images}
|
|
38
|
+
* onChange={setImages}
|
|
39
|
+
* folder="products"
|
|
40
|
+
* entityId={productId}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Module Structure
|
|
45
|
+
*
|
|
46
|
+
* - `@rovela-ai/sdk/media` - Types, config, components, hooks
|
|
47
|
+
* - `@rovela-ai/sdk/media/server` - Server-side utilities (presign, delete)
|
|
48
|
+
* - `@rovela-ai/sdk/media/api` - Next.js API route handlers
|
|
49
|
+
*
|
|
50
|
+
* @module media
|
|
51
|
+
*/
|
|
52
|
+
export type { MediaStorageConfig, UploadConfig, MediaFolder, PresignedUrlRequest, PresignedUrlResponse, PresignApiRequest, PresignApiResponse, DeleteApiRequest, DeleteApiResponse, MediaApiError, UploadProgress, UploadStatus, UploadResult, UploadState, UseUploadOptions, UseUploadReturn, DropZoneProps, ImageUploadProps, ImageGalleryUploadProps, FileValidation, } from './types';
|
|
53
|
+
export { DEFAULT_UPLOAD_CONFIG, validateFile, generateUniqueFilename, getFileExtension } from './types';
|
|
54
|
+
export { getMediaConfig, isMediaConfigured, requireMediaConfig, defaultUploadConfig, getUploadConfig, buildStorageKey, buildPublicUrl, extractKeyFromUrl, getMimeType, isAllowedImageType, } from './config';
|
|
55
|
+
export { useUpload } from './hooks';
|
|
56
|
+
export { DropZone, ImageUpload, ImageGalleryUpload } from './components';
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAMH,YAAY,EAEV,kBAAkB,EAClB,YAAY,EACZ,WAAW,EAEX,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EAEb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,WAAW,EAEX,gBAAgB,EAChB,eAAe,EAEf,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EAEvB,cAAc,GACf,MAAM,SAAS,CAAA;AAMhB,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAMvG,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,kBAAkB,GACnB,MAAM,UAAU,CAAA;AAMjB,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAMnC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media
|
|
3
|
+
*
|
|
4
|
+
* Media upload module for R2 storage.
|
|
5
|
+
*
|
|
6
|
+
* This module provides:
|
|
7
|
+
* - Server-side presigned URL generation
|
|
8
|
+
* - Client-side upload hooks and components
|
|
9
|
+
* - API route handlers for Next.js
|
|
10
|
+
*
|
|
11
|
+
* ## Quick Start
|
|
12
|
+
*
|
|
13
|
+
* 1. Add environment variables:
|
|
14
|
+
* ```env
|
|
15
|
+
* R2_ACCOUNT_ID=xxx
|
|
16
|
+
* R2_ACCESS_KEY_ID=xxx
|
|
17
|
+
* R2_SECRET_ACCESS_KEY=xxx
|
|
18
|
+
* R2_BUCKET_NAME=xxx
|
|
19
|
+
* R2_PUBLIC_URL=https://pub-xxx.r2.dev
|
|
20
|
+
* STORE_ID=xxx
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 2. Create API routes:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // app/api/media/presign/route.ts
|
|
26
|
+
* export { POST } from '@rovela-ai/sdk/media/api'
|
|
27
|
+
*
|
|
28
|
+
* // app/api/media/route.ts
|
|
29
|
+
* export { DELETE } from '@rovela-ai/sdk/media/api'
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* 3. Use components:
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { ImageGalleryUpload } from '@rovela-ai/sdk/media'
|
|
35
|
+
*
|
|
36
|
+
* <ImageGalleryUpload
|
|
37
|
+
* value={images}
|
|
38
|
+
* onChange={setImages}
|
|
39
|
+
* folder="products"
|
|
40
|
+
* entityId={productId}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Module Structure
|
|
45
|
+
*
|
|
46
|
+
* - `@rovela-ai/sdk/media` - Types, config, components, hooks
|
|
47
|
+
* - `@rovela-ai/sdk/media/server` - Server-side utilities (presign, delete)
|
|
48
|
+
* - `@rovela-ai/sdk/media/api` - Next.js API route handlers
|
|
49
|
+
*
|
|
50
|
+
* @module media
|
|
51
|
+
*/
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Constants & Utilities
|
|
54
|
+
// =============================================================================
|
|
55
|
+
export { DEFAULT_UPLOAD_CONFIG, validateFile, generateUniqueFilename, getFileExtension } from './types';
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Configuration
|
|
58
|
+
// =============================================================================
|
|
59
|
+
export { getMediaConfig, isMediaConfigured, requireMediaConfig, defaultUploadConfig, getUploadConfig, buildStorageKey, buildPublicUrl, extractKeyFromUrl, getMimeType, isAllowedImageType, } from './config';
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Hooks
|
|
62
|
+
// =============================================================================
|
|
63
|
+
export { useUpload } from './hooks';
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Components
|
|
66
|
+
// =============================================================================
|
|
67
|
+
export { DropZone, ImageUpload, ImageGalleryUpload } from './components';
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAmCH,gFAAgF;AAChF,wBAAwB;AACxB,gFAAgF;AAEhF,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAEvG,gFAAgF;AAChF,gBAAgB;AAChB,gFAAgF;AAEhF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,kBAAkB,GACnB,MAAM,UAAU,CAAA;AAEjB,gFAAgF;AAChF,QAAQ;AACR,gFAAgF;AAEhF,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media/server/delete
|
|
3
|
+
*
|
|
4
|
+
* File deletion from R2 storage.
|
|
5
|
+
* Server-side only - do not import in client components.
|
|
6
|
+
*/
|
|
7
|
+
import type { MediaStorageConfig } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Delete a file from R2 storage.
|
|
10
|
+
*
|
|
11
|
+
* @param url - Public URL of the file to delete
|
|
12
|
+
* @param config - Optional storage configuration override
|
|
13
|
+
* @returns True if deletion was successful
|
|
14
|
+
* @throws Error if URL is invalid or doesn't belong to this store
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* await deleteFile('https://pub-xxx.r2.dev/stores/abc123/products/img.jpg');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function deleteFile(url: string, config?: MediaStorageConfig): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Delete a file by its storage key.
|
|
24
|
+
* Use this when you have the key directly instead of the URL.
|
|
25
|
+
*
|
|
26
|
+
* @param key - Storage key of the file
|
|
27
|
+
* @param config - Optional storage configuration override
|
|
28
|
+
* @returns True if deletion was successful
|
|
29
|
+
*/
|
|
30
|
+
export declare function deleteFileByKey(key: string, config?: MediaStorageConfig): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete multiple files from R2 storage.
|
|
33
|
+
*
|
|
34
|
+
* @param urls - Array of public URLs to delete
|
|
35
|
+
* @param config - Optional storage configuration override
|
|
36
|
+
* @returns Object with results for each URL
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const results = await deleteFiles([
|
|
41
|
+
* 'https://pub-xxx.r2.dev/stores/abc123/products/img1.jpg',
|
|
42
|
+
* 'https://pub-xxx.r2.dev/stores/abc123/products/img2.jpg',
|
|
43
|
+
* ]);
|
|
44
|
+
* // { succeeded: ['url1', 'url2'], failed: [] }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function deleteFiles(urls: string[], config?: MediaStorageConfig): Promise<{
|
|
48
|
+
succeeded: string[];
|
|
49
|
+
failed: string[];
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Safely delete a file - returns false instead of throwing if file doesn't exist.
|
|
53
|
+
*
|
|
54
|
+
* @param url - Public URL of the file to delete
|
|
55
|
+
* @param config - Optional storage configuration override
|
|
56
|
+
* @returns True if deleted, false if not found or invalid
|
|
57
|
+
*/
|
|
58
|
+
export declare function safeDeleteFile(url: string, config?: MediaStorageConfig): Promise<boolean>;
|
|
59
|
+
//# sourceMappingURL=delete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../src/media/server/delete.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AASlD;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAoEpD;AAMD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAMlB"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media/server/delete
|
|
3
|
+
*
|
|
4
|
+
* File deletion from R2 storage.
|
|
5
|
+
* Server-side only - do not import in client components.
|
|
6
|
+
*/
|
|
7
|
+
import { DeleteObjectCommand, DeleteObjectsCommand } from '@aws-sdk/client-s3';
|
|
8
|
+
import { extractKeyFromUrl, requireMediaConfig } from '../config';
|
|
9
|
+
import { getR2Client, getBucketName, getPublicUrlBase } from './r2-client';
|
|
10
|
+
import { validateStoreUrl } from './presign';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Single File Deletion
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Delete a file from R2 storage.
|
|
16
|
+
*
|
|
17
|
+
* @param url - Public URL of the file to delete
|
|
18
|
+
* @param config - Optional storage configuration override
|
|
19
|
+
* @returns True if deletion was successful
|
|
20
|
+
* @throws Error if URL is invalid or doesn't belong to this store
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* await deleteFile('https://pub-xxx.r2.dev/stores/abc123/products/img.jpg');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function deleteFile(url, config) {
|
|
28
|
+
const resolvedConfig = config || requireMediaConfig();
|
|
29
|
+
// Validate URL belongs to this store
|
|
30
|
+
if (!validateStoreUrl(url, resolvedConfig)) {
|
|
31
|
+
throw new Error('Invalid URL: does not belong to this store');
|
|
32
|
+
}
|
|
33
|
+
// Extract key from URL
|
|
34
|
+
const publicUrlBase = getPublicUrlBase(resolvedConfig);
|
|
35
|
+
const key = extractKeyFromUrl(publicUrlBase, url);
|
|
36
|
+
if (!key) {
|
|
37
|
+
throw new Error('Invalid URL: could not extract storage key');
|
|
38
|
+
}
|
|
39
|
+
const client = getR2Client(resolvedConfig);
|
|
40
|
+
const bucketName = getBucketName(resolvedConfig);
|
|
41
|
+
try {
|
|
42
|
+
await client.send(new DeleteObjectCommand({
|
|
43
|
+
Bucket: bucketName,
|
|
44
|
+
Key: key,
|
|
45
|
+
}));
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('[Media] Delete failed:', error);
|
|
50
|
+
throw new Error('Failed to delete file');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Delete a file by its storage key.
|
|
55
|
+
* Use this when you have the key directly instead of the URL.
|
|
56
|
+
*
|
|
57
|
+
* @param key - Storage key of the file
|
|
58
|
+
* @param config - Optional storage configuration override
|
|
59
|
+
* @returns True if deletion was successful
|
|
60
|
+
*/
|
|
61
|
+
export async function deleteFileByKey(key, config) {
|
|
62
|
+
const resolvedConfig = config || requireMediaConfig();
|
|
63
|
+
const client = getR2Client(resolvedConfig);
|
|
64
|
+
const bucketName = getBucketName(resolvedConfig);
|
|
65
|
+
// Validate key belongs to this store
|
|
66
|
+
const expectedPrefix = `stores/${resolvedConfig.storeId}/`;
|
|
67
|
+
if (!key.startsWith(expectedPrefix)) {
|
|
68
|
+
throw new Error('Invalid key: does not belong to this store');
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await client.send(new DeleteObjectCommand({
|
|
72
|
+
Bucket: bucketName,
|
|
73
|
+
Key: key,
|
|
74
|
+
}));
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('[Media] Delete by key failed:', error);
|
|
79
|
+
throw new Error('Failed to delete file');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Batch Deletion
|
|
84
|
+
// =============================================================================
|
|
85
|
+
/**
|
|
86
|
+
* Delete multiple files from R2 storage.
|
|
87
|
+
*
|
|
88
|
+
* @param urls - Array of public URLs to delete
|
|
89
|
+
* @param config - Optional storage configuration override
|
|
90
|
+
* @returns Object with results for each URL
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const results = await deleteFiles([
|
|
95
|
+
* 'https://pub-xxx.r2.dev/stores/abc123/products/img1.jpg',
|
|
96
|
+
* 'https://pub-xxx.r2.dev/stores/abc123/products/img2.jpg',
|
|
97
|
+
* ]);
|
|
98
|
+
* // { succeeded: ['url1', 'url2'], failed: [] }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export async function deleteFiles(urls, config) {
|
|
102
|
+
const resolvedConfig = config || requireMediaConfig();
|
|
103
|
+
const publicUrlBase = getPublicUrlBase(resolvedConfig);
|
|
104
|
+
// Filter and extract keys
|
|
105
|
+
const validEntries = [];
|
|
106
|
+
const invalidUrls = [];
|
|
107
|
+
for (const url of urls) {
|
|
108
|
+
if (!validateStoreUrl(url, resolvedConfig)) {
|
|
109
|
+
invalidUrls.push(url);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const key = extractKeyFromUrl(publicUrlBase, url);
|
|
113
|
+
if (!key) {
|
|
114
|
+
invalidUrls.push(url);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
validEntries.push({ url, key });
|
|
118
|
+
}
|
|
119
|
+
if (validEntries.length === 0) {
|
|
120
|
+
return { succeeded: [], failed: urls };
|
|
121
|
+
}
|
|
122
|
+
const client = getR2Client(resolvedConfig);
|
|
123
|
+
const bucketName = getBucketName(resolvedConfig);
|
|
124
|
+
try {
|
|
125
|
+
// Use batch delete for efficiency
|
|
126
|
+
await client.send(new DeleteObjectsCommand({
|
|
127
|
+
Bucket: bucketName,
|
|
128
|
+
Delete: {
|
|
129
|
+
Objects: validEntries.map(({ key }) => ({ Key: key })),
|
|
130
|
+
Quiet: true,
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
133
|
+
return {
|
|
134
|
+
succeeded: validEntries.map(({ url }) => url),
|
|
135
|
+
failed: invalidUrls,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.error('[Media] Batch delete failed:', error);
|
|
140
|
+
// On error, try to delete individually
|
|
141
|
+
const succeeded = [];
|
|
142
|
+
const failed = [...invalidUrls];
|
|
143
|
+
for (const { url, key } of validEntries) {
|
|
144
|
+
try {
|
|
145
|
+
await client.send(new DeleteObjectCommand({
|
|
146
|
+
Bucket: bucketName,
|
|
147
|
+
Key: key,
|
|
148
|
+
}));
|
|
149
|
+
succeeded.push(url);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
failed.push(url);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { succeeded, failed };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// Safe Deletion (with existence check)
|
|
160
|
+
// =============================================================================
|
|
161
|
+
/**
|
|
162
|
+
* Safely delete a file - returns false instead of throwing if file doesn't exist.
|
|
163
|
+
*
|
|
164
|
+
* @param url - Public URL of the file to delete
|
|
165
|
+
* @param config - Optional storage configuration override
|
|
166
|
+
* @returns True if deleted, false if not found or invalid
|
|
167
|
+
*/
|
|
168
|
+
export async function safeDeleteFile(url, config) {
|
|
169
|
+
try {
|
|
170
|
+
return await deleteFile(url, config);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../../src/media/server/delete.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media/server
|
|
3
|
+
*
|
|
4
|
+
* Server-side media utilities.
|
|
5
|
+
* Only import this in server components and API routes.
|
|
6
|
+
*/
|
|
7
|
+
export { getR2Client, clearR2ClientCache, getBucketName, getPublicUrlBase, getStoreId } from './r2-client';
|
|
8
|
+
export { generatePresignedUrl, generatePresignedUrls, isR2Url, validateStoreUrl, type GeneratePresignedUrlOptions, } from './presign';
|
|
9
|
+
export { deleteFile, deleteFileByKey, deleteFiles, safeDeleteFile } from './delete';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/media/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAG1G,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,OAAO,EACP,gBAAgB,EAChB,KAAK,2BAA2B,GACjC,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA"}
|