From 4f5a8c9a37d9b17edc93171db28de8b3eafb278b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:43:06 -0500 Subject: [PATCH] feat: patreon exempt --- packages/website/src/components/Ad.tsx | 11 ++- .../website/src/components/AdWithContext.tsx | 10 +++ packages/website/src/hooks/useAdExempt.ts | 13 +-- .../src/pages/api/patreon/membership/index.ts | 82 ++++++++++++++++++- packages/website/src/pages/index.astro | 6 +- 5 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 packages/website/src/components/AdWithContext.tsx diff --git a/packages/website/src/components/Ad.tsx b/packages/website/src/components/Ad.tsx index c17c4ae59..2082dd33a 100644 --- a/packages/website/src/components/Ad.tsx +++ b/packages/website/src/components/Ad.tsx @@ -1,12 +1,17 @@ import { Box, type BoxProps } from '@radix-ui/themes'; import { useEffect } from 'react'; +import { useAdExempt } from '../hooks/useAdExempt'; -type AdProps = BoxProps & { +export type AdProps = BoxProps & { id: string; }; export function Ad({ id, ...props }: AdProps) { + const exempt = useAdExempt(); + useEffect(() => { + if (exempt) return; + (window as any).nitroAds.createAd(id, { refreshTime: 30, demo: import.meta.env.MODE === 'development', @@ -19,7 +24,9 @@ export function Ad({ id, ...props }: AdProps) { position: 'top-right', }, }); - }, []); + }, [exempt, id]); + + if (exempt) return null; return ; } diff --git a/packages/website/src/components/AdWithContext.tsx b/packages/website/src/components/AdWithContext.tsx new file mode 100644 index 000000000..85639762a --- /dev/null +++ b/packages/website/src/components/AdWithContext.tsx @@ -0,0 +1,10 @@ +import { App } from '../stores/app'; +import { Ad, type AdProps } from './Ad'; + +export function AdWithContext(props: AdProps) { + return ( + + + + ); +} diff --git a/packages/website/src/hooks/useAdExempt.ts b/packages/website/src/hooks/useAdExempt.ts index f2f7ce1a9..825820b3d 100644 --- a/packages/website/src/hooks/useAdExempt.ts +++ b/packages/website/src/hooks/useAdExempt.ts @@ -5,7 +5,7 @@ let cache: Record> = {}; export function useAdExempt() { const patreon = App.use((state) => state.logins.patreon); - const [exempt, setExempt] = useState(false); + const [exempt, setExempt] = useState(true); useEffect(() => { (async () => { @@ -13,14 +13,17 @@ export function useAdExempt() { if (!(patreon.token in cache)) { cache[patreon.token] = new Promise(async (resolve) => { - const response = await fetch( - `/api/patreon/membership/${patreon.token}`, - { cache: 'force-cache' }, - ); + const response = await fetch(`/api/patreon/membership`, { + cache: 'default', + headers: { token: patreon.token }, + }); if (!response.ok) return resolve(false); const json = await response.json(); + + console.log(json); + const exempt = typeof json === 'boolean' && json; cache[patreon.token] = exempt; diff --git a/packages/website/src/pages/api/patreon/membership/index.ts b/packages/website/src/pages/api/patreon/membership/index.ts index 347c713e7..8e1cf61e9 100644 --- a/packages/website/src/pages/api/patreon/membership/index.ts +++ b/packages/website/src/pages/api/patreon/membership/index.ts @@ -1,7 +1,85 @@ +import { assertSecret } from '@blitzkit/core'; import type { APIContext } from 'astro'; export const prerender = false; -export async function GET({}: APIContext) { - return Response.json({ hello: Math.random() }); +interface PatreonMembershipResponse { + data: { + attributes: unknown; + id: `${number}`; + relationships: { + memberships: { + data: { + id: string; // uuid + type: 'member'; + }[]; + }; + }; + type: 'user'; + }; + included?: ( + | { + attributes: { + patron_status: + | 'active_patron' + | 'declined_patron' + | 'former_patron' + | null; // null = never a paid member + }; + id: string; // uuid + relationships: { + campaign: { + data: { + id: `${number}`; // id of my campaign to check against + type: 'campaign'; + }; + links: { + related: string; // url + }; + }; + }; + type: 'member'; + } + | { + attributes: {}; + id: `${number}`; + type: 'campaign'; // ignore this discriminator + } + )[]; + links: { + self: 'https://www.patreon.com/api/oauth2/v2/user/124201389'; + }; +} + +export async function GET({ request }: APIContext) { + const token = request.headers.get('token'); + + if (!token) return Response.json(false); + + console.log('doing it'); + + const patreonResponse = await fetch( + encodeURI( + 'https://www.patreon.com/api/oauth2/v2/identity?include=memberships.campaign&fields[member]=patron_status', + ), + { headers: { Authorization: `Bearer ${token}` } }, + ); + + if (!patreonResponse.ok) return Response.json(false); + + const json = (await patreonResponse.json()) as PatreonMembershipResponse; + + const response = Response.json( + json.included?.some( + (item) => + item.type === 'member' && + item.relationships.campaign.data.id === + assertSecret(process.env.PATREON_CAMPAIGN_ID) && + item.attributes.patron_status === 'active_patron', + ) ?? false, + ); + + response.headers.set('Cache-Control', 'max-age=86400'); // a day + + return response; } diff --git a/packages/website/src/pages/index.astro b/packages/website/src/pages/index.astro index 3d0ba976a..a9ad4a090 100644 --- a/packages/website/src/pages/index.astro +++ b/packages/website/src/pages/index.astro @@ -1,6 +1,6 @@ --- import { Box, Flex, Heading } from '@radix-ui/themes'; -import { Ad } from '../components/Ad'; +import { AdWithContext } from '../components/AdWithContext'; import HomeHero from '../components/HomeHero.astro'; import { Plugs } from '../components/Plugs.tsx'; import ToolCard from '../components/ToolCard.astro'; @@ -24,7 +24,7 @@ import BaseLayout from '../layouts/BaseLayout.astro'; - + {/* */} @@ -39,7 +39,7 @@ import BaseLayout from '../layouts/BaseLayout.astro'; - +