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

208 lines
4.9 KiB
TypeScript

/**
* Cloudflare Images - Upload via URL
*
* Ingest images from external URLs without downloading first.
*
* Use cases:
* - Migrating images from another service
* - Ingesting user-provided URLs
* - Backing up images from external sources
*/
interface Env {
IMAGES_ACCOUNT_ID: string;
IMAGES_API_TOKEN: string;
}
interface UploadViaURLOptions {
url: string; // Image URL to ingest
id?: string; // Custom ID (optional)
requireSignedURLs?: boolean;
metadata?: Record<string, string>;
}
interface CloudflareImagesResponse {
success: boolean;
result?: {
id: string;
filename: string;
uploaded: string;
requireSignedURLs: boolean;
variants: string[];
};
errors?: Array<{ code: number; message: string }>;
}
/**
* Upload image from external URL
*/
export async function uploadImageViaURL(
options: UploadViaURLOptions,
env: Env
): Promise<CloudflareImagesResponse> {
const formData = new FormData();
// Required: URL to ingest
formData.append('url', options.url);
// Optional: Custom ID
if (options.id) {
formData.append('id', options.id);
}
// Optional: Require signed URLs
if (options.requireSignedURLs !== undefined) {
formData.append('requireSignedURLs', String(options.requireSignedURLs));
}
// Optional: Metadata
if (options.metadata) {
formData.append('metadata', JSON.stringify(options.metadata));
}
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}`
},
body: formData
}
);
const result: CloudflareImagesResponse = await response.json();
if (!result.success) {
console.error('Upload via URL failed:', result.errors);
throw new Error(`Upload via URL failed: ${result.errors?.[0]?.message || 'Unknown error'}`);
}
return result;
}
/**
* Example Cloudflare Worker
*/
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Endpoint: POST /ingest-image
if (request.method === 'POST' && url.pathname === '/ingest-image') {
try {
const body = await request.json<{ imageUrl: string }>();
if (!body.imageUrl) {
return Response.json({ error: 'imageUrl required' }, { status: 400 });
}
// Validate URL format
try {
new URL(body.imageUrl);
} catch {
return Response.json({ error: 'Invalid URL' }, { status: 400 });
}
// Upload from external URL
const result = await uploadImageViaURL(
{
url: body.imageUrl,
metadata: {
source: 'external',
ingestedAt: 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 : 'Ingestion failed' },
{ status: 500 }
);
}
}
return Response.json({ error: 'Not found' }, { status: 404 });
}
};
/**
* Batch ingestion example
*/
export async function batchIngestImages(
imageUrls: string[],
env: Env
): Promise<Array<{ url: string; result?: CloudflareImagesResponse; error?: string }>> {
const results = await Promise.allSettled(
imageUrls.map(async (url) => {
return {
url,
result: await uploadImageViaURL({ url }, env)
};
})
);
return results.map((result, index) => {
if (result.status === 'fulfilled') {
return result.value;
} else {
return {
url: imageUrls[index],
error: result.reason instanceof Error ? result.reason.message : 'Unknown error'
};
}
});
}
/**
* Example with authentication for private origins
*/
export async function uploadFromPrivateURL(
imageUrl: string,
username: string,
password: string,
env: Env
): Promise<CloudflareImagesResponse> {
// Cloudflare supports HTTP Basic Auth in URL
const urlObj = new URL(imageUrl);
const authenticatedURL = `${urlObj.protocol}//${username}:${password}@${urlObj.host}${urlObj.pathname}${urlObj.search}`;
return uploadImageViaURL({ url: authenticatedURL }, env);
}
/**
* Usage examples:
*
* ```typescript
* // Single image
* const result = await uploadImageViaURL({
* url: 'https://example.com/photo.jpg',
* metadata: { source: 'migration' }
* }, env);
*
* // Batch ingestion
* const urls = [
* 'https://example.com/photo1.jpg',
* 'https://example.com/photo2.jpg',
* 'https://example.com/photo3.jpg'
* ];
* const results = await batchIngestImages(urls, env);
*
* // Private origin with auth
* const result = await uploadFromPrivateURL(
* 'https://private-storage.example.com/image.jpg',
* 'username',
* 'password',
* env
* );
* ```
*/