From a75acdb10d0fdcca1d4c286073fce0d07eca6e3e Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Wed, 24 Sep 2025 14:34:35 +0200 Subject: Move /imgs/ path --- src/components/Geshem.tsx | 2 +- src/pages/api/imgs.ts | 76 -------------------- src/pages/api/update.ts | 177 ---------------------------------------------- src/pages/imgs.ts | 76 ++++++++++++++++++++ wrangler.jsonc | 5 +- 5 files changed, 81 insertions(+), 255 deletions(-) delete mode 100644 src/pages/api/imgs.ts delete mode 100644 src/pages/api/update.ts create mode 100644 src/pages/imgs.ts diff --git a/src/components/Geshem.tsx b/src/components/Geshem.tsx index 982d841..85c1a11 100644 --- a/src/components/Geshem.tsx +++ b/src/components/Geshem.tsx @@ -21,7 +21,7 @@ export function Geshem({ date }: GeshemProps) { useEffect(() => { const fetchImages = async () => - fetch(`${IMAGES_BASE_URL}/imgs.json`) + fetch(`/imgs/`) .then(res => res.json()) .then(imgs => (imgs as any)["280"]) .then(setImages); diff --git a/src/pages/api/imgs.ts b/src/pages/api/imgs.ts deleted file mode 100644 index 378d36d..0000000 --- a/src/pages/api/imgs.ts +++ /dev/null @@ -1,76 +0,0 @@ -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 deleted file mode 100644 index 0127ee3..0000000 --- a/src/pages/api/update.ts +++ /dev/null @@ -1,177 +0,0 @@ -import type { APIRoute } from 'astro'; - -export const prerender = false; - - -interface CloudflareEnv { - BASE_URL: string; - AUTH_USER: string; - AUTH_PASS: string; - IMGS_BUCKET: R2Bucket; - SCHEDULED?: boolean; -} - -class GeshemUpdate { - private bucket: R2Bucket; - private env: CloudflareEnv; - - constructor(bucket: R2Bucket, env: CloudflareEnv) { - this.bucket = bucket; - this.env = env; - } - - private async authenticate(request: Request): Promise { - const headers = new Headers(request.headers); - const credentials = btoa(`${this.env.AUTH_USER}:${this.env.AUTH_PASS}`); - headers.set('Authorization', `Basic ${credentials}`); - - return new Request(request.url, { - method: request.method, - headers, - body: request.body - }); - } - - private async getLatestImages(): Promise { - const authenticatedRequest = await this.authenticate(new Request(this.env.BASE_URL)); - const response = await fetch(authenticatedRequest); - const html = await response.text(); - - const regex = /radar280comp_\d+\.png/g; - const matches = html.match(regex) || []; - const uniqueImages = [...new Set(matches)]; - - return uniqueImages.sort().slice(-10); - } - - private async getLatestBucketKeys(): Promise { - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - const yesterdayStr = yesterday.toISOString().slice(0, 10).replace(/-/g, ''); - - try { - const objects = await this.bucket.list({ - prefix: 'imgs/', - startAfter: `imgs/${yesterdayStr}` - }); - - return objects.objects.map(obj => obj.key); - } catch (error) { - console.error('Error listing bucket objects:', error); - return []; - } - } - - private async fetchMissingImages(): Promise { - const images = await this.getLatestImages(); - const s3Images = await this.getLatestBucketKeys(); - let updated = false; - - for (const img of images) { - const key = this.keyFromFilename(img); - if (!s3Images.includes(key)) { - await this.fetchImage(img, key); - updated = true; - } - } - - return updated; - } - - private async fetchImage(imgName: string, key: string): Promise { - console.log(`Downloading ${imgName} from web server`); - - const imageUrl = `${this.env.BASE_URL}/${imgName}`; - const authenticatedRequest = await this.authenticate(new Request(imageUrl)); - const response = await fetch(authenticatedRequest); - - if (!response.ok) { - throw new Error(`Failed to fetch image: ${response.statusText}`); - } - - const imageData = await response.arrayBuffer(); - - console.log(`Uploading to ${key}`); - await this.bucket.put(key, imageData, { - httpMetadata: { - contentType: 'image/png', - cacheControl: 'public, max-age=31536000' - } - }); - } - - private keyFromFilename(filename: string): string { - const [name, dateStr] = filename.split('.')[0].split('_'); - const res = 280; - - // Parse date: YYYYMMDDHHMM - const year = dateStr.slice(0, 4); - const month = dateStr.slice(4, 6); - const day = dateStr.slice(6, 8); - const hour = dateStr.slice(8, 10); - const minute = dateStr.slice(10, 12); - - const d = `${year}${month}${day}`; - const t = `${hour}${minute}`; - - return `imgs/${d}/${t}/${res}.png`; - } - - - async run(): Promise { - try { - const updated = await this.fetchMissingImages(); - return `Updated: ${updated}`; - } catch (error) { - console.error('Error in GeshemUpdate.run():', error); - throw error; - } - } -} - -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) { - return new Response('Unauthorized', { status: 401 }); - } - - try { - 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 }); - } - - if (!env.IMGS_BUCKET) { - return new Response('R2 bucket not configured', { status: 500 }); - } - - const bucket = env.IMGS_BUCKET; - - const updater = new GeshemUpdate(bucket, env); - const result = await updater.run(); - - return new Response(JSON.stringify({ - message: `SUCCESS: ${result}`, - timestamp: new Date().toISOString() - }), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - - } catch (error) { - console.error('Error in update handler:', error); - return new Response(JSON.stringify({ - message: 'ERROR', - error: 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/imgs.ts b/src/pages/imgs.ts new file mode 100644 index 0000000..378d36d --- /dev/null +++ b/src/pages/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/wrangler.jsonc b/wrangler.jsonc index ac53b79..fdb2ae8 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -27,5 +27,8 @@ "pattern": "www.geshem.space", "custom_domain": true } - ] + ], + // "triggers": { + // "crons": ["* * * * *"] + // } } \ No newline at end of file -- cgit v1.3.1