| 395 | // list. Cached per slug, so each redirect page fills a tiny cache entry |
| 396 | // instead of waiting on the full-table plugins fetch. |
| 397 | export async function getRuleRedirectTarget(ruleSlug: string): Promise<{ |
| 398 | data: string | null; |
| 399 | error: unknown; |
| 400 | }> { |
| 401 | "use cache"; |
| 402 | cacheLife("hours"); |
| 403 | cacheTag("plugins"); |
| 404 | |
| 405 | const supabase = await createClient(); |
| 406 | const { data, error } = await supabase |
| 407 | .from("plugin_components") |
| 408 | .select("plugins!inner(slug, created_at)") |
| 409 | .eq("type", "rule") |
| 410 | .eq("slug", ruleSlug) |
| 411 | .eq("plugins.active", true); |
| 412 | |
| 413 | if (error) return { data: null, error }; |
| 414 | |
| 415 | const newest = (data ?? []) |
| 416 | .map( |
| 417 | (row) => |
| 418 | row.plugins as unknown as { slug: string; created_at: string } | null, |
| 419 | ) |
| 420 | .filter((plugin) => plugin !== null) |
| 421 | .sort( |
| 422 | (a, b) => |
| 423 | new Date(b.created_at).getTime() - new Date(a.created_at).getTime(), |
| 424 | )[0]; |
| 425 | |
| 426 | return { data: newest?.slug ?? null, error: null }; |
| 427 | } |
| 428 | |
| 429 | // Returns a Map<plugin_id, installs in last `windowDays` days>, derived |
| 430 | // from `plugin_install_snapshots` via the `plugin_install_velocity` SQL |