From 32f39cf1d9ec41419fd1367b1eaa804b201eaecb Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Wed, 23 Nov 2022 16:41:23 +0800 Subject: [PATCH] feat: add og image storage caching example. --- apps/docs/pages/guides/functions/auth.mdx | 2 +- .../og-image-with-storage-cdn/README.md | 21 ++ .../og-image-with-storage-cdn/handler.tsx | 205 ++++++++++++++++++ .../og-image-with-storage-cdn/index.ts | 11 + .../supabase/functions/opengraph/README.md | 8 + .../supabase/functions/opengraph/handler.tsx | 4 +- .../supabase/functions/opengraph/index.ts | 5 +- 7 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 examples/edge-functions/supabase/functions/og-image-with-storage-cdn/README.md create mode 100644 examples/edge-functions/supabase/functions/og-image-with-storage-cdn/handler.tsx create mode 100644 examples/edge-functions/supabase/functions/og-image-with-storage-cdn/index.ts diff --git a/apps/docs/pages/guides/functions/auth.mdx b/apps/docs/pages/guides/functions/auth.mdx index 2a611f8c3b9..871c16bd6f2 100644 --- a/apps/docs/pages/guides/functions/auth.mdx +++ b/apps/docs/pages/guides/functions/auth.mdx @@ -21,7 +21,7 @@ By creating a supabase client with the auth context from the function, you can d ```js lines=14,17-19,22-23 title=supabase/functions/select-from-table-with-auth-rls/index.ts import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' -import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0-rc.12' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' serve(async (req: Request) => { try { diff --git a/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/README.md b/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/README.md new file mode 100644 index 00000000000..2adb83328c7 --- /dev/null +++ b/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/README.md @@ -0,0 +1,21 @@ +# Open Graph (OG) Image Generation with Supabase Storage CDN Caching + +Generate Open Graph images with Deno and Supabase Edge Functions and cache the generated image with Supabase Storage CDN. + +- Docs: https://deno.land/x/og_edge@0.0.2 +- Examples: https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples +- Demo: https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?ticketNumber=1234&username=thorwebdev&name=Thor%20%E9%9B%B7%E7%A5%9E%20Schaeff + +## Run locally + +```bash +supabase functions serve og-image-with-storage-cdn --no-verify-jwt +``` + +Navigate to http://localhost:54321/functions/v1/og-image-with-storage-cdn?ticketNumber=3524&username=thorwebdev&name=Thor%20%E9%9B%B7%E7%A5%9E%20Schaeff + +## Deploy + +```bash +supabase functions deploy og-image-with-storage-cdn --no-verify-jwt +``` diff --git a/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/handler.tsx b/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/handler.tsx new file mode 100644 index 00000000000..6a47dc69170 --- /dev/null +++ b/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/handler.tsx @@ -0,0 +1,205 @@ +import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' +import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' +import { corsHeaders } from '../_shared/cors.ts' + +const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw6' +const BACKGROUND_IMAGE_STD = `${STORAGE_URL}/lw6_ticket_regular.jpg` +const BACKGROUND_IMAGE_GOLDEN = `${STORAGE_URL}/lw6_ticket_gold.jpg` +const SUPA_CHECKMARK = `${STORAGE_URL}/supaverified.png` + +// Load custom font +const FONT_URL = `${STORAGE_URL}/CircularStd-Bold.otf` +const font = fetch(new URL(FONT_URL, import.meta.url)).then((res) => res.arrayBuffer()) + +export async function handler(req: Request) { + const url = new URL(req.url) + const ticketNumber = url.searchParams.get('ticketNumber') + const username = url.searchParams.get('username') ?? url.searchParams.get('amp;username') + const name = url.searchParams.get('name') ?? url.searchParams.get('amp;name') + const golden = url.searchParams.get('golden') ?? url.searchParams.get('amp;golden') + + if (!username || !ticketNumber || !name) + return new Response(JSON.stringify({ error: 'missing params' }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + + try { + // Try to get image from Supabase Storage CDN. + const storageResponse = await fetch(`${STORAGE_URL}/tickets/${username}.png?version=dev`) // TODO change for prod. + if (storageResponse.ok) return storageResponse + + // Else, generate image ad upload to storage. + const fontData = await font + const numDigits = `${Number(ticketNumber)}`.length + const prefix = `00000000`.slice(numDigits) + + const generatedImage = new ImageResponse( + ( +
+ {/* Background image */} + + {/* GitHub Avatar image */} + + {/* Name & username */} +
+

+ {name} +

+ {/* Username and supaverified checkmark */} +
+ {`@${username}`} + + + +
+
+ {/* Date */} +

+ December 12th, 2022 +

+ {/* URL */} +

+ supabase.com/launch-week +

+ {/* Ticket No */} +
+

+ {`№ ${prefix}${ticketNumber}`} +

+
+
+ ), + { + width: 2000, + height: 1000, + fonts: [ + { + name: 'Circular', + data: fontData, + style: 'normal', + }, + ], + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', + 'cdn-cache-control': 'max-age=31536000', + }, + } + ) + + const supabaseAdminClient = createClient( + // Supabase API URL - env var exported by default when deployed. + Deno.env.get('SUPABASE_URL') ?? '', + // Supabase API SERVICE ROLE KEY - env var exported by default when deployed. + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' + ) + + // Upload image to storage. + const { error } = await supabaseAdminClient.storage + .from('images') + .upload(`lw6/tickets/${username}.png`, generatedImage.body!, { + // cacheControl: '31536000', // TODO add for prod + upsert: false, + }) + if (error) throw error + + return generatedImage + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + } +} diff --git a/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/index.ts b/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/index.ts new file mode 100644 index 00000000000..5b2cf3da0b9 --- /dev/null +++ b/examples/edge-functions/supabase/functions/og-image-with-storage-cdn/index.ts @@ -0,0 +1,11 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +import { serve } from 'https://deno.land/std@0.140.0/http/server.ts' + +import { handler } from './handler.tsx' + +console.log(`Function "og-image-with-storage-cdn" up and running!`) + +serve(handler) diff --git a/examples/edge-functions/supabase/functions/opengraph/README.md b/examples/edge-functions/supabase/functions/opengraph/README.md index a8a337e0065..9242524394e 100644 --- a/examples/edge-functions/supabase/functions/opengraph/README.md +++ b/examples/edge-functions/supabase/functions/opengraph/README.md @@ -5,6 +5,14 @@ Generate Open Graph images with Deno and Supabase Edge Functions, no framework n - Docs: https://deno.land/x/og_edge@0.0.2 - Demo: https://zdtdtxajzydjqzuktnqx.functions.supabase.co/opengraph +## Run locally + +```bash +supabase functions serve opengraph --no-verify-jwt +``` + +Navigate to http://localhost:54321/functions/v1/opengraph + ## Deploy ```bash diff --git a/examples/edge-functions/supabase/functions/opengraph/handler.tsx b/examples/edge-functions/supabase/functions/opengraph/handler.tsx index c5448aa74db..ef64dc4b8e9 100644 --- a/examples/edge-functions/supabase/functions/opengraph/handler.tsx +++ b/examples/edge-functions/supabase/functions/opengraph/handler.tsx @@ -1,7 +1,7 @@ import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' -import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.2/mod.ts' +import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' -export async function handler(req: Request) { +export function handler(req: Request) { return new ImageResponse( (