@xenterprises/fastify-xstorage 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/.env.example +28 -0
- package/EXAMPLES.md +797 -0
- package/QUICK_START.md +345 -0
- package/README.md +492 -0
- package/TESTING.md +567 -0
- package/package.json +51 -0
- package/server/app.js +136 -0
- package/src/index.js +9 -0
- package/src/services/storage.js +352 -0
- package/src/utils/helpers.js +238 -0
- package/src/xStorage.js +86 -0
- package/test/storage.test.js +257 -0
package/QUICK_START.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get up and running with xStorage in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @xenterprises/fastify-xstorage @aws-sdk/client-s3 @aws-sdk/s3-request-presigner @fastify/multipart
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**For image processing** (optional), also install [@xenterprises/fastify-ximagepipeline](https://github.com/x-enterprises/fastify-plugins/tree/main/fastify-ximagepipeline)
|
|
12
|
+
|
|
13
|
+
## 2. Setup Storage Provider
|
|
14
|
+
|
|
15
|
+
### Digital Ocean Spaces
|
|
16
|
+
|
|
17
|
+
1. Go to https://cloud.digitalocean.com/spaces
|
|
18
|
+
2. Create a new Space
|
|
19
|
+
3. Go to API → Tokens/Keys
|
|
20
|
+
4. Generate new Spaces access key
|
|
21
|
+
5. Configure CORS in your Space settings
|
|
22
|
+
|
|
23
|
+
### AWS S3
|
|
24
|
+
|
|
25
|
+
1. Go to https://console.aws.amazon.com/s3
|
|
26
|
+
2. Create new bucket
|
|
27
|
+
3. Go to IAM → Users → Create user
|
|
28
|
+
4. Attach policy: `AmazonS3FullAccess`
|
|
29
|
+
5. Create access keys
|
|
30
|
+
|
|
31
|
+
### Cloudflare R2
|
|
32
|
+
|
|
33
|
+
1. Go to https://dash.cloudflare.com/r2
|
|
34
|
+
2. Create new bucket
|
|
35
|
+
3. Create API token with R2 edit permissions
|
|
36
|
+
4. Set up custom domain (optional)
|
|
37
|
+
|
|
38
|
+
## 3. Configure Environment
|
|
39
|
+
|
|
40
|
+
Create `.env`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Digital Ocean Spaces
|
|
44
|
+
STORAGE_ENDPOINT=https://nyc3.digitaloceanspaces.com
|
|
45
|
+
STORAGE_REGION=us-east-1
|
|
46
|
+
STORAGE_ACCESS_KEY_ID=your_access_key_here
|
|
47
|
+
STORAGE_SECRET_ACCESS_KEY=your_secret_key_here
|
|
48
|
+
STORAGE_BUCKET=your-bucket-name
|
|
49
|
+
STORAGE_PUBLIC_URL=https://your-bucket-name.nyc3.digitaloceanspaces.com
|
|
50
|
+
|
|
51
|
+
PORT=3000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 4. Basic Setup
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import Fastify from "fastify";
|
|
58
|
+
import multipart from "@fastify/multipart";
|
|
59
|
+
import xStorage from "@xenterprises/fastify-xstorage";
|
|
60
|
+
|
|
61
|
+
const fastify = Fastify({ logger: true });
|
|
62
|
+
|
|
63
|
+
// Register multipart for file uploads
|
|
64
|
+
await fastify.register(multipart, {
|
|
65
|
+
limits: {
|
|
66
|
+
fileSize: 10 * 1024 * 1024, // 10MB
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Register xStorage
|
|
71
|
+
await fastify.register(xStorage, {
|
|
72
|
+
endpoint: process.env.STORAGE_ENDPOINT,
|
|
73
|
+
region: process.env.STORAGE_REGION,
|
|
74
|
+
accessKeyId: process.env.STORAGE_ACCESS_KEY_ID,
|
|
75
|
+
secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY,
|
|
76
|
+
bucket: process.env.STORAGE_BUCKET,
|
|
77
|
+
publicUrl: process.env.STORAGE_PUBLIC_URL,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await fastify.listen({ port: 3000 });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 5. Upload Files
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
fastify.post("/upload", async (request, reply) => {
|
|
87
|
+
const data = await request.file();
|
|
88
|
+
const buffer = await data.toBuffer();
|
|
89
|
+
|
|
90
|
+
const result = await fastify.xStorage.upload(buffer, data.filename, {
|
|
91
|
+
folder: "uploads",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Store in your database
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
file: {
|
|
98
|
+
storageKey: result.key, // Store for deletion
|
|
99
|
+
url: result.url,
|
|
100
|
+
size: result.size,
|
|
101
|
+
contentType: result.contentType,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**For image uploads with processing**, use [@xenterprises/fastify-ximagepipeline](https://github.com/x-enterprises/fastify-plugins/tree/main/fastify-ximagepipeline) for:
|
|
108
|
+
- Automatic optimization and format conversion
|
|
109
|
+
- Thumbnail generation
|
|
110
|
+
- EXIF stripping
|
|
111
|
+
- Multiple variant generation (WebP, AVIF)
|
|
112
|
+
|
|
113
|
+
## 6. Store in Database
|
|
114
|
+
|
|
115
|
+
Example with Prisma:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
fastify.post("/upload", async (request, reply) => {
|
|
119
|
+
const data = await request.file();
|
|
120
|
+
const buffer = await data.toBuffer();
|
|
121
|
+
|
|
122
|
+
// Upload to storage
|
|
123
|
+
const result = await fastify.xStorage.upload(buffer, data.filename, {
|
|
124
|
+
folder: "uploads",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Save to database
|
|
128
|
+
const file = await fastify.prisma.file.create({
|
|
129
|
+
data: {
|
|
130
|
+
filename: data.filename,
|
|
131
|
+
storageKey: result.key, // Store for deletion
|
|
132
|
+
size: result.size,
|
|
133
|
+
contentType: result.contentType,
|
|
134
|
+
userId: request.user.id,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return { success: true, file };
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 7. Delete Files
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
fastify.delete("/files/:id", async (request, reply) => {
|
|
146
|
+
const { id } = request.params;
|
|
147
|
+
|
|
148
|
+
// Get file from database
|
|
149
|
+
const file = await fastify.prisma.file.findUnique({
|
|
150
|
+
where: { id },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Delete from storage
|
|
154
|
+
await fastify.xStorage.delete(file.storageKey);
|
|
155
|
+
|
|
156
|
+
// Delete from database
|
|
157
|
+
await fastify.prisma.file.delete({
|
|
158
|
+
where: { id },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return { success: true };
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 8. Test Locally
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Start server
|
|
169
|
+
npm run dev
|
|
170
|
+
|
|
171
|
+
# Upload a file
|
|
172
|
+
curl -X POST http://localhost:3000/upload \
|
|
173
|
+
-F "file=@./test-image.jpg"
|
|
174
|
+
|
|
175
|
+
# You'll get back:
|
|
176
|
+
{
|
|
177
|
+
"success": true,
|
|
178
|
+
"file": {
|
|
179
|
+
"url": "https://your-bucket.nyc3.digitaloceanspaces.com/uploads/test-image-a1b2c3d4.jpg",
|
|
180
|
+
"key": "uploads/test-image-a1b2c3d4.jpg",
|
|
181
|
+
"size": 245678
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Visit the URL in your browser to verify it works!
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Common Use Cases
|
|
189
|
+
|
|
190
|
+
### Document Upload with Validation
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
import { helpers } from "@xenterprises/fastify-xstorage";
|
|
194
|
+
|
|
195
|
+
fastify.post("/documents", async (request, reply) => {
|
|
196
|
+
const data = await request.file();
|
|
197
|
+
|
|
198
|
+
// Validate file type
|
|
199
|
+
if (!helpers.isPdf(data.filename) && !helpers.isImage(data.filename)) {
|
|
200
|
+
return reply.code(400).send({ error: "Only PDF and images allowed" });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const buffer = await data.toBuffer();
|
|
204
|
+
|
|
205
|
+
// Upload file
|
|
206
|
+
const result = await fastify.xStorage.upload(buffer, data.filename, {
|
|
207
|
+
folder: "documents",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// For images, use xImagePipeline for processing
|
|
211
|
+
// For PDFs, store as-is
|
|
212
|
+
|
|
213
|
+
await fastify.prisma.document.create({
|
|
214
|
+
data: {
|
|
215
|
+
filename: data.filename,
|
|
216
|
+
storageKey: result.key,
|
|
217
|
+
size: result.size,
|
|
218
|
+
contentType: result.contentType,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return { success: true, document: result };
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Generate Signed URLs for Private Files
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// Get download URL for private file
|
|
230
|
+
fastify.get("/documents/:id/download", async (request, reply) => {
|
|
231
|
+
const { id } = request.params;
|
|
232
|
+
|
|
233
|
+
const document = await fastify.prisma.document.findUnique({ where: { id } });
|
|
234
|
+
|
|
235
|
+
// Generate signed URL valid for 1 hour
|
|
236
|
+
const signedUrl = await fastify.xStorage.getSignedUrl(
|
|
237
|
+
document.storageKey,
|
|
238
|
+
3600
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
return { download: signedUrl };
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Batch Upload
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
fastify.post("/batch-upload", async (request, reply) => {
|
|
249
|
+
const parts = request.parts();
|
|
250
|
+
const files = [];
|
|
251
|
+
|
|
252
|
+
for await (const part of parts) {
|
|
253
|
+
if (part.type === "file") {
|
|
254
|
+
const buffer = await part.toBuffer();
|
|
255
|
+
files.push({ file: buffer, filename: part.filename });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const results = await fastify.xStorage.uploadMultiple(files, {
|
|
260
|
+
folder: "batch-uploads",
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return { success: true, files: results };
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Image Processing
|
|
268
|
+
|
|
269
|
+
For user avatars, product images, and other image processing needs, use **[@xenterprises/fastify-ximagepipeline](https://github.com/x-enterprises/fastify-plugins/tree/main/fastify-ximagepipeline)** which handles:
|
|
270
|
+
- Automatic optimization and resizing
|
|
271
|
+
- WebP/AVIF conversion
|
|
272
|
+
- Thumbnail generation with multiple sizes
|
|
273
|
+
- EXIF stripping
|
|
274
|
+
- Blur hash generation
|
|
275
|
+
|
|
276
|
+
## Tips
|
|
277
|
+
|
|
278
|
+
1. **Always store the storage key** in your database
|
|
279
|
+
- `storageKey` - For deleting or accessing files later
|
|
280
|
+
- Store reference to key, not just the URL
|
|
281
|
+
|
|
282
|
+
2. **Use folders** to organize files logically
|
|
283
|
+
```javascript
|
|
284
|
+
folder: "users/123/documents"
|
|
285
|
+
folder: "invoices/2024/january"
|
|
286
|
+
folder: "receipts/2024"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
3. **Use signed URLs for private files**
|
|
290
|
+
```javascript
|
|
291
|
+
const signedUrl = await fastify.xStorage.getSignedUrl(key, 3600);
|
|
292
|
+
// Expires in 1 hour
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
4. **Validate file types** before uploading
|
|
296
|
+
```javascript
|
|
297
|
+
import { helpers } from "@xenterprises/fastify-xstorage";
|
|
298
|
+
|
|
299
|
+
if (!helpers.isPdf(filename)) {
|
|
300
|
+
return reply.code(400).send({ error: "Only PDFs allowed" });
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
5. **Delete old files** when replacing
|
|
305
|
+
```javascript
|
|
306
|
+
if (oldFile.storageKey) {
|
|
307
|
+
await fastify.xStorage.delete(oldFile.storageKey);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
6. **Use batch operations** for better performance
|
|
312
|
+
```javascript
|
|
313
|
+
await fastify.xStorage.uploadMultiple(files, { folder: "uploads" });
|
|
314
|
+
await fastify.xStorage.deleteMultiple(keys);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Next Steps
|
|
318
|
+
|
|
319
|
+
- [Full Documentation](./README.md)
|
|
320
|
+
- [Testing Guide](./TESTING.md)
|
|
321
|
+
- [Complete Examples](./EXAMPLES.md)
|
|
322
|
+
|
|
323
|
+
## Troubleshooting
|
|
324
|
+
|
|
325
|
+
### Files not uploading?
|
|
326
|
+
|
|
327
|
+
1. Check credentials are correct
|
|
328
|
+
2. Verify bucket exists and is accessible
|
|
329
|
+
3. Check IAM permissions (should allow S3 operations)
|
|
330
|
+
4. Ensure CORS is configured on bucket
|
|
331
|
+
5. Check file size limits
|
|
332
|
+
|
|
333
|
+
### Signed URLs not working?
|
|
334
|
+
|
|
335
|
+
1. Verify credentials are correct
|
|
336
|
+
2. Check file actually exists in bucket
|
|
337
|
+
3. Check URL hasn't expired
|
|
338
|
+
4. Try with longer expiration time
|
|
339
|
+
|
|
340
|
+
### Connection issues?
|
|
341
|
+
|
|
342
|
+
1. Verify `endpoint` URL is correct
|
|
343
|
+
2. Check network connectivity to S3 provider
|
|
344
|
+
3. Ensure credentials haven't expired
|
|
345
|
+
4. Verify region is correct for AWS S3
|