|
| 1 | +/* eslint-disable react/no-unknown-property */ |
| 2 | +import 'server-only'; |
| 3 | + |
| 4 | +import { fetchArtists, fetchShowByUUID } from '@/app/queries'; |
| 5 | +import { ImageResponse } from 'next/og'; |
| 6 | +import { NextRequest } from 'next/server'; |
| 7 | +import React from 'react'; |
| 8 | + |
| 9 | +export const runtime = 'edge'; |
| 10 | + |
| 11 | +const notFound = () => |
| 12 | + new Response('Not Found', { |
| 13 | + status: 404, |
| 14 | + }); |
| 15 | + |
| 16 | +// Function to generate a color based on artist UUID |
| 17 | +const getArtistColor = (uuid: string) => { |
| 18 | + // Generate a consistent color based on the UUID |
| 19 | + const hash = Array.from(uuid).reduce((acc, char) => char.charCodeAt(0) + ((acc << 5) - acc), 0); |
| 20 | + |
| 21 | + // Map to a curated set of Tailwind colors |
| 22 | + const tailwindColors = [ |
| 23 | + '#3b82f6', // blue-500 |
| 24 | + '#10b981', // emerald-500 |
| 25 | + '#8b5cf6', // violet-500 |
| 26 | + '#ef4444', // red-500 |
| 27 | + '#f59e0b', // amber-500 |
| 28 | + '#06b6d4', // cyan-500 |
| 29 | + '#f97316', // orange-500 |
| 30 | + '#8b5cf6', // violet-500 |
| 31 | + '#0ea5e9', // sky-500 |
| 32 | + '#22c55e', // green-500 |
| 33 | + '#dc2626', // red-600 |
| 34 | + '#0d9488', // teal-600 |
| 35 | + ]; |
| 36 | + |
| 37 | + return tailwindColors[Math.abs(hash) % tailwindColors.length]; |
| 38 | +}; |
| 39 | + |
| 40 | +// Function to generate a gradient based on artist UUID |
| 41 | +const getArtistGradient = (uuid: string) => { |
| 42 | + const baseColor = getArtistColor(uuid); |
| 43 | + |
| 44 | + // Create complementary color pairs for beautiful gradients using Tailwind colors |
| 45 | + const gradientPairs = { |
| 46 | + '#3b82f6': '#8b5cf6', // blue-500 to violet-500 |
| 47 | + '#10b981': '#06b6d4', // emerald-500 to cyan-500 |
| 48 | + '#8b5cf6': '#3b82f6', // violet-500 to blue-500 |
| 49 | + '#ef4444': '#f97316', // red-500 to orange-500 |
| 50 | + '#f59e0b': '#ef4444', // amber-500 to red-500 |
| 51 | + '#06b6d4': '#10b981', // cyan-500 to emerald-500 |
| 52 | + '#f97316': '#f59e0b', // orange-500 to amber-500 |
| 53 | + // '#8b5cf6': '#a855f7', // violet-500 to purple-500 |
| 54 | + '#0ea5e9': '#3b82f6', // sky-500 to blue-500 |
| 55 | + '#22c55e': '#10b981', // green-500 to emerald-500 |
| 56 | + '#dc2626': '#ef4444', // red-600 to red-500 |
| 57 | + '#0d9488': '#06b6d4', // teal-600 to cyan-500 |
| 58 | + }; |
| 59 | + |
| 60 | + const secondColor = gradientPairs[baseColor] || '#8b5cf6'; // Default to violet-500 |
| 61 | + |
| 62 | + return `linear-gradient(135deg, ${baseColor}, ${secondColor})`; |
| 63 | +}; |
| 64 | + |
| 65 | +export async function GET(request: NextRequest) { |
| 66 | + try { |
| 67 | + const { searchParams } = new URL(request.url); |
| 68 | + |
| 69 | + const showUuid = searchParams.get('showUuid'); |
| 70 | + if (!showUuid) return notFound(); |
| 71 | + |
| 72 | + const artists = await fetchArtists(); |
| 73 | + const show = await fetchShowByUUID(showUuid); |
| 74 | + |
| 75 | + if (!show || !show.sources?.length) return notFound(); |
| 76 | + |
| 77 | + // Get params |
| 78 | + const artist = artists.find((artist) => artist.uuid === show.artist_uuid); |
| 79 | + const artistName = artist?.name ?? 'Unknown Artist'; |
| 80 | + |
| 81 | + // Generate dynamic background color and pattern based on artist UUID |
| 82 | + const bgGradient = getArtistGradient(show.artist_uuid || ''); |
| 83 | + |
| 84 | + // Generate a pattern of circles for the background based on artist UUID |
| 85 | + const patternSeed = parseInt(show.artist_uuid?.replace(/\D/g, '').slice(0, 8) || '0', 10); |
| 86 | + const patternElements: React.ReactNode[] = []; |
| 87 | + |
| 88 | + for (let i = 0; i < 5; i++) { |
| 89 | + const size = 100 + (patternSeed % 200); |
| 90 | + const x = (patternSeed * (i + 1)) % 900; |
| 91 | + const y = (patternSeed * (i + 2)) % 900; |
| 92 | + const opacity = 0.1 + i * 0.05; |
| 93 | + |
| 94 | + patternElements.push( |
| 95 | + <div |
| 96 | + key={i} |
| 97 | + tw="absolute rounded-full" |
| 98 | + style={{ |
| 99 | + width: size, |
| 100 | + height: size, |
| 101 | + left: x, |
| 102 | + top: y, |
| 103 | + background: 'white', |
| 104 | + opacity: opacity, |
| 105 | + filter: 'blur(40px)', |
| 106 | + }} |
| 107 | + /> |
| 108 | + ); |
| 109 | + } |
| 110 | + |
| 111 | + // Fetch Roboto font |
| 112 | + const fontReg = await fetch( |
| 113 | + new URL('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.ttf') |
| 114 | + ).then((res) => res.arrayBuffer()); |
| 115 | + |
| 116 | + const fontBold = await fetch( |
| 117 | + new URL('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-700-normal.ttf') |
| 118 | + ).then((res) => res.arrayBuffer()); |
| 119 | + |
| 120 | + const fontMegaBold = await fetch( |
| 121 | + new URL('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-900-normal.ttf') |
| 122 | + ).then((res) => res.arrayBuffer()); |
| 123 | + |
| 124 | + return new ImageResponse( |
| 125 | + ( |
| 126 | + <div |
| 127 | + tw="flex h-full w-full flex-col items-center justify-center p-10 text-white relative overflow-hidden" |
| 128 | + style={{ background: bgGradient }} |
| 129 | + > |
| 130 | + <div tw="flex w-full max-w-[800px] flex-col items-center justify-center rounded-xl bg-black/10 p-10"> |
| 131 | + <div tw="mb-2 text-center text-7xl font-extrabold">{artistName}</div> |
| 132 | + <div tw="mb-2 text-center text-6xl font-bold">{show.display_date}</div> |
| 133 | + <div tw="flex items-center justify-center" style={{ gap: 4 }}> |
| 134 | + {show.venue?.name && ( |
| 135 | + <div tw="rounded-lg bg-white/20 px-4 py-2 text-4xl flex"> |
| 136 | + {show.venue?.name} {show.venue?.location ?? ''} |
| 137 | + </div> |
| 138 | + )} |
| 139 | + </div> |
| 140 | + </div> |
| 141 | + <div tw="absolute bottom-5 right-5 text-4xl opacity-80 font-bold">Relisten.net</div> |
| 142 | + {patternElements} |
| 143 | + </div> |
| 144 | + ), |
| 145 | + { |
| 146 | + width: 1024, |
| 147 | + height: 1024, |
| 148 | + fonts: [ |
| 149 | + { |
| 150 | + name: 'Roboto', |
| 151 | + data: fontReg, |
| 152 | + weight: 400, |
| 153 | + style: 'normal', |
| 154 | + }, |
| 155 | + { |
| 156 | + name: 'Roboto', |
| 157 | + data: fontBold, |
| 158 | + weight: 700, |
| 159 | + style: 'normal', |
| 160 | + }, |
| 161 | + { |
| 162 | + name: 'Roboto', |
| 163 | + data: fontMegaBold, |
| 164 | + weight: 900, |
| 165 | + style: 'normal', |
| 166 | + }, |
| 167 | + ], |
| 168 | + } |
| 169 | + ); |
| 170 | + } catch (error) { |
| 171 | + console.error('Error generating album art:', error); |
| 172 | + return new Response('Error generating album art', { status: 500 }); |
| 173 | + } |
| 174 | +} |
0 commit comments