From 9afd2c4dff11ec32129cc879567bce29283deab1 Mon Sep 17 00:00:00 2001 From: Francis Turmel Date: Tue, 9 Jul 2024 08:09:26 -0400 Subject: [PATCH 1/3] YouTube redirects feature --- netlify/edge-functions/yt/index.mjs | 28 +++++++++++ netlify/edge-functions/yt/redirects.json | 1 + node-scripts/generate-youtube-redirects.js | 55 ++++++++++++++++++++++ package.json | 2 +- 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 netlify/edge-functions/yt/index.mjs create mode 100644 netlify/edge-functions/yt/redirects.json create mode 100644 node-scripts/generate-youtube-redirects.js diff --git a/netlify/edge-functions/yt/index.mjs b/netlify/edge-functions/yt/index.mjs new file mode 100644 index 000000000..a504fe436 --- /dev/null +++ b/netlify/edge-functions/yt/index.mjs @@ -0,0 +1,28 @@ +// The JSON lookup gets generated at build time, do not manually edit. See `node-scripts/generate-youtube-redirects.js` +import redirects from './redirects.json' assert { type: 'json' }; + +export default async (request) => { + const url = new URL(request.url); + const youtubeId = url.pathname.split('/')[2]; + + const headers = { + 'Cache-Control': 'public, max-age=86400' // 24h + }; + + if (redirects[youtubeId]) { + return new Response(null, { + status: 302, + headers: { + ...headers, + Location: redirects[youtubeId] + } + }); + } + + return new Response('Not Found', { + status: 404, + headers + }); +}; + +export const config = { path: '/yt/:youtubeId' }; diff --git a/netlify/edge-functions/yt/redirects.json b/netlify/edge-functions/yt/redirects.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/netlify/edge-functions/yt/redirects.json @@ -0,0 +1 @@ +{} diff --git a/node-scripts/generate-youtube-redirects.js b/node-scripts/generate-youtube-redirects.js new file mode 100644 index 000000000..f40ec29fb --- /dev/null +++ b/node-scripts/generate-youtube-redirects.js @@ -0,0 +1,55 @@ +const fs = require('node:fs'); +const { paths, toSlug } = require('../content-testing/content'); + +// Generates a JSON file with mappings of YouTube video IDS to their challenge or track URL +// Priotity: challenge > canonicalTrack > track + +const redirects = {}; + +for (const path of paths.challenges) { + const slug = toSlug.videosAndChallenges(path); + const video = JSON.parse(fs.readFileSync(path)); + const parts = video.parts ?? [video]; + + parts.forEach((part, i) => { + const partAnchor = i > 0 ? `#part-${i + 1}` : ''; + redirects[part.videoId] = `/${slug}${partAnchor}`; + }); +} + +const slugToVideo = new Map(); +for (const path of paths.videos) { + const slug = toSlug.videosAndChallenges(path); + const video = JSON.parse(fs.readFileSync(path)); + slugToVideo.set(slug, video); +} + +for (const path of paths.tracks) { + const trackSlug = toSlug.tracks(path); + const track = JSON.parse(fs.readFileSync(path)); + + const chaptersOrVideos = + track.videos ?? track.chapters.flatMap((c) => c.videos); + + for (const slug of chaptersOrVideos) { + if (!slugToVideo.has(slug)) continue; + + const video = slugToVideo.get(slug); + const parts = video.parts ?? [video]; + const isCanonicalTrack = video.canonicalTrack === trackSlug; + + parts.forEach((part, i) => { + if (!redirects[part.videoId] || isCanonicalTrack) { + const partAnchor = i > 0 ? `#part-${i + 1}` : ''; + redirects[part.videoId] = `/tracks/${trackSlug}/${slug}${partAnchor}`; + } + }); + } +} + +fs.writeFileSync( + 'netlify/edge-functions/yt/redirects.json', + JSON.stringify(redirects) +); + +console.log(`${Object.keys(redirects).length} YouTube redirects we generated.`); diff --git a/package.json b/package.json index 54ec4f8fb..4461be349 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "dev": "gatsby develop -H 0.0.0.0", "build": "gatsby build --verbose", - "build-ci": "node node-scripts/generate-challenges-redirects && npm run tags-transforms && npm run build", + "build-ci": "node node-scripts/generate-challenges-redirects && node node-scripts/generate-youtube-redirects && npm run tags-transforms && npm run build", "serve": "gatsby serve", "clean": "gatsby clean", "test": "jest", From 56875e36e4f2c767c7c0dd180529404366bf89be Mon Sep 17 00:00:00 2001 From: Francis Turmel Date: Tue, 9 Jul 2024 08:23:22 -0400 Subject: [PATCH 2/3] fix log typo --- node-scripts/generate-youtube-redirects.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node-scripts/generate-youtube-redirects.js b/node-scripts/generate-youtube-redirects.js index f40ec29fb..a15a687ba 100644 --- a/node-scripts/generate-youtube-redirects.js +++ b/node-scripts/generate-youtube-redirects.js @@ -52,4 +52,6 @@ fs.writeFileSync( JSON.stringify(redirects) ); -console.log(`${Object.keys(redirects).length} YouTube redirects we generated.`); +console.log( + `${Object.keys(redirects).length} YouTube redirects were generated.` +); From 6ff260fcc5039a73281ec508c7079505cbfc6d4c Mon Sep 17 00:00:00 2001 From: Francis Turmel Date: Tue, 9 Jul 2024 11:45:58 -0400 Subject: [PATCH 3/3] improve comment and fix another typo --- node-scripts/generate-youtube-redirects.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-scripts/generate-youtube-redirects.js b/node-scripts/generate-youtube-redirects.js index a15a687ba..d74c7643a 100644 --- a/node-scripts/generate-youtube-redirects.js +++ b/node-scripts/generate-youtube-redirects.js @@ -1,8 +1,8 @@ const fs = require('node:fs'); const { paths, toSlug } = require('../content-testing/content'); -// Generates a JSON file with mappings of YouTube video IDS to their challenge or track URL -// Priotity: challenge > canonicalTrack > track +// Generates a JSON file that maps YouTube IDs to their challenge or track URL +// Priority: challenge > canonical track > track const redirects = {};