Skip to content
This repository was archived by the owner on Mar 26, 2025. It is now read-only.

Commit 5cfba9a

Browse files
authored
Merge pull request #11 from aibtcdev/feat/images
Create image-do.ts
2 parents 1c92db2 + 685769c commit 5cfba9a

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed

src/durable-objects/image-do.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { DurableObject } from 'cloudflare:workers';
2+
import { Env } from '../../worker-configuration';
3+
import { createJsonResponse } from '../utils/requests-responses';
4+
import { validateSharedKeyAuth } from '../utils/auth-helper';
5+
import OpenAI from 'openai';
6+
7+
export class ImageGeneratorDO extends DurableObject<Env> {
8+
private readonly BASE_PATH = '/image';
9+
private readonly KEY_PREFIX = 'img';
10+
private readonly SUPPORTED_ENDPOINTS = ['/generate', '/get', '/list'];
11+
private openai: OpenAI;
12+
13+
constructor(ctx: DurableObjectState, env: Env) {
14+
super(ctx, env);
15+
this.ctx = ctx;
16+
this.env = env;
17+
18+
// Initialize OpenAI client
19+
this.openai = new OpenAI({
20+
apiKey: env.OPENAI_API_KEY
21+
});
22+
}
23+
24+
async fetch(request: Request): Promise<Response> {
25+
const url = new URL(request.url);
26+
const path = url.pathname;
27+
28+
if (!path.startsWith(this.BASE_PATH)) {
29+
return createJsonResponse(
30+
{ error: `Invalid base path: ${path}` },
31+
404
32+
);
33+
}
34+
35+
const endpoint = path.replace(this.BASE_PATH, '');
36+
37+
// Require auth for all endpoints
38+
const authResult = await validateSharedKeyAuth(this.env, request);
39+
if (!authResult.success) {
40+
return createJsonResponse({ error: authResult.error }, authResult.status);
41+
}
42+
43+
if (endpoint === '/generate') {
44+
if (request.method !== 'POST') {
45+
return createJsonResponse({ error: 'Method not allowed' }, 405);
46+
}
47+
48+
try {
49+
const { prompt, size = "1024x1024", n = 1 } = await request.json();
50+
51+
if (!prompt) {
52+
return createJsonResponse({ error: 'Prompt is required' }, 400);
53+
}
54+
55+
// Generate image with OpenAI
56+
const response = await this.openai.images.generate({
57+
model: "dall-e-3",
58+
prompt,
59+
n,
60+
size,
61+
});
62+
63+
// Store each generated image in R2
64+
const storedImages = await Promise.all(
65+
response.data.map(async (image, index) => {
66+
const imageResponse = await fetch(image.url);
67+
const imageData = await imageResponse.blob();
68+
69+
const key = `${this.KEY_PREFIX}_${Date.now()}_${index}`;
70+
const metadata = {
71+
prompt,
72+
generated: new Date().toISOString(),
73+
size,
74+
};
75+
76+
await this.env.AIBTCDEV_SERVICES_BUCKET.put(key, imageData, {
77+
httpMetadata: {
78+
contentType: 'image/png',
79+
customMetadata: metadata,
80+
},
81+
});
82+
83+
return {
84+
key,
85+
url: `/image/get/${key}`,
86+
metadata,
87+
};
88+
})
89+
);
90+
91+
return createJsonResponse({
92+
success: true,
93+
images: storedImages
94+
});
95+
96+
} catch (error) {
97+
console.error('Image generation failed:', error);
98+
return createJsonResponse({
99+
error: 'Failed to generate or store image'
100+
}, 500);
101+
}
102+
}
103+
104+
if (endpoint.startsWith('/get/')) {
105+
const key = endpoint.replace('/get/', '');
106+
try {
107+
const object = await this.env.AIBTCDEV_SERVICES_BUCKET.get(key);
108+
109+
if (!object) {
110+
return createJsonResponse({ error: 'Image not found' }, 404);
111+
}
112+
113+
return new Response(object.body, {
114+
headers: {
115+
'content-type': 'image/png',
116+
'cache-control': 'public, max-age=31536000',
117+
},
118+
});
119+
} catch (error) {
120+
return createJsonResponse({ error: 'Failed to retrieve image' }, 500);
121+
}
122+
}
123+
124+
if (endpoint === '/list') {
125+
try {
126+
const options: R2ListOptions = {
127+
limit: 100,
128+
prefix: this.KEY_PREFIX,
129+
cursor: url.searchParams.get('cursor') || undefined,
130+
};
131+
132+
const objects = await this.env.AIBTCDEV_SERVICES_BUCKET.list(options);
133+
134+
return createJsonResponse({
135+
images: objects.objects.map((obj) => ({
136+
key: obj.key,
137+
url: `/image/get/${obj.key}`,
138+
size: obj.size,
139+
uploaded: obj.uploaded,
140+
metadata: obj.httpMetadata?.customMetadata,
141+
})),
142+
truncated: objects.truncated,
143+
cursor: objects.truncated ? objects.cursor : undefined,
144+
});
145+
} catch (error) {
146+
return createJsonResponse({ error: 'Failed to list images' }, 500);
147+
}
148+
}
149+
150+
return createJsonResponse(
151+
{ error: `Unsupported endpoint: ${endpoint}` },
152+
404
153+
);
154+
}
155+
}

0 commit comments

Comments
 (0)