From 50d2992168223b32eeb90a6c9afc545136747b50 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Wed, 24 Sep 2025 12:44:27 +0200 Subject: Add /imgs/ endpoint and fix type errors --- src/config.ts | 2 +- src/functions/scheduled.ts | 22 -------------- src/pages/api/imgs.ts | 76 ++++++++++++++++++++++++++++++++++++++++++++++ src/pages/api/update.ts | 29 +++--------------- worker-configuration.d.ts | 5 ++- 5 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 src/pages/api/imgs.ts diff --git a/src/config.ts b/src/config.ts index 615e6d4..8bce091 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,7 +7,7 @@ export const IMAGES_BASE_URL = PRODUCTION ? "https://imgs.geshem.space" : ""; export const MAPBOX_ACCESS_TOKEN = "pk.eyJ1IjoieXV2YWRtIiwiYSI6ImNpcnMxbzBuaTAwZWdoa25oczlzZmkwbHcifQ.UHtLngbKm9O8945pJm23Nw"; -export const IMAGE_COORDINATES: [number, number][] = [ +export const IMAGE_COORDINATES: [[number, number], [number, number], [number, number], [number, number]] = [ [31.7503896894, 34.4878044232], [37.8574239563, 34.5078463729], [37.7157066403, 29.4538271687], diff --git a/src/functions/scheduled.ts b/src/functions/scheduled.ts index 5aa3fa0..ae43469 100644 --- a/src/functions/scheduled.ts +++ b/src/functions/scheduled.ts @@ -8,9 +8,6 @@ interface Env { IMGS_BUCKET: R2Bucket; } -interface ImageData { - [key: string]: string[]; -} class GeshemUpdate { private bucket: R2Bucket; @@ -118,29 +115,10 @@ class GeshemUpdate { return `imgs/${d}/${t}/${res}.png`; } - private async generateJson(): Promise { - const latestKeys = await this.getLatestBucketKeys(); - const keys = latestKeys - .filter(key => key.endsWith('280.png')) - .sort() - .slice(-10); - - const index: ImageData = { '280': keys }; - - await this.bucket.put('imgs.json', JSON.stringify(index), { - httpMetadata: { - contentType: 'application/json', - cacheControl: 'public, max-age=60' - } - }); - } async run(): Promise { try { const updated = await this.fetchMissingImages(); - if (updated) { - await this.generateJson(); - } return `Updated: ${updated}`; } catch (error) { console.error('Error in GeshemUpdate.run():', error); diff --git a/src/pages/api/imgs.ts b/src/pages/api/imgs.ts new file mode 100644 index 0000000..378d36d --- /dev/null +++ b/src/pages/api/imgs.ts @@ -0,0 +1,76 @@ +import type { APIRoute } from 'astro'; + +export const prerender = false; + +interface CloudflareEnv { + IMGS_BUCKET: R2Bucket; +} + +export const GET: APIRoute = async ({ locals }) => { + try { + const env = (locals as any).runtime?.env; + + if (!env?.IMGS_BUCKET) { + return new Response('R2 bucket not configured', { status: 500 }); + } + + const bucket = env.IMGS_BUCKET; + + // Get the latest images from yesterday onwards + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const yesterdayStr = yesterday.toISOString().slice(0, 10).replace(/-/g, ''); + + const objects = await bucket.list({ + prefix: 'imgs/', + startAfter: `imgs/${yesterdayStr}` + }); + + // Filter for 280.png files and get the latest 10 + const imageKeys = objects.objects + .map((obj: any) => obj.key) + .filter((key: string) => key.endsWith('/280.png')) + .sort() + .slice(-10); + + // Transform keys to full URLs + const images = imageKeys.map((key: string) => { + // Extract date/time from path: imgs/20250622/0930/280.png + const pathParts = key.split('/'); + const date = pathParts[1]; + const time = pathParts[2]; + const filename = pathParts[3]; + + return { + path: key, + url: `https://imgs.geshem.space/${date}/${time}/${filename}`, + date: date, + time: time + }; + }); + + return new Response(JSON.stringify({ + images: images, + count: images.length, + timestamp: new Date().toISOString() + }), { + status: 200, + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'public, max-age=60, s-maxage=60' + } + }); + + } catch (error) { + console.error('Error fetching images:', error); + return new Response(JSON.stringify({ + error: 'Failed to fetch images', + message: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 500, + headers: { + 'Content-Type': 'application/json' + } + }); + } +}; \ No newline at end of file diff --git a/src/pages/api/update.ts b/src/pages/api/update.ts index 88dfa3c..ca60384 100644 --- a/src/pages/api/update.ts +++ b/src/pages/api/update.ts @@ -2,9 +2,6 @@ import type { APIRoute } from 'astro'; export const prerender = false; -interface ImageData { - [key: string]: string[]; -} interface CloudflareEnv { BASE_URL: string; @@ -12,6 +9,7 @@ interface CloudflareEnv { AUTH_PASS: string; BUCKET_NAME: string; SCHEDULED?: boolean; + [key: string]: any; } class GeshemUpdate { @@ -120,29 +118,10 @@ class GeshemUpdate { return `imgs/${d}/${t}/${res}.png`; } - private async generateJson(): Promise { - const latestKeys = await this.getLatestBucketKeys(); - const keys = latestKeys - .filter(key => key.endsWith('280.png')) - .sort() - .slice(-10); - - const index: ImageData = { '280': keys }; - - await this.bucket.put('imgs.json', JSON.stringify(index), { - httpMetadata: { - contentType: 'application/json', - cacheControl: 'public, max-age=60' - } - }); - } async run(): Promise { try { const updated = await this.fetchMissingImages(); - if (updated) { - await this.generateJson(); - } return `Updated: ${updated}`; } catch (error) { console.error('Error in GeshemUpdate.run():', error); @@ -151,7 +130,7 @@ class GeshemUpdate { } } -export const GET: APIRoute = async ({ platform, request }) => { +export const GET: APIRoute = async ({ locals, request }) => { // This endpoint should only be accessible via Cloudflare Cron const cron = request.headers.get('CF-Cron'); if (!cron) { @@ -159,14 +138,14 @@ export const GET: APIRoute = async ({ platform, request }) => { } try { - const env = platform?.env as unknown as CloudflareEnv; + const env = (locals as any).runtime?.env as unknown as CloudflareEnv; if (!env?.BASE_URL || !env?.AUTH_USER || !env?.AUTH_PASS) { return new Response('Missing environment variables', { status: 500 }); } // Get R2 bucket (this would be configured in your Cloudflare Workers environment) - const bucket = env.BUCKET_NAME ? platform?.env[env.BUCKET_NAME] as R2Bucket : null; + const bucket = env.BUCKET_NAME ? env[env.BUCKET_NAME] as R2Bucket : null; if (!bucket) { return new Response('R2 bucket not configured', { status: 500 }); diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index a658054..5d6133a 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,7 +1,10 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: f813a33da05ec796e4b781d9c5e44dd1) +// Generated by Wrangler by running `wrangler types` (hash: 86ac7c910eaa8ed87087e10bf93f5438) // Runtime types generated with workerd@1.20250923.0 2025-09-24 nodejs_compat declare namespace Cloudflare { + interface GlobalProps { + mainModule: typeof import("./dist/_worker.js/index"); + } interface Env { IMGS_BUCKET: R2Bucket; ASSETS: Fetcher; -- cgit v1.3.1