6import { generateUniqueId } from "./utils.ts";
7import {
8storeImage,
9getImage,
10getImageMetadata,
11listSubmissionImages,
12deleteImage,
13ImageMetadata,
14testBlobStorage
15} from "./image-storage.ts";
16import { blob } from "https://esm.town/v/std/blob";
17103
104// Remove id from the fields to update
105const { id: _, images: __, ...fieldsToUpdate } = body;
106
107// Update the submission in the database
119});
120121// Upload an image
122app.post("/upload-image", async (c) => {
123try {
124console.log("[SERVER] Received image upload request");
125
126// Parse the multipart form data
162console.log(`[SERVER] File read as Uint8Array, length: ${uint8Array.length} bytes`);
163
164// Store the image
165console.log(`[SERVER] Calling storeImage function`);
166const metadata = await storeImage(
167uint8Array,
168file.name,
171);
172
173console.log(`[SERVER] Image stored successfully with ID: ${metadata.id}`);
174
175// Immediately try to list images to verify
176console.log(`[SERVER] Verifying image listing immediately after upload`);
177const images = await listSubmissionImages(submissionId);
178console.log(`[SERVER] Verification found ${images.length} images`);
179
180return c.json({
181success: true,
182message: "Image uploaded successfully",
183data: metadata,
184debug: {
185imagesFound: images.length,
186imageIds: images.map(img => img.id)
187}
188});
189} catch (error) {
190console.error("[SERVER] Error uploading image:", error);
191return c.json({
192success: false,
193message: "An error occurred uploading your image",
194error: error.toString()
195}, 500);
197});
198199// Get an image by ID
200app.get("/image", async (c) => {
201try {
202const imageId = c.req.query("id");
203console.log(`[SERVER] Image request received for ID: ${imageId}`);
204
205if (!imageId) {
206console.log("[SERVER] Missing image ID in request");
207return c.json({ success: false, message: "Image ID is required" }, 400);
208}
209
210// Get image metadata
211const metadata = await getImageMetadata(imageId);
212if (!metadata) {
213console.log(`[SERVER] Metadata not found for image: ${imageId}`);
214return c.json({ success: false, message: "Image not found" }, 404);
215}
216
217console.log(`[SERVER] Metadata retrieved for image: ${imageId}`);
218console.log(`[SERVER] Image details: name=${metadata.originalName}, type=${metadata.contentType}, size=${metadata.size} bytes`);
219
220// Get the image data
221const imageData = await getImage(imageId);
222if (!imageData) {
223console.log(`[SERVER] Image data not found for ID: ${imageId}`);
224return c.json({ success: false, message: "Image data not found" }, 404);
225}
226
227console.log(`[SERVER] Image data retrieved successfully, size: ${imageData.length} bytes`);
228
229// Return the image with proper content type
230console.log(`[SERVER] Serving image with content type: ${metadata.contentType}`);
231return new Response(imageData, {
232headers: {
233"Content-Type": metadata.contentType,
236});
237} catch (error) {
238console.error("[SERVER] Error getting image:", error);
239return c.json({ success: false, message: "An error occurred getting the image" }, 500);
240}
241});
242243// List images for a submission
244app.get("/list-images", async (c) => {
245try {
246const submissionId = c.req.query("submissionId");
247console.log(`[SERVER] List images request for submission: ${submissionId}`);
248
249if (!submissionId) {
250console.log("[SERVER] Missing submission ID in list images request");
251return c.json({ success: false, message: "Submission ID is required" }, 400);
252}
253
254// Get all images for this submission
255const images = await listSubmissionImages(submissionId);
256console.log(`[SERVER] Found ${images.length} images for submission: ${submissionId}`);
257
258// Log details of each image
259images.forEach((img, index) => {
260console.log(`[SERVER] Image ${index + 1}: id=${img.id}, name=${img.originalName}, type=${img.contentType}, size=${img.size} bytes`);
261});
262
263return c.json({
264success: true,
265data: images
266});
267} catch (error) {
268console.error("[SERVER] Error listing images:", error);
269return c.json({
270success: false,
271message: "An error occurred listing images"
272}, 500);
273}
274});
275276// Delete an image
277app.delete("/image", async (c) => {
278try {
279const body = await c.req.json();
280const { imageId, submissionId } = body;
281console.log(`[SERVER] Delete image request: imageId=${imageId}, submissionId=${submissionId}`);
282
283if (!imageId || !submissionId) {
284console.log("[SERVER] Missing required parameters for image deletion");
285return c.json({
286success: false,
287message: "Image ID and Submission ID are required"
288}, 400);
289}
290
291// Get image metadata to verify it belongs to this submission
292const metadata = await getImageMetadata(imageId);
293if (!metadata) {
294console.log(`[SERVER] Metadata not found for image: ${imageId}`);
295return c.json({
296success: false,
297message: "Image not found"
298}, 404);
299}
300
301if (metadata.submissionId !== submissionId) {
302console.log(`[SERVER] Image ${imageId} does not belong to submission ${submissionId}`);
303console.log(`[SERVER] Image belongs to submission: ${metadata.submissionId}`);
304return c.json({
305success: false,
306message: "Image does not belong to this submission"
307}, 403);
308}
309
310console.log(`[SERVER] Verified image ${imageId} belongs to submission ${submissionId}`);
311
312// Delete the image
313const success = await deleteImage(imageId);
314
315if (!success) {
316console.log(`[SERVER] Failed to delete image: ${imageId}`);
317return c.json({ success: false, message: "Failed to delete image" }, 500);
318}
319
320console.log(`[SERVER] Successfully deleted image: ${imageId}`);
321
322return c.json({
323success: true,
324message: "Image deleted successfully"
325});
326} catch (error) {
327console.error("[SERVER] Error deleting image:", error);
328return c.json({
329success: false,
330message: "An error occurred deleting the image"
331}, 500);
332}
adportalimage-storage.ts75 matches
1import { blob } from "https://esm.town/v/std/blob";
23// Prefix for image keys in blob storage
4const IMAGE_PREFIX = "laura_mepson_image_";
56// Interface for image metadata
7export interface ImageMetadata {
8id: string;
9originalName: string;
14}
1516// Generate a unique ID for an image
17export function generateImageId(submissionId: string): string {
18const timestamp = Date.now().toString(36);
19const randomPart = Math.random().toString(36).substring(2, 8);
20return `${IMAGE_PREFIX}${submissionId}_${timestamp}${randomPart}`;
21}
2223// Store an image in blob storage
24export async function storeImage(
25imageData: Uint8Array, // Raw image data
26fileName: string,
27contentType: string,
28submissionId: string
29): Promise<ImageMetadata> {
30try {
31console.log(`[BLOB STORAGE] Storing image: ${fileName} (${contentType}) for submission: ${submissionId}`);
32console.log(`[BLOB STORAGE] Image size: ${imageData.length} bytes`);
33
34// Generate a unique ID for the image
35const imageId = generateImageId(submissionId);
36console.log(`[BLOB STORAGE] Generated image ID: ${imageId}`);
37
38// Store the image data directly
39await blob.set(imageId, imageData, { contentType });
40console.log(`[BLOB STORAGE] Image data stored successfully with ID: ${imageId}`);
41
42// Verify the image was stored
43const storedData = await blob.get(imageId);
44if (storedData) {
45console.log(`[BLOB STORAGE] Verified image data is stored (${storedData.length} bytes)`);
46} else {
47console.log(`[BLOB STORAGE] WARNING: Could not verify image data storage`);
48}
49
50// Create metadata
51const metadata: ImageMetadata = {
52id: imageId,
53originalName: fileName,
54contentType,
55size: imageData.length,
56uploadedAt: new Date().toISOString(),
57submissionId
60console.log(`[BLOB STORAGE] Storing metadata: ${JSON.stringify(metadata)}`);
61
62// Store metadata alongside the image
63await blob.setJSON(`${imageId}_metadata`, metadata);
64console.log(`[BLOB STORAGE] Image metadata stored successfully`);
65
66// Verify metadata was stored
67const storedMetadata = await blob.getJSON(`${imageId}_metadata`);
68if (storedMetadata) {
69console.log(`[BLOB STORAGE] Verified metadata is stored: ${JSON.stringify(storedMetadata)}`);
79return metadata;
80} catch (error) {
81console.error("[BLOB STORAGE] Error storing image:", error);
82throw error;
83}
84}
8586// Get an image from blob storage
87export async function getImage(imageId: string): Promise<Uint8Array | null> {
88try {
89console.log(`[BLOB STORAGE] Retrieving image with ID: ${imageId}`);
90const imageData = await blob.get(imageId);
91
92if (imageData) {
93console.log(`[BLOB STORAGE] Image retrieved successfully, size: ${imageData.length} bytes`);
94} else {
95console.log(`[BLOB STORAGE] Image not found with ID: ${imageId}`);
96}
97
98return imageData;
99} catch (error) {
100console.error(`[BLOB STORAGE] Error getting image ${imageId}:`, error);
101return null;
102}
103}
104105// Get image metadata
106export async function getImageMetadata(imageId: string): Promise<ImageMetadata | null> {
107try {
108console.log(`[BLOB STORAGE] Retrieving metadata for image: ${imageId}`);
109const metadata = await blob.getJSON(`${imageId}_metadata`);
110
111if (metadata) {
112console.log(`[BLOB STORAGE] Metadata retrieved successfully for image: ${imageId}`);
113} else {
114console.log(`[BLOB STORAGE] Metadata not found for image: ${imageId}`);
115}
116
117return metadata as ImageMetadata;
118} catch (error) {
119console.error(`[BLOB STORAGE] Error getting image metadata for ${imageId}:`, error);
120return null;
121}
122}
123124// List all images for a submission
125export async function listSubmissionImages(submissionId: string): Promise<ImageMetadata[]> {
126try {
127console.log(`[BLOB STORAGE] Listing images for submission: ${submissionId}`);
128console.log(`[BLOB STORAGE] Using prefix: ${IMAGE_PREFIX}${submissionId}_`);
129
130// First, list all keys to see what's in the blob storage
133allKeys.forEach(key => console.log(`[BLOB STORAGE] - ${key}`));
134
135// List all blobs with the image prefix for this submission
136const keys = await blob.list(`${IMAGE_PREFIX}${submissionId}_`);
137console.log(`[BLOB STORAGE] Found ${keys.length} keys for submission: ${submissionId}`);
138keys.forEach(key => console.log(`[BLOB STORAGE] - ${key}`));
139
140// Filter out metadata keys and get only image keys
141const imageKeys = keys.filter(key => !key.endsWith('_metadata'));
142console.log(`[BLOB STORAGE] Found ${imageKeys.length} image keys after filtering`);
143imageKeys.forEach(key => console.log(`[BLOB STORAGE] - Image key: ${key}`));
144
145// Get metadata for each image
146const metadataPromises = imageKeys.map(key => getImageMetadata(key));
147const metadataResults = await Promise.all(metadataPromises);
148
149// Filter out any null results
150const validMetadata = metadataResults.filter(metadata => metadata !== null) as ImageMetadata[];
151console.log(`[BLOB STORAGE] Retrieved ${validMetadata.length} valid metadata objects`);
152validMetadata.forEach(meta => console.log(`[BLOB STORAGE] - Metadata: ${meta.id}, ${meta.originalName}`));
154return validMetadata;
155} catch (error) {
156console.error(`[BLOB STORAGE] Error listing submission images for ${submissionId}:`, error);
157return [];
158}
159}
160161// Delete an image and its metadata
162export async function deleteImage(imageId: string): Promise<boolean> {
163try {
164console.log(`[BLOB STORAGE] Deleting image with ID: ${imageId}`);
165
166await blob.delete(imageId);
167console.log(`[BLOB STORAGE] Image deleted: ${imageId}`);
168
169await blob.delete(`${imageId}_metadata`);
170console.log(`[BLOB STORAGE] Image metadata deleted: ${imageId}_metadata`);
171
172return true;
173} catch (error) {
174console.error(`[BLOB STORAGE] Error deleting image ${imageId}:`, error);
175return false;
176}
218await blob.delete(testKey);
219
220// 6. List image keys for this submission
221console.log(`[BLOB STORAGE TEST] Listing image keys for submission: ${submissionId}`);
222const imageKeys = await blob.list(`${IMAGE_PREFIX}${submissionId}_`);
223console.log(`[BLOB STORAGE TEST] Found ${imageKeys.length} image keys`);
224
225return {
229totalKeys: allKeys.length,
230prefixedKeys: prefixedKeys.length,
231imageKeys: imageKeys,
232testKeyWorked: true
233}
blob_adminREADME.md1 match
3This is a lightweight Blob Admin interface to view and debug your Blob data.
45
67To use this, fork it to your account.
blob_adminmain.tsx2 matches
60const { ValTown } = await import("npm:@valtown/sdk");
61const vt = new ValTown();
62const { email: authorEmail, profileImageUrl, username } = await vt.me.profile.retrieve();
63// const authorEmail = me.email;
64128129c.set("email", email);
130c.set("profile", { profileImageUrl, username });
131await next();
132};
blob_adminapp.tsx3 matches
437{profile && (
438<div className="flex items-center space-x-4">
439<img src={profile.profileImageUrl} alt="Profile" className="w-8 h-8 rounded-full" />
440<span>{profile.username}</span>
441<a href="/auth/logout" className="text-blue-400 hover:text-blue-300">Logout</a>
580alt="Blob content"
581className="max-w-full h-auto"
582onError={() => console.error("Error loading image")}
583/>
584</div>
630<li>Create public shareable links for blobs</li>
631<li>View and manage public folder</li>
632<li>Preview images directly in the interface</li>
633</ul>
634</div>
10- Email functionality to send users a unique link
11- Continuation page for adding more information
12- Image upload and storage functionality
1314## Project Structure
20- `landing.html` - Main landing page
21- `continue.html` - Page for adding additional information
22- `image-storage.ts` - Image storage and retrieval functionality
2324## Database Schema
445. User can click the link to add more information
456. Additional information is saved to the database
467. User can upload and manage images related to their submission
4748## URL Structure
51- Continuation page: `https://[username].web.val.run?action=continue&id=[unique_id]`
5253## Image Storage
5455Images are stored using Val Town's blob storage:
56- Images are stored with a unique ID
57- Metadata is stored alongside each image
58- Users can upload, view, and delete images
59- Images are associated with a specific submission
6061## Development Notes
blob_adminREADME.md1 match
3This is a lightweight Blob Admin interface to view and debug your Blob data.
45
67To use this, fork it to your account.
blob_adminmain.tsx2 matches
60const { ValTown } = await import("npm:@valtown/sdk");
61const vt = new ValTown();
62const { email: authorEmail, profileImageUrl, username } = await vt.me.profile.retrieve();
63// const authorEmail = me.email;
64128129c.set("email", email);
130c.set("profile", { profileImageUrl, username });
131await next();
132};
blob_adminapp.tsx3 matches
437{profile && (
438<div className="flex items-center space-x-4">
439<img src={profile.profileImageUrl} alt="Profile" className="w-8 h-8 rounded-full" />
440<span>{profile.username}</span>
441<a href="/auth/logout" className="text-blue-400 hover:text-blue-300">Logout</a>
580alt="Blob content"
581className="max-w-full h-auto"
582onError={() => console.error("Error loading image")}
583/>
584</div>
630<li>Create public shareable links for blobs</li>
631<li>View and manage public folder</li>
632<li>Preview images directly in the interface</li>
633</ul>
634</div>
GitHub-PR-AutomationREADME.md2 matches
1112See all 3 in action👇
13
1415### 1. PR Auto-Assign
5051See this in action👇
52
5354