diff --git a/helpers/api/client.js b/helpers/api/client.js index 2e3b7bc..d5d06bb 100644 --- a/helpers/api/client.js +++ b/helpers/api/client.js @@ -25,7 +25,10 @@ const defaultFetchMethod = async function (...args) { return axios(...args) .then( response => response.data ) .catch( error => { - console.error( error ) + if ( error?.response?.status !== 404 ) { + console.error( error ) + } + throw error }) } diff --git a/helpers/build-video-list.js b/helpers/build-video-list.js index a3df213..634e057 100644 --- a/helpers/build-video-list.js +++ b/helpers/build-video-list.js @@ -33,6 +33,10 @@ const videoFeaturesApp = function (app, video) { return false } +export function makeVideoSlug ( title, videoId ) { + return makeSlug( `${ title }-i-${ videoId }` ) +} + const generateVideoTags = function ( video ) { const tags = { 'benchmark': { @@ -129,7 +133,7 @@ const makeThumbnailData = function ( thumbnails, widthLimit = null ) { } } -async function handleFetchedVideo ( fetchedVideo, videoId, applist ) { +export async function buildVideoListingFromFetchedVideo ( fetchedVideo, videoId, applist ) { // Skip private videos if (fetchedVideo.title === 'Private video') return @@ -138,7 +142,7 @@ async function handleFetchedVideo ( fetchedVideo, videoId, applist ) { if (fetchedVideo.title === 'Deleted video') return // Build video slug - const slug = makeSlug( `${fetchedVideo.title}-i-${videoId}` ) + const slug = makeVideoSlug( fetchedVideo.title, videoId ) const appLinks = [] // Generate new tag set based on api data @@ -200,8 +204,7 @@ export default async function ( applist ) { .withConcurrency(1000) .for( Object.entries( fetchedVideos ) ) .process(async ( [ videoId, fetchedVideo ], index, pool ) => { - const mappedVideo = await handleFetchedVideo ( fetchedVideo, videoId, applist ) - + const mappedVideo = await buildVideoListingFromFetchedVideo( fetchedVideo, videoId, applist ) // Skip if this video is not an object if ( Object( mappedVideo ) !== mappedVideo ) return diff --git a/helpers/site-listings.js b/helpers/site-listings.js new file mode 100644 index 0000000..6ba4c90 --- /dev/null +++ b/helpers/site-listings.js @@ -0,0 +1,47 @@ +import fs from 'fs-extra' +import path from 'path' +import { fileURLToPath } from 'url' + +import { + buildVideoListingFromFetchedVideo, + makeVideoSlug +} from '~/helpers/build-video-list.js' +import { youtubeVideoPath } from '~/helpers/api/youtube/build.js' + +const currentModuleDirectory = path.dirname( fileURLToPath( import.meta.url ) ) +const appListPath = path.join( currentModuleDirectory, '../static/app-list.json' ) +const gameListPath = path.join( currentModuleDirectory, '../static/game-list.json' ) +const deviceListPath = path.join( currentModuleDirectory, '../static/device-list.json' ) +const trailingCommaPattern = /,\s*([\]}])/g + +function parseGeneratedJsonFile ( filePath ) { + const fileContents = fs.readFileSync( filePath, 'utf8' ) + + return JSON.parse( fileContents.replace( trailingCommaPattern, '$1' ) ) +} + +export function getDeviceListingBySlug ( slug ) { + const deviceList = parseGeneratedJsonFile( deviceListPath ) + + return deviceList.find( device => device.slug === slug ) || null +} + +function getAllVideoAppsList () { + return [ + ...parseGeneratedJsonFile( appListPath ), + ...parseGeneratedJsonFile( gameListPath ) + ] +} + +export async function getVideoListingBySlug ( slug ) { + const fetchedVideos = await fs.readJson( youtubeVideoPath ) + const allVideoAppsList = getAllVideoAppsList() + + for ( const [ videoId, fetchedVideo ] of Object.entries( fetchedVideos ) ) { + if ( makeVideoSlug( fetchedVideo.title, videoId ) !== slug ) continue + + return await buildVideoListingFromFetchedVideo( fetchedVideo, videoId, allVideoAppsList ) + } + + return null +} diff --git a/scripts/health b/scripts/health new file mode 100755 index 0000000..3c5c81a --- /dev/null +++ b/scripts/health @@ -0,0 +1,104 @@ +#!/usr/bin/env bun + +const routeGroups = { + topLevel: [ + '/', + '/categories', + '/devices', + '/benchmarks', + '/games', + '/apple-silicon-app-test' + ], + dynamic: [ + '/app/kicad-eda', + '/app/spotify', + '/formula/bash', + '/kind/developer-tools', + '/device/m1-imac', + '/app/expressvpn/benchmarks' + ], + video: [ + '/tv/apple-silicon-gaming-is-here', + '/tv/install-instagram-app-on-m1-macbook-air-apple-silicon-tutorial-i-vfbmworal6i', + '/tv/xamarin-and-visual-studio-on-apple-macbook-pro-13-m1-in-4k-i-rwpspmmlos', + '/tv/watch-this-before-buying-apple-m1-macbook-for-xampp-or-apple-silicon-tests-in-4k-i-ebwwewsis8s' + ] +} + +function parseHosts(rawHosts) { + const source = rawHosts && rawHosts.trim().length > 0 + ? rawHosts + : 'doesitarm.com' + + return source + .split(',') + .map(host => host.trim()) + .filter(Boolean) + .map(host => host.startsWith('http://') || host.startsWith('https://') ? host : `https://${host}`) +} + +function getPaths() { + return Object.values(routeGroups).flat() +} + +function extractTitle(html) { + const match = html.match(/