Files
gh-jezweb-claude-skills-ski…/templates/upload-api-basic.ts
2025-11-30 08:24:18 +08:00

151 lines
3.8 KiB
TypeScript

/**
* Cloudflare Images - Basic Upload via API
*
* Uploads an image file to Cloudflare Images storage.
*
* Usage:
* const result = await uploadImageToCloudflare(file, {
* requireSignedURLs: false,
* metadata: { userId: '12345' }
* });
*/
interface Env {
IMAGES_ACCOUNT_ID: string;
IMAGES_API_TOKEN: string;
}
interface UploadOptions {
id?: string; // Custom ID (optional, auto-generated if not provided)
requireSignedURLs?: boolean; // true for private images
metadata?: Record<string, string>; // Max 1024 bytes, not visible to end users
}
interface CloudflareImagesResponse {
success: boolean;
result?: {
id: string;
filename: string;
uploaded: string;
requireSignedURLs: boolean;
variants: string[];
};
errors?: Array<{ code: number; message: string }>;
}
/**
* Upload image to Cloudflare Images
*/
export async function uploadImageToCloudflare(
file: File,
options: UploadOptions = {},
env: Env
): Promise<CloudflareImagesResponse> {
const formData = new FormData();
// Required: File to upload
formData.append('file', file);
// Optional: Custom ID (if not provided, auto-generated)
if (options.id) {
formData.append('id', options.id);
}
// Optional: Require signed URLs for private images
if (options.requireSignedURLs !== undefined) {
formData.append('requireSignedURLs', String(options.requireSignedURLs));
}
// Optional: Metadata (JSON object, max 1024 bytes)
if (options.metadata) {
formData.append('metadata', JSON.stringify(options.metadata));
}
// Upload to Cloudflare Images API
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${env.IMAGES_ACCOUNT_ID}/images/v1`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${env.IMAGES_API_TOKEN}`
// Don't set Content-Type - FormData sets it automatically with boundary
},
body: formData
}
);
const result: CloudflareImagesResponse = await response.json();
if (!result.success) {
console.error('Upload failed:', result.errors);
throw new Error(`Upload failed: ${result.errors?.[0]?.message || 'Unknown error'}`);
}
return result;
}
/**
* Example Cloudflare Worker endpoint
*/
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method === 'POST' && new URL(request.url).pathname === '/upload') {
try {
// Parse multipart/form-data from request
const formData = await request.formData();
const file = formData.get('image') as File;
if (!file) {
return Response.json({ error: 'No file provided' }, { status: 400 });
}
// Upload to Cloudflare Images
const result = await uploadImageToCloudflare(
file,
{
requireSignedURLs: false,
metadata: {
uploadedBy: 'worker',
timestamp: new Date().toISOString()
}
},
env
);
return Response.json({
success: true,
imageId: result.result?.id,
variants: result.result?.variants
});
} catch (error) {
return Response.json(
{ error: error instanceof Error ? error.message : 'Upload failed' },
{ status: 500 }
);
}
}
return Response.json({ error: 'Method not allowed' }, { status: 405 });
}
};
/**
* Example usage from another script:
*
* ```typescript
* const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
* const file = fileInput.files?.[0];
*
* if (file) {
* const result = await uploadImageToCloudflare(file, {
* requireSignedURLs: false,
* metadata: { source: 'user-upload' }
* }, env);
*
* console.log('Uploaded:', result.result?.id);
* console.log('Serve at:', result.result?.variants[0]);
* }
* ```
*/