+5
.env.example
+5
.env.example
···
36
36
# Service Configuration
37
37
# -----------------------------------------------------------------------------
38
38
39
+
# Auth token for API uploads (optional, but recommended)
40
+
# Generate with: openssl rand -hex 32
41
+
# Used for POST /upload endpoint
42
+
AUTH_TOKEN=your-secret-token-here
43
+
39
44
# Public URL where this service is accessible
40
45
# This is the domain/URL users will access
41
46
# Examples:
+41
src/index.ts
+41
src/index.ts
···
4
4
// Configuration from env
5
5
const R2_PUBLIC_URL = process.env.R2_PUBLIC_URL!;
6
6
const PUBLIC_URL = process.env.PUBLIC_URL || "http://localhost:3000";
7
+
const AUTH_TOKEN = process.env.AUTH_TOKEN;
7
8
8
9
// S3 configuration
9
10
const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID!;
···
62
63
},
63
64
},
64
65
66
+
"/upload": {
67
+
async POST(request) {
68
+
return handleUpload(request);
69
+
},
70
+
},
71
+
65
72
"/health": {
66
73
async GET(request) {
67
74
return Response.json({ status: "ok" });
···
88
95
return new Response("Not found", { status: 404 });
89
96
},
90
97
});
98
+
99
+
async function handleUpload(request: Request) {
100
+
try {
101
+
// Check auth token
102
+
const authHeader = request.headers.get("Authorization");
103
+
if (!AUTH_TOKEN || authHeader !== `Bearer ${AUTH_TOKEN}`) {
104
+
return new Response("Unauthorized", { status: 401 });
105
+
}
106
+
107
+
// Parse multipart form data
108
+
const formData = await request.formData();
109
+
const file = formData.get("file") as File;
110
+
111
+
if (!file) {
112
+
return Response.json({ success: false, error: "No file provided" }, { status: 400 });
113
+
}
114
+
115
+
// Read file buffer
116
+
const originalBuffer = Buffer.from(await file.arrayBuffer());
117
+
const contentType = file.type || "image/jpeg";
118
+
119
+
// Optimize image
120
+
const { buffer: optimizedBuffer, contentType: newContentType } = await optimizeImage(originalBuffer, contentType);
121
+
122
+
// Upload to R2
123
+
const imageKey = await uploadImageToR2(optimizedBuffer, newContentType);
124
+
const url = `${PUBLIC_URL}/i/${imageKey}`;
125
+
126
+
return Response.json({ success: true, url });
127
+
} catch (error) {
128
+
console.error("Error handling upload:", error);
129
+
return Response.json({ success: false, error: "Upload failed" }, { status: 500 });
130
+
}
131
+
}
91
132
92
133
async function handleSlackEvent(request: Request) {
93
134
try {