feat: add lw7 ticket og.

This commit is contained in:
thorwebdev committed 2023-02-23 17:03:00 +08:00
1 parent 0d5a5b7501
commit 50a2cddd8d
6 files changed
+283 -3

No files matched your search

@@ -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 ?? ''
)}`
@@ -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((_) => {})
@@ -36,7 +36,7 @@ export default function TicketShare({ username, ticketNumber, name, golden, refe
return <Error statusCode={404} />
}
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 ?? ''
)}`
@@ -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
```
@@ -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(
(
<>
<div
style={{
width: '1200px',
height: '630px',
backgroundColor: '#000',
color: '#fff',
fontFamily: '"Circular"',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{/* Background image color */}
<img
width="1200"
height="630"
style={{
position: 'absolute',
top: '0',
left: '0',
zIndex: '-9000',
}}
src={golden ? BACKGROUND_IMAGE_GOLDEN : BACKGROUND_IMAGE_STD}
/>
{/* Background image overlay */}
<img
width="1200"
height="630"
style={{
position: 'absolute',
top: '0',
left: '0',
zIndex: '-9000',
}}
src={BACKGROUND_SHAPE}
/>
{/* GitHub Avatar image */}
<img
width="200"
height="200"
style={{
position: 'absolute',
top: '215',
left: '155',
borderRadius: 100,
}}
src={`https://github.com/${username}.png`}
/>
{/* Name & username */}
<div
style={{
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
position: 'absolute',
top: '215',
left: '400',
width: '550',
height: '200',
overflow: 'hidden',
textOverflow: 'clip',
}}
>
<div
style={{
display: 'flex',
color: 'transparent',
backgroundImage:
'linear-gradient(90deg, #F8F9FA 1.73%, rgba(248, 249, 250, 0.5) 100%)',
backgroundClip: 'text',
}}
>
<p
style={{
fontSize: '60px',
lineHeight: '60px',
}}
>
{name ?? username}
</p>
</div>
{/* Username and supaverified checkmark */}
<div
style={{
display: 'flex',
fontSize: '25',
color: golden ? '#fff' : '#A0A0A0',
}}
>
<span>{`@${username}`}</span>
</div>
</div>
{/* Date */}
<p
style={{
position: 'absolute',
top: '520',
left: '400',
fontSize: '22',
color: golden ? '#fff' : '#A0A0A0',
}}
>
December 12th 2022
</p>
{/* URL */}
<p
style={{
position: 'absolute',
top: '520',
left: '680',
fontSize: '22',
color: golden ? '#fff' : '#A0A0A0',
}}
>
supabase.com/launch-week
</p>
</div>
{/* Ticket No */}
<div
style={{
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
bottom: '225',
right: '-165',
width: '575',
height: '175',
transform: 'rotate(90deg)',
}}
>
<p
style={{
fontSize: '70',
}}
>
{`No ${prefix}${ticketNumber}`}
</p>
</div>
</>
),
{
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,
})
}
}
@@ -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)