squarefi-bff-api-module 1.30.10 → 1.31.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/CHANGELOG.md +296 -1
- package/FIXED_RLS_ERROR.md +146 -0
- package/QUICK_TEST.md +127 -0
- package/README.md +87 -10
- package/STORAGE_MODULE_SUMMARY.md +228 -0
- package/TEST_INSTRUCTIONS.md +122 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useFileUpload.d.ts +18 -3
- package/dist/hooks/useFileUpload.js +19 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/fileStorage.d.ts +8 -4
- package/dist/utils/fileStorage.js +8 -4
- package/docs/AUTH_TOKEN_USAGE.md +290 -0
- package/docs/BACKEND_SERVICE_URL.md +334 -0
- package/docs/FRONTEND_STORAGE_GUIDE.md +529 -0
- package/docs/READY_TO_USE_COMPONENT.tsx +395 -0
- package/docs/STORAGE_MODULE.md +490 -0
- package/docs/STORAGE_QUICK_START.md +76 -0
- package/package.json +1 -1
- package/scripts/supabase-storage-setup.sql +223 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFileUpload.ts +129 -0
- package/src/index.ts +1 -0
- package/src/utils/fileStorage.ts +367 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# Frontend Storage Module Guide
|
|
2
|
+
|
|
3
|
+
A simple and quick guide for using the file upload module in React applications.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Environment Variables Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Copy env.example to .env and fill in your values
|
|
11
|
+
# See env.example in repository root for all available variables
|
|
12
|
+
|
|
13
|
+
# .env or .env.local
|
|
14
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
15
|
+
SUPABASE_PUBLIC_KEY=your-anon-key
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 2. Supabase Setup (one time)
|
|
19
|
+
|
|
20
|
+
1. Open Supabase Dashboard → SQL Editor
|
|
21
|
+
2. Copy and execute the content of `scripts/supabase-storage-setup.sql`
|
|
22
|
+
3. Edit the `is_super_admin()` function to match your user schema
|
|
23
|
+
|
|
24
|
+
### 3. Done! Use the hooks
|
|
25
|
+
|
|
26
|
+
## 📤 Upload Files with Hook
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import React from 'react';
|
|
30
|
+
import { useFileUpload } from 'squarefi-bff-api-module';
|
|
31
|
+
|
|
32
|
+
function FileUploadComponent({ userId }: { userId: string }) {
|
|
33
|
+
const { upload, uploading, progress, error, result } = useFileUpload({
|
|
34
|
+
userId,
|
|
35
|
+
onSuccess: (result) => {
|
|
36
|
+
console.log('File uploaded:', result.path);
|
|
37
|
+
console.log('Link:', result.signedUrl);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
42
|
+
const file = e.target.files?.[0];
|
|
43
|
+
if (file) {
|
|
44
|
+
await upload(file);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div>
|
|
50
|
+
<input
|
|
51
|
+
type="file"
|
|
52
|
+
onChange={handleFileChange}
|
|
53
|
+
disabled={uploading}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
{uploading && <p>Uploading... {progress}%</p>}
|
|
57
|
+
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
|
|
58
|
+
{result?.success && (
|
|
59
|
+
<p style={{ color: 'green' }}>
|
|
60
|
+
Successfully uploaded!
|
|
61
|
+
<a href={result.signedUrl} target="_blank" rel="noopener noreferrer">
|
|
62
|
+
Open file
|
|
63
|
+
</a>
|
|
64
|
+
</p>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default FileUploadComponent;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 📋 File List with Hook
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import React from 'react';
|
|
77
|
+
import { useUserFiles } from 'squarefi-bff-api-module';
|
|
78
|
+
|
|
79
|
+
function FileListComponent({ userId }: { userId: string }) {
|
|
80
|
+
const { files, loading, deleteOne, reload } = useUserFiles({
|
|
81
|
+
userId,
|
|
82
|
+
autoLoad: true,
|
|
83
|
+
autoGenerateUrls: true, // Automatically get URLs
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (loading) return <div>Loading...</div>;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div>
|
|
90
|
+
<button onClick={reload}>Refresh</button>
|
|
91
|
+
|
|
92
|
+
<ul>
|
|
93
|
+
{files.map((file) => (
|
|
94
|
+
<li key={file.id}>
|
|
95
|
+
<a href={file.signedUrl} target="_blank" rel="noopener noreferrer">
|
|
96
|
+
{file.name}
|
|
97
|
+
</a>
|
|
98
|
+
<span> ({new Date(file.created_at).toLocaleDateString()})</span>
|
|
99
|
+
<button onClick={() => deleteOne(file.name)}>Delete</button>
|
|
100
|
+
</li>
|
|
101
|
+
))}
|
|
102
|
+
</ul>
|
|
103
|
+
|
|
104
|
+
{files.length === 0 && <p>No files</p>}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default FileListComponent;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 🎨 Complete Example: Component with Upload and List
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import React, { useState } from 'react';
|
|
116
|
+
import { useFileUpload, useUserFiles, DEFAULT_BUCKET } from 'squarefi-bff-api-module';
|
|
117
|
+
|
|
118
|
+
interface FileManagerProps {
|
|
119
|
+
userId: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const FileManager: React.FC<FileManagerProps> = ({ userId }) => {
|
|
123
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
124
|
+
|
|
125
|
+
// Upload hook
|
|
126
|
+
const { upload, uploading, progress, error: uploadError } = useFileUpload({
|
|
127
|
+
userId,
|
|
128
|
+
bucket: DEFAULT_BUCKET,
|
|
129
|
+
onSuccess: () => {
|
|
130
|
+
setSelectedFile(null);
|
|
131
|
+
reload(); // Refresh list after upload
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// File list hook
|
|
136
|
+
const {
|
|
137
|
+
files,
|
|
138
|
+
loading,
|
|
139
|
+
error: listError,
|
|
140
|
+
reload,
|
|
141
|
+
deleteOne
|
|
142
|
+
} = useUserFiles({
|
|
143
|
+
userId,
|
|
144
|
+
autoLoad: true,
|
|
145
|
+
autoGenerateUrls: true,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
149
|
+
const file = e.target.files?.[0];
|
|
150
|
+
setSelectedFile(file || null);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleUpload = async () => {
|
|
154
|
+
if (selectedFile) {
|
|
155
|
+
await upload(selectedFile);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const handleDelete = async (fileName: string) => {
|
|
160
|
+
if (window.confirm(`Delete file ${fileName}?`)) {
|
|
161
|
+
await deleteOne(fileName);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
|
|
167
|
+
<h2>My Files</h2>
|
|
168
|
+
|
|
169
|
+
{/* Upload section */}
|
|
170
|
+
<div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
|
|
171
|
+
<h3>Upload File</h3>
|
|
172
|
+
<input type="file" onChange={handleFileSelect} disabled={uploading} />
|
|
173
|
+
|
|
174
|
+
{selectedFile && (
|
|
175
|
+
<div style={{ marginTop: '10px' }}>
|
|
176
|
+
<p>Selected: {selectedFile.name} ({(selectedFile.size / 1024 / 1024).toFixed(2)} MB)</p>
|
|
177
|
+
<button onClick={handleUpload} disabled={uploading}>
|
|
178
|
+
{uploading ? `Uploading... ${progress}%` : 'Upload'}
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{uploadError && (
|
|
184
|
+
<p style={{ color: 'red', marginTop: '10px' }}>❌ {uploadError}</p>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* File list section */}
|
|
189
|
+
<div>
|
|
190
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
|
|
191
|
+
<h3>Uploaded Files ({files.length})</h3>
|
|
192
|
+
<button onClick={reload} disabled={loading}>
|
|
193
|
+
🔄 Refresh
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{loading && <p>Loading list...</p>}
|
|
198
|
+
|
|
199
|
+
{listError && <p style={{ color: 'red' }}>❌ {listError}</p>}
|
|
200
|
+
|
|
201
|
+
{!loading && files.length === 0 && (
|
|
202
|
+
<p style={{ textAlign: 'center', color: '#888' }}>
|
|
203
|
+
You don't have any uploaded files yet
|
|
204
|
+
</p>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{files.length > 0 && (
|
|
208
|
+
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
209
|
+
<thead>
|
|
210
|
+
<tr style={{ borderBottom: '2px solid #ddd' }}>
|
|
211
|
+
<th style={{ textAlign: 'left', padding: '10px' }}>File</th>
|
|
212
|
+
<th style={{ textAlign: 'left', padding: '10px' }}>Date</th>
|
|
213
|
+
<th style={{ textAlign: 'right', padding: '10px' }}>Actions</th>
|
|
214
|
+
</tr>
|
|
215
|
+
</thead>
|
|
216
|
+
<tbody>
|
|
217
|
+
{files.map((file) => (
|
|
218
|
+
<tr key={file.id} style={{ borderBottom: '1px solid #eee' }}>
|
|
219
|
+
<td style={{ padding: '10px' }}>
|
|
220
|
+
<a
|
|
221
|
+
href={file.signedUrl}
|
|
222
|
+
target="_blank"
|
|
223
|
+
rel="noopener noreferrer"
|
|
224
|
+
style={{ color: '#007bff', textDecoration: 'none' }}
|
|
225
|
+
>
|
|
226
|
+
📄 {file.name}
|
|
227
|
+
</a>
|
|
228
|
+
</td>
|
|
229
|
+
<td style={{ padding: '10px', color: '#666' }}>
|
|
230
|
+
{new Date(file.created_at).toLocaleDateString()}
|
|
231
|
+
</td>
|
|
232
|
+
<td style={{ padding: '10px', textAlign: 'right' }}>
|
|
233
|
+
<button
|
|
234
|
+
onClick={() => handleDelete(file.name)}
|
|
235
|
+
style={{
|
|
236
|
+
background: '#dc3545',
|
|
237
|
+
color: 'white',
|
|
238
|
+
border: 'none',
|
|
239
|
+
padding: '5px 10px',
|
|
240
|
+
borderRadius: '4px',
|
|
241
|
+
cursor: 'pointer'
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
🗑️ Delete
|
|
245
|
+
</button>
|
|
246
|
+
</td>
|
|
247
|
+
</tr>
|
|
248
|
+
))}
|
|
249
|
+
</tbody>
|
|
250
|
+
</table>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 🖼️ Image Upload with Preview
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import React, { useState } from 'react';
|
|
262
|
+
import { useFileUpload, IMAGES_BUCKET } from 'squarefi-bff-api-module';
|
|
263
|
+
|
|
264
|
+
export const ImageUploader: React.FC<{ userId: string }> = ({ userId }) => {
|
|
265
|
+
const [preview, setPreview] = useState<string | null>(null);
|
|
266
|
+
|
|
267
|
+
const { upload, uploading, error, result } = useFileUpload({
|
|
268
|
+
userId,
|
|
269
|
+
bucket: IMAGES_BUCKET,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
273
|
+
const file = e.target.files?.[0];
|
|
274
|
+
if (!file) return;
|
|
275
|
+
|
|
276
|
+
// Create preview
|
|
277
|
+
const reader = new FileReader();
|
|
278
|
+
reader.onloadend = () => {
|
|
279
|
+
setPreview(reader.result as string);
|
|
280
|
+
};
|
|
281
|
+
reader.readAsDataURL(file);
|
|
282
|
+
|
|
283
|
+
// Upload
|
|
284
|
+
await upload(file);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<div>
|
|
289
|
+
<input
|
|
290
|
+
type="file"
|
|
291
|
+
accept="image/*"
|
|
292
|
+
onChange={handleFileChange}
|
|
293
|
+
disabled={uploading}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
{preview && (
|
|
297
|
+
<div style={{ marginTop: '20px' }}>
|
|
298
|
+
<img
|
|
299
|
+
src={preview}
|
|
300
|
+
alt="Preview"
|
|
301
|
+
style={{ maxWidth: '300px', borderRadius: '8px' }}
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
{uploading && <p>Uploading image...</p>}
|
|
307
|
+
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
308
|
+
{result?.success && (
|
|
309
|
+
<p style={{ color: 'green' }}>✅ Image uploaded!</p>
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## 🎯 Drag & Drop Upload
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
import React, { useState, useCallback } from 'react';
|
|
320
|
+
import { useFileUpload } from 'squarefi-bff-api-module';
|
|
321
|
+
|
|
322
|
+
export const DragDropUploader: React.FC<{ userId: string }> = ({ userId }) => {
|
|
323
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
324
|
+
const { upload, uploading } = useFileUpload({ userId });
|
|
325
|
+
|
|
326
|
+
const handleDrop = useCallback(
|
|
327
|
+
async (e: React.DragEvent) => {
|
|
328
|
+
e.preventDefault();
|
|
329
|
+
setIsDragging(false);
|
|
330
|
+
|
|
331
|
+
const files = Array.from(e.dataTransfer.files);
|
|
332
|
+
for (const file of files) {
|
|
333
|
+
await upload(file);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
[upload]
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div
|
|
341
|
+
onDragOver={(e) => {
|
|
342
|
+
e.preventDefault();
|
|
343
|
+
setIsDragging(true);
|
|
344
|
+
}}
|
|
345
|
+
onDragLeave={() => setIsDragging(false)}
|
|
346
|
+
onDrop={handleDrop}
|
|
347
|
+
style={{
|
|
348
|
+
border: `3px dashed ${isDragging ? '#007bff' : '#ccc'}`,
|
|
349
|
+
borderRadius: '12px',
|
|
350
|
+
padding: '60px 40px',
|
|
351
|
+
textAlign: 'center',
|
|
352
|
+
backgroundColor: isDragging ? '#f0f8ff' : '#fafafa',
|
|
353
|
+
transition: 'all 0.3s ease',
|
|
354
|
+
cursor: 'pointer',
|
|
355
|
+
}}
|
|
356
|
+
>
|
|
357
|
+
{uploading ? (
|
|
358
|
+
<p>⏳ Uploading files...</p>
|
|
359
|
+
) : (
|
|
360
|
+
<div>
|
|
361
|
+
<p style={{ fontSize: '48px', margin: '0 0 10px 0' }}>📁</p>
|
|
362
|
+
<p style={{ fontSize: '18px', margin: 0 }}>
|
|
363
|
+
Drag and drop files here
|
|
364
|
+
</p>
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## 🔧 Hook APIs
|
|
373
|
+
|
|
374
|
+
### useFileUpload
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
const {
|
|
378
|
+
upload, // (file: File, fileName?: string) => Promise<UploadFileResult>
|
|
379
|
+
uploading, // boolean - is uploading
|
|
380
|
+
progress, // number - upload progress (0-100)
|
|
381
|
+
error, // string | null - error text
|
|
382
|
+
result, // UploadFileResult | null - upload result
|
|
383
|
+
reset, // () => void - reset state
|
|
384
|
+
} = useFileUpload({
|
|
385
|
+
userId: 'user-id', // required
|
|
386
|
+
bucket?: 'user-files', // optional
|
|
387
|
+
onSuccess?: (result) => {}, // success callback
|
|
388
|
+
onError?: (error) => {}, // error callback
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### useUserFiles
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
const {
|
|
396
|
+
files, // FileItem[] - file list
|
|
397
|
+
loading, // boolean - is loading
|
|
398
|
+
error, // string | null - error text
|
|
399
|
+
reload, // () => Promise<void> - reload list
|
|
400
|
+
deleteOne, // (fileName: string) => Promise<boolean>
|
|
401
|
+
deleteMultiple, // (fileNames: string[]) => Promise<boolean>
|
|
402
|
+
getFileUrl, // (fileName: string) => Promise<string | null>
|
|
403
|
+
} = useUserFiles({
|
|
404
|
+
userId: 'user-id', // required
|
|
405
|
+
bucket?: 'user-files', // optional
|
|
406
|
+
autoLoad?: true, // auto-load on mount
|
|
407
|
+
autoGenerateUrls?: false, // auto-generate signed URLs
|
|
408
|
+
urlExpiresIn?: 3600, // URL expiration in seconds
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## 💡 Tips
|
|
413
|
+
|
|
414
|
+
### 1. Use different buckets for different file types
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
import { DOCUMENTS_BUCKET, IMAGES_BUCKET } from 'squarefi-bff-api-module';
|
|
418
|
+
|
|
419
|
+
// For documents
|
|
420
|
+
const { upload: uploadDoc } = useFileUpload({
|
|
421
|
+
userId,
|
|
422
|
+
bucket: DOCUMENTS_BUCKET
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// For images
|
|
426
|
+
const { upload: uploadImage } = useFileUpload({
|
|
427
|
+
userId,
|
|
428
|
+
bucket: IMAGES_BUCKET
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### 2. Validate files before upload
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
436
|
+
const file = e.target.files?.[0];
|
|
437
|
+
if (!file) return;
|
|
438
|
+
|
|
439
|
+
// Check size (max 10MB)
|
|
440
|
+
if (file.size > 10 * 1024 * 1024) {
|
|
441
|
+
alert('File too large. Maximum 10MB');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Check type
|
|
446
|
+
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
|
|
447
|
+
if (!allowedTypes.includes(file.type)) {
|
|
448
|
+
alert('Unsupported file type');
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
await upload(file);
|
|
453
|
+
};
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### 3. Add unique prefix to file name
|
|
457
|
+
|
|
458
|
+
```tsx
|
|
459
|
+
// Avoid name conflicts
|
|
460
|
+
const timestamp = Date.now();
|
|
461
|
+
const randomId = Math.random().toString(36).substring(7);
|
|
462
|
+
const uniqueFileName = `${timestamp}-${randomId}-${file.name}`;
|
|
463
|
+
|
|
464
|
+
await upload(file, uniqueFileName);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### 4. Error handling
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
const { upload } = useFileUpload({
|
|
471
|
+
userId,
|
|
472
|
+
onSuccess: (result) => {
|
|
473
|
+
toast.success('File uploaded!');
|
|
474
|
+
// Save result.path in your DB
|
|
475
|
+
},
|
|
476
|
+
onError: (error) => {
|
|
477
|
+
toast.error(`Error: ${error}`);
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## 🔒 Security
|
|
483
|
+
|
|
484
|
+
All files are automatically protected:
|
|
485
|
+
|
|
486
|
+
- ✅ User can only upload files to their own folder
|
|
487
|
+
- ✅ User can only see their own files
|
|
488
|
+
- ✅ Superadmins have access to all files
|
|
489
|
+
- ✅ Use `signedUrl` for temporary secure access
|
|
490
|
+
- ✅ Don't use `publicUrl` for confidential data
|
|
491
|
+
|
|
492
|
+
## 📚 Additional Resources
|
|
493
|
+
|
|
494
|
+
- [Full Documentation](./STORAGE_MODULE.md)
|
|
495
|
+
- [Examples for Different Frameworks](./STORAGE_EXAMPLES.md)
|
|
496
|
+
- [SQL Setup Script](../scripts/supabase-storage-setup.sql)
|
|
497
|
+
|
|
498
|
+
## ❓ FAQ
|
|
499
|
+
|
|
500
|
+
**Q: How to get current userId?**
|
|
501
|
+
A: Use your authentication system (Supabase Auth, JWT token, etc.)
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
import { supabaseClient } from 'squarefi-bff-api-module';
|
|
505
|
+
|
|
506
|
+
const userId = supabaseClient?.auth.user()?.id;
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**Q: Files not uploading, what to do?**
|
|
510
|
+
A: Check:
|
|
511
|
+
1. SQL script executed in Supabase
|
|
512
|
+
2. Environment variables are set
|
|
513
|
+
3. User is authenticated in Supabase
|
|
514
|
+
|
|
515
|
+
**Q: How to change maximum file size?**
|
|
516
|
+
A: In Supabase Dashboard → Settings → Storage → File Size Limit
|
|
517
|
+
|
|
518
|
+
**Q: Can I use without React?**
|
|
519
|
+
A: Yes! Use functions directly: `uploadFile()`, `deleteFile()`, etc.
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
import { uploadFile } from 'squarefi-bff-api-module';
|
|
523
|
+
|
|
524
|
+
const result = await uploadFile({
|
|
525
|
+
file: myFile,
|
|
526
|
+
fileName: 'test.pdf',
|
|
527
|
+
userId: 'user-123',
|
|
528
|
+
});
|
|
529
|
+
```
|