Skip to content

Commit

Permalink
feat: statistics poster (#128)
Browse files Browse the repository at this point in the history
* chore: refactor wording

* feat: add statistic poster

* chore: remove log
  • Loading branch information
tom-bywild authored Jan 5, 2023
1 parent 9bd1d5b commit 0b05bee
Show file tree
Hide file tree
Showing 17 changed files with 142 additions and 32 deletions.
2 changes: 1 addition & 1 deletion services/og-img-gen/.nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.6.0
18.12.1
14 changes: 8 additions & 6 deletions services/og-img-gen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ export default async function (req, res) {
path = path.slice(1);
}

const [domain, id] = path.split('/');
if (!domain || !id) {
console.warn(`Invalid request: domain=${domain} id=${id}`);
send(res, 400, `Invalid request: domain=${domain} id=${id}`);
const [domain, ...rest] = path.split('/');
if (!domain || !rest.length) {
console.warn(`Invalid request: domain=${domain} rest=${rest}`);
send(res, 400, `Invalid request: domain=${domain} rest=${rest}`);
return;
}

const allowedDomains = ['package', 'author'];
const allowedDomains = ['package', 'author', 'statistic'];
if (!allowedDomains.includes(domain)) {
warn(`Invalid domain: ${domain}`);
send(res, 400, `Invalid domain: ${domain}`);
return;
}

try {
const url = new URL(`${process.env.FE_BASE_URL}/${domain}/${id}/poster`);
const urlPath = [process.env.FE_BASE_URL, domain, ...rest].join('/');
const url = new URL(urlPath);

console.log(`Generating screenshot for ${url}`);

const imageBuffer = await getScreenshot(url.toString());
Expand Down
2 changes: 1 addition & 1 deletion web/src/lib/controls/views/CommonControls.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<ControlsLink withGap href="/" title="Latest packages">
<Iconic name="carbon:switcher" size="16" />
</ControlsLink>
<ControlsLink withGap href="/statistics" title="Statistics">
<ControlsLink withGap href="/statistic" title="Statistics">
<Iconic name="carbon:chart-line" size="16" />
</ControlsLink>
</svelte:fragment>
Expand Down
11 changes: 8 additions & 3 deletions web/src/lib/seo/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ export function serializeSchema(thing: Schema) {
return `<script type="application/ld+json">${JSON.stringify(thing, null, 2)}</script>`;
}

export async function fetchOgPosterImage(domain: 'author' | 'package', id?: string) {
const url = new URL(`${import.meta.env.VITE_BASE_OG_POSTER_API_URL}/${domain}/${id}`);
const res = await fetch(url.toString());
export async function fetchOgPosterImage(
domain: 'author' | 'package' | 'statistic',
...rest: string[]
) {
const pathBase = import.meta.env.VITE_BASE_OG_POSTER_API_URL;
const urlPath = [pathBase, domain, ...rest, 'poster'].join('/');
const url = new URL(urlPath);

const res = await fetch(url.toString());
return res.arrayBuffer();
}
13 changes: 9 additions & 4 deletions web/src/lib/seo/views/OpenGraphImage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import clsx from 'clsx';
export let title: string;
export let titleSize: 'lg' | 'xl' = 'xl';
export let titleSize: 'md' | 'lg' | 'xl' = 'xl';
export let subtitleSize: 'lg' | 'xl' = 'xl';
export let subtitle: string | undefined = undefined;
export let theme: HeroGradientTheme | undefined = undefined;
Expand All @@ -24,6 +24,8 @@
>
<h1
class={clsx('font-bold', {
'text-[clamp(2.4rem,7vw,4rem)]': titleSize === 'md' && !isSingleLongTitle,
'text-[clamp(2rem,8vw,2.8rem)]': titleSize === 'md' && isSingleLongTitle,
'text-[clamp(2.8rem,8vw,7rem)]': titleSize === 'lg' && !isSingleLongTitle,
'text-[clamp(2.8rem,8vw,5rem)]': titleSize === 'lg' && isSingleLongTitle,
'text-[clamp(2.8rem,9vw,9rem)]': titleSize === 'xl' && !isSingleLongTitle,
Expand All @@ -34,14 +36,17 @@
>
{title}
</h1>
<h2
<div
class={clsx('text-base opacity-70 text-center leading-6 lg:leading-10', {
'text-[clamp(0.8rem,8vw,1.8rem)]': subtitleSize === 'lg',
'text-[clamp(1rem,3vw,2rem)]': subtitleSize === 'xl'
})}
>
{subtitle}
</h2>
{#if subtitle}
<h2>{subtitle}</h2>
{/if}
<slot name="subtitle" />
</div>

<slot />
</section>
2 changes: 1 addition & 1 deletion web/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<ControlsBase variant="black">
<SearchControls theme="dark">
<svelte:fragment slot="links-start">
<ControlsLink withGap href="/statistics" title="Statistics">
<ControlsLink withGap href="/statistic" title="Statistics">
<Iconic name="carbon:chart-line" size="16" />
</ControlsLink>
</svelte:fragment>
Expand Down
6 changes: 5 additions & 1 deletion web/src/routes/api/author/[id]/poster.jpeg/+server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error, type RequestHandler } from '@sveltejs/kit';
import { fetchOgPosterImage } from '$lib/seo/model';

export const GET: RequestHandler = async (ctx) => {
if (!ctx.params.id) {
throw error(400, 'Missing author id');
}

const imageBuffer = await fetchOgPosterImage('author', ctx.params.id);
return new Response(imageBuffer, {
headers: {
Expand Down
12 changes: 10 additions & 2 deletions web/src/routes/api/package/[id]/poster.jpeg/+server.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error, type RequestHandler } from '@sveltejs/kit';
import { fetchOgPosterImage } from '$lib/seo/model';

export const GET: RequestHandler = async (ctx) => {
if (!ctx.params.id) {
throw error(400, 'Missing package id');
}

const imageBuffer = await fetchOgPosterImage('package', ctx.params.id);

// 1 week.
const cacheThreshold = 60 * 60 * 24 * 7;

return new Response(imageBuffer, {
headers: {
'Content-Type': 'image/jpeg',
'Cache-Control': 's-maxage=3600, stale-while-revalidate=86400'
'Cache-Control': `s-maxage=${cacheThreshold}, stale-while-revalidate=${cacheThreshold + 60}`
}
});
};
16 changes: 16 additions & 0 deletions web/src/routes/api/statistic/[...rest]/poster.jpeg/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { error, type RequestHandler } from '@sveltejs/kit';
import { fetchOgPosterImage } from '$lib/seo/model';

export const GET: RequestHandler = async ({ params }) => {
if (!params.rest) {
throw error(400, 'Missing path fragments');
}

const imageBuffer = await fetchOgPosterImage('statistic', ...params.rest.split('/'));
return new Response(imageBuffer, {
headers: {
'Content-Type': 'image/jpeg',
'Cache-Control': 's-maxage=60, stale-while-revalidate=3600'
}
});
};
2 changes: 1 addition & 1 deletion web/src/routes/author/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
<ControlsLink withGap href="/" title="Latest packages">
<Iconic name="carbon:switcher" size="16" />
</ControlsLink>
<ControlsLink withGap href="/statistics" title="Statistics">
<ControlsLink withGap href="/statistic" title="Statistics">
<Iconic name="carbon:chart-line" size="16" />
</ControlsLink>
</svelte:fragment>
Expand Down
4 changes: 2 additions & 2 deletions web/src/routes/package/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
<ControlsLink withGap href="/" title="Latest packages">
<Iconic name="carbon:switcher" size="16" />
</ControlsLink>
<ControlsLink withGap href="/statistics" title="Statistics">
<ControlsLink withGap href="/statistic" title="Statistics">
<Iconic name="carbon:chart-line" size="16" />
</ControlsLink>
</svelte:fragment>
Expand Down Expand Up @@ -200,7 +200,7 @@
<!-- Usage -->

{#if downloads.length}
<Section withTwoFoldLayout withPaddingX={false} id="statistics">
<Section withTwoFoldLayout withPaddingX={false} id="statistic">
<SectionHeader>
<SectionTitleSelect selected="Statistics" options={titles} />
</SectionHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
const titles = ['Github'];
</script>

<BasePageInit title="Statistics" path="/statistics" />
<BasePageInit title="Statistics" path="/statistic" />
<CommonControls />

<main>
<Hero
isFixed
title="Statistics"
titleSize="lg"
subtitle="General statistics about the R package ecosystem"
subtitle="General statistic about the R package ecosystem"
height="50"
theme="dark"
variant="prominent"
Expand All @@ -42,7 +42,7 @@
withSpaceY="xs"
key="Trending packages by stars"
title="Trending packages by stars"
url="/statistics/github/stars/6h"
url="/statistic/github/stars/6h"
emphasis="key"
class="p-0"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load: PageLoad = async () => {
throw redirect(301, '/statistics/github/stars/6h');
throw redirect(301, '/statistic/github/stars/6h');
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@
<BasePageInit
withBreadcrumb={false}
title="Trending R packages by Github stars for the last {mapRangeToLabel(selectedRange)}"
path="/statistics/github/stars/{selectedRange}"
path="/statistic/github/stars/{selectedRange}"
/>
<BreadcrumbMeta
items={[
{ name: 'Statistics', href: '/statistics' },
{ name: 'Github statistics', href: '/github/statistics' },
{ name: 'Trending R packages by Github stars', href: '/statistics/github/stars' },
{ name: 'Statistics', href: '/statistic' },
{ name: 'Github statistic', href: '/github/statistic' },
{ name: 'Trending R packages by Github stars', href: '/statistic/github/stars' },
{
name: `Trending R packages by Github stars for the last ${mapRangeToLabel(selectedRange)}`,
href: `/statistics/github/stars/${selectedRange}`
href: `/statistic/github/stars/${selectedRange}`
}
]}
/>
Expand All @@ -59,7 +59,7 @@
class="appearance-none bg-black/0 overflow-hidden border border-neutral-500 px-2 rounded cursor-pointer"
on:change={(ev) => {
const { value } = ev.currentTarget;
window.location.href = `/statistics/github/stars/${value}`;
window.location.href = `/statistic/github/stars/${value}`;
}}
>
{#each ranges as range}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { githubTrendRanges, fetchReposByStars } from '$lib/statistics/models/github';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, setHeaders }) => {
setHeaders({
'Cache-Control': 's-maxage=3600, stale-while-revalidate=7200'
});

// @ts-expect-error Range not picked up by type inference.
const range: typeof githubTrendRanges[number] = params.range;

if (!githubTrendRanges.includes(range)) {
throw error(401, `Invalid range: ${range}`);
}

const { items } = await fetchReposByStars({ range });

return {
items: items.slice(0, 3),
selectedRange: range
};
};
47 changes: 47 additions & 0 deletions web/src/routes/statistic/github/stars/[range]/poster/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script lang="ts">
import Iconic from '$lib/blocks/views/Iconic.svelte';
import OpenGraphImage from '$lib/seo/views/OpenGraphImage.svelte';
import { mapRangeToLabel } from '$lib/statistics/models/github';
import type { PageData } from './$types';
export let data: PageData;
const { items, selectedRange } = data;
const subtitle =
items && items.length
? `By stars within last ${mapRangeToLabel(selectedRange)}`
: `No trend yet available for last ${mapRangeToLabel(selectedRange)}`;
</script>

<OpenGraphImage
title={`Trending R-code on GitHub`}
{subtitle}
titleSize="md"
textVariant="fit"
theme="gradient-dark-zinc"
>
{#if items && items.length}
<div class="flex items-center space-x-16 text-[clamp(1.2rem,3vw,1.8rem)] opacity-80">
{#each items as item}
<div class="flex flex-col items-center">
<div class="flex items-center space-x-1">
<span>
{item.trend.stargazers_count > 0 ? '+' : ''}
{item.trend.stargazers_count}
</span>
<Iconic name="carbon:star-filled" class="w-6 h-6" />
</div>
<div class="flex items-center gap-x-2">
<img
src={item.original.owner.avatar_url}
class="w-7 h-7 rounded-full overflow-hidden"
alt="Github avatar"
loading="eager"
/>
{item.original.name}
</div>
</div>
{/each}
</div>
{/if}
</OpenGraphImage>

0 comments on commit 0b05bee

Please sign in to comment.