diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx
index fd7c62842a4..1f11be290c7 100644
--- a/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx
@@ -18,7 +18,7 @@ export default function TicketActions({ username, golden = false }: Props) {
const { userData } = useConfData()
const tweetUrl = `https://twitter.com/intent/tweet?url=${permalink}&via=supabase&text=${text}`
const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${permalink}`
- const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent(
+ const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent(
userData.username ?? ''
)}`
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx
index 44ad832b3d8..d348626896c 100644
--- a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx
@@ -57,7 +57,7 @@ export default function Form({ defaultUsername = '', setTicketGenerationState }:
fetch(`/launch-week/tickets/${username}`).catch((_) => {})
// Prefetch ticket og image.
fetch(
- `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent(
+ `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent(
username ?? ''
)}`
).catch((_) => {})
diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx
index 45b3b027df3..9a58b1a3ab1 100644
--- a/apps/www/pages/launch-week/tickets/[username].tsx
+++ b/apps/www/pages/launch-week/tickets/[username].tsx
@@ -36,7 +36,7 @@ export default function TicketShare({ username, ticketNumber, name, golden, refe
return
}
- const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent(
+ const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent(
username ?? ''
)}`
diff --git a/apps/www/supabase/functions/lw7-ticket-og/README.md b/apps/www/supabase/functions/lw7-ticket-og/README.md
new file mode 100644
index 00000000000..3eaafadbae2
--- /dev/null
+++ b/apps/www/supabase/functions/lw7-ticket-og/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/lw7-ticket-og?username=thorwebdev
+
+## Run locally
+
+```bash
+supabase functions serve --no-verify-jwt
+```
+
+Navigate to http://localhost:54321/functions/v1/lw7-ticket-og?username=thorwebdev
+
+## Deploy
+
+```bash
+supabase functions deploy lw7-ticket-og --no-verify-jwt
+```
diff --git a/apps/www/supabase/functions/lw7-ticket-og/handler.tsx b/apps/www/supabase/functions/lw7-ticket-og/handler.tsx
new file mode 100644
index 00000000000..2cce5a4bc15
--- /dev/null
+++ b/apps/www/supabase/functions/lw7-ticket-og/handler.tsx
@@ -0,0 +1,248 @@
+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.7.1'
+import { corsHeaders } from '../_shared/cors.ts'
+
+const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw7'
+const BACKGROUND_SHAPE = `${STORAGE_URL}/lw7_ticket_shape.png`
+const BACKGROUND_IMAGE_STD = `${STORAGE_URL}/lw7_ticket_regular.png`
+const BACKGROUND_IMAGE_GOLDEN = `${STORAGE_URL}/lw7_ticket_gold.png`
+
+// Load custom font
+const FONT_URL = `${STORAGE_URL}/CircularStd-Book.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 username = url.searchParams.get('username') ?? url.searchParams.get('amp;username')
+ const userAgent = req.headers.get('user-agent')
+
+ try {
+ if (!username) throw new Error('missing username param')
+ // Track social shares
+ 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') ?? ''
+ )
+ if (userAgent?.toLocaleLowerCase().includes('twitter')) {
+ const { error } = await supabaseAdminClient
+ .from('lw7_tickets')
+ .update({ sharedOnTwitter: 'now' })
+ .eq('username', username)
+ .is('sharedOnTwitter', null)
+ if (error) console.log(error.message)
+ } else if (userAgent?.toLocaleLowerCase().includes('linkedin')) {
+ const { error } = await supabaseAdminClient
+ .from('lw7_tickets')
+ .update({ sharedOnLinkedIn: 'now' })
+ .eq('username', username)
+ .is('sharedOnLinkedIn', null)
+ if (error) console.log(error.message)
+ }
+
+ // TODO: reenable storage
+ // Try to get image from Supabase Storage CDN.
+ // const storageResponse = await fetch(`${STORAGE_URL}/tickets/${username}.png`)
+ // if (storageResponse.ok) return storageResponse
+
+ // Get ticket data
+ const { data, error } = await supabaseAdminClient
+ .from('lw7_tickets_golden')
+ .select('name, ticketNumber, golden')
+ .eq('username', username)
+ .maybeSingle()
+ if (error) console.log(error.message)
+
+ const { name = 'Testr Test', ticketNumber = 1234 } = data ?? {}
+ const golden = data?.golden ?? false
+
+ // 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 color */}
+

+ {/* Background image overlay */}
+

+ {/* GitHub Avatar image */}
+

+ {/* Name & username */}
+
+
+
+ {name ?? username}
+
+
+ {/* Username and supaverified checkmark */}
+
+ {`@${username}`}
+
+
+ {/* Date */}
+
+ December 12th 2022
+
+ {/* URL */}
+
+ supabase.com/launch-week
+
+
+ {/* Ticket No */}
+
+
+ {`No ${prefix}${ticketNumber}`}
+
+
+ >
+ ),
+ {
+ width: 1200,
+ height: 630,
+ 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',
+ },
+ }
+ )
+
+ // TODO: reenable storage
+ // Upload image to storage.
+ // const { error: storageError } = await supabaseAdminClient.storage
+ // .from('images')
+ // .upload(`lw7/tickets/${username}.png`, generatedImage.body!, {
+ // contentType: 'image/png',
+ // cacheControl: '31536000',
+ // upsert: false,
+ // })
+ // if (storageError) throw storageError
+
+ return generatedImage
+ } catch (error) {
+ return new Response(JSON.stringify({ error: error.message }), {
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
+ status: 400,
+ })
+ }
+}
diff --git a/apps/www/supabase/functions/lw7-ticket-og/index.ts b/apps/www/supabase/functions/lw7-ticket-og/index.ts
new file mode 100644
index 00000000000..71d2a1df0a6
--- /dev/null
+++ b/apps/www/supabase/functions/lw7-ticket-og/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.177.0/http/server.ts'
+
+import { handler } from './handler.tsx'
+
+console.log(`Function "launchweek-ticket-og" up and running!`)
+
+serve(handler)