feat: significantly reduce build times

This commit is contained in:
Cory Dransfeldt 2024-07-31 15:42:48 -07:00
parent 228bd1b681
commit 1c75acb37e
No known key found for this signature in database
21 changed files with 671 additions and 558 deletions

View file

@ -50,6 +50,7 @@
/now.html /now 301 /now.html /now 301
# feeds # feeds
/feed https://feedpress.me/coryd 301
/rss https://feedpress.me/coryd 301 /rss https://feedpress.me/coryd 301
/atom https://feedpress.me/coryd 301 /atom https://feedpress.me/coryd 301
/rss.xml https://feedpress.me/coryd 301 /rss.xml https://feedpress.me/coryd 301

View file

@ -155,12 +155,12 @@ export default {
url: item['url'], url: item['url'],
type: item.type type: item.type
} }
if (item.type === 'artists') { if (item.type === 'artist') {
normalized['title'] = item['title'] normalized['title'] = item['title']
normalized['alt'] = `${item['plays']} plays of ${item['title']}` normalized['alt'] = `${item['plays']} plays of ${item['title']}`
normalized['subtext'] = `${item['plays']} plays` normalized['subtext'] = `${item['plays']} plays`
} }
if (item.type === 'albums') { if (item.type === 'album') {
normalized['title'] = item['title'] normalized['title'] = item['title']
normalized['alt'] = `${item['title']} by ${item['artist']}` normalized['alt'] = `${item['title']} by ${item['artist']}`
normalized['subtext'] = `${item['artist']}` normalized['subtext'] = `${item['artist']}`

39
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "20.13.4", "version": "21.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "coryd.dev", "name": "coryd.dev",
"version": "20.13.4", "version": "21.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@cdransf/api-text": "^1.4.0", "@cdransf/api-text": "^1.4.0",
@ -16,7 +16,7 @@
"youtube-video-element": "^1.1.6" "youtube-video-element": "^1.1.6"
}, },
"devDependencies": { "devDependencies": {
"@11ty/eleventy": "3.0.0-alpha.18", "@11ty/eleventy": "v3.0.0-beta.1",
"@11ty/eleventy-fetch": "^4.0.1", "@11ty/eleventy-fetch": "^4.0.1",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"@11tyrocks/eleventy-plugin-lightningcss": "^1.4.0", "@11tyrocks/eleventy-plugin-lightningcss": "^1.4.0",
@ -68,9 +68,9 @@
} }
}, },
"node_modules/@11ty/eleventy": { "node_modules/@11ty/eleventy": {
"version": "3.0.0-alpha.18", "version": "3.0.0-beta.1",
"resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.0.0-alpha.18.tgz", "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.0.0-beta.1.tgz",
"integrity": "sha512-CQu4HOtYJySEexVRT/tFtLtqtI4+winun0NFmFIUp0SvxRpP46+ZxBvBc9ezSFLo1nN0zJkwoG8GTkhMPOThtg==", "integrity": "sha512-iJT7vekH11l8PAUPBfUAcb5oWbYK0w4ijgwDTutUsk6tX9rp4ZRL1jdhVWvZq04/rkc55mczNFPPhHB/XO1/qw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -582,9 +582,9 @@
"peer": true "peer": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.0.0", "version": "22.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.2.tgz",
"integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", "integrity": "sha512-yPL6DyFwY5PiMVEwymNeqUTKsDczQBJ/5T7W/46RwLU/VH+AA8aT5TZkvBviLKLbbm0hlfftEkGrNzfRk/fofQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -896,9 +896,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001644", "version": "1.0.30001645",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001644.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001645.tgz",
"integrity": "sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw==", "integrity": "sha512-GFtY2+qt91kzyMk6j48dJcwJVq5uTkk71XxE3RtScx7XWRLsO7bU44LOFkOZYR8w9YMS0UhPSYpN/6rAMImmLw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1254,9 +1254,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.3", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.3.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz",
"integrity": "sha512-QNdYSS5i8D9axWp/6XIezRObRHqaav/ur9z1VzCDUCH1XIFOr9WQk5xmgunhsTpjjgDy3oLxO/WMOVZlpUQrlA==", "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -3111,9 +3111,9 @@
} }
}, },
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "5.0.9", "version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
"integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@ -3122,9 +3122,6 @@
"bin": { "bin": {
"rimraf": "dist/esm/bin.mjs" "rimraf": "dist/esm/bin.mjs"
}, },
"engines": {
"node": "14 >=14.20 || 16 >=16.20 || >=18"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "20.13.4", "version": "21.0.0",
"description": "The source for my personal site. Built using 11ty (and other tools).", "description": "The source for my personal site. Built using 11ty (and other tools).",
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -31,7 +31,7 @@
"youtube-video-element": "^1.1.6" "youtube-video-element": "^1.1.6"
}, },
"devDependencies": { "devDependencies": {
"@11ty/eleventy": "3.0.0-alpha.18", "@11ty/eleventy": "v3.0.0-beta.1",
"@11ty/eleventy-fetch": "^4.0.1", "@11ty/eleventy-fetch": "^4.0.1",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"@11tyrocks/eleventy-plugin-lightningcss": "^1.4.0", "@11tyrocks/eleventy-plugin-lightningcss": "^1.4.0",

View file

@ -9,37 +9,46 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const fetchAlbumReleases = async () => { const fetchAlbumReleases = async () => {
const today = DateTime.utc().toISO() const today = DateTime.utc().toISO()
const { data, error } = await supabase const { data, error } = await supabase
.from('albums') .from('optimized_album_releases')
.select(` .select(`
name, name,
key, key,
release_date, release_date,
release_link, release_link,
total_plays, total_plays,
art(filename_disk), art,
artists(name_string, mbid, country) artist_name,
artist_mbid,
artist_country
`) `)
.gt('release_date', today) .gt('release_date', today)
if (error) { if (error) {
console.error('Error fetching data:', error) console.error('Error fetching data:', error)
return return []
} }
return data.filter(album => !album['total_plays'] || !album['total_plays'] > 0).map(album => ({ return data
artist: album['artists']['name_string'], .filter(album => !album['total_plays'] || album['total_plays'] <= 0)
.map(album => ({
artist: album['artist_name'],
title: album['name'], title: album['name'],
date: DateTime.fromISO(album['release_date']).toLocaleString(DateTime.DATE_FULL), date: DateTime.fromISO(album['release_date']).toLocaleString(DateTime.DATE_FULL),
url: album['release_link'], url: album['release_link'],
image: `/${album?.['art']?.['filename_disk']}` || '', image: album['art'] ? `/${album['art']}` : '',
artist_url: `/music/artists/${sanitizeMediaString(album['artists']['name_string'])}-${sanitizeMediaString(parseCountryField(album['artists']['country']))}`, artist_url: `/music/artists/${sanitizeMediaString(album['artist_name'])}-${sanitizeMediaString(parseCountryField(album['artist_country']))}`,
mbid: album['artists']['mbid'], mbid: album['artist_mbid'],
timestamp: DateTime.fromISO(album['release_date']).toSeconds(), timestamp: DateTime.fromISO(album['release_date']).toSeconds(),
type: 'album-release' type: 'album-release'
} }))
)).sort((a, b) => a['timestamp'] - b['timestamp']) .sort((a, b) => a['timestamp'] - b['timestamp'])
} }
export default async function () { export default async function () {
try {
return await fetchAlbumReleases() return await fetchAlbumReleases()
} catch (error) {
console.error('Error fetching and processing album releases:', error)
return []
}
} }

View file

@ -1,85 +1,81 @@
import { createClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js'
import { sanitizeMediaString, parseCountryField } from '../../config/utilities/index.js' import { sanitizeMediaString, parseCountryField } from '../../config/utilities/index.js'
import { DateTime } from 'luxon'
const SUPABASE_URL = process.env.SUPABASE_URL const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env.SUPABASE_KEY const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 500
const PAGE_SIZE = 50 const fetchAllArtists = async () => {
let artists = []
let rangeStart = 0
const fetchPaginatedData = async (table, selectFields) => { while (true) {
let data = [] const { data, error } = await supabase
let page = 0 .from('optimized_artists')
let hasMoreRecords = true .select(`
id,
while (hasMoreRecords) { mbid,
const { data: pageData, error } = await supabase name_string,
.from(table) tentative,
.select(selectFields) total_plays,
.order('id', { ascending: true }) country,
.range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1) description,
favorite,
genre,
emoji,
tattoo,
art,
albums,
concerts
`)
.range(rangeStart, rangeStart + PAGE_SIZE - 1)
if (error) { if (error) {
console.error(`Error fetching ${table}:`, error) console.error('Error fetching artists:', error)
break break
} }
data = data.concat(pageData) artists = artists.concat(data)
if (data.length < PAGE_SIZE) break
if (pageData.length < PAGE_SIZE) { rangeStart += PAGE_SIZE
hasMoreRecords = false
} else {
page++
}
}
return data
}
const fetchGenreMapping = async () => {
const { data, error } = await supabase
.from('genres')
.select('id, name')
if (error) {
console.error('Error fetching genres:', error)
return {}
}
return data.reduce((acc, genre) => {
acc[genre['id']] = genre['name']
return acc
}, {})
}
export default async function () {
const genreMapping = await fetchGenreMapping()
const artists = await fetchPaginatedData('artists', 'id, mbid, name_string, art(filename_disk), total_plays, country, description, favorite, tattoo, genres')
const allAlbums = await fetchPaginatedData('albums', 'id, mbid, name, release_year, total_plays, artist, release_date')
const albums = allAlbums.filter(album =>
!album['release_date'] ||
DateTime.fromISO(album['release_date']) <= DateTime.now() ||
(DateTime.fromISO(album['release_date']) > DateTime.now() && album['total_plays'] > 0)
)
const albumsByArtist = albums.reduce((acc, album) => {
if (!acc[album['artist']]) acc[album['artist']] = []
acc[album['artist']].push({
id: album['id'],
name: album['name'],
release_year: album['release_year'],
total_plays: album['total_plays'] > 0 ? album['total_plays'] : '-'
})
return acc
}, {})
for (const artist of artists) {
artist['albums'] = albumsByArtist[artist['id']]?.sort((a, b) => a['release_year'] - b['release_year']) || []
artist['image'] = `/${artist['art']['filename_disk']}`
artist['country'] = parseCountryField(artist['country'])
artist['genres'] = genreMapping[artist['genres']] || ''
artist['url'] = `/music/artists/${sanitizeMediaString(artist['name_string'])}-${sanitizeMediaString(artist['country'])}`
} }
return artists return artists
} }
const processArtists = (artists) => {
return artists.map(artist => ({
id: artist['id'],
mbid: artist['mbid'],
name: artist['name_string'],
tentative: artist['tentative'],
totalPlays: artist['total_plays'],
country: parseCountryField(artist['country']),
description: artist['description'],
favorite: artist['favorite'],
genre: artist['genre'],
emoji: artist['emoji'],
tattoo: artist['tattoo'],
image: artist['art'] ? `/${artist['art']}` : '',
url: `/music/artists/${sanitizeMediaString(artist['name_string'])}-${sanitizeMediaString(parseCountryField(artist['country']))}`,
albums: (artist['albums'] || []).map(album => ({
id: album['id'],
name: album['name'],
releaseYear: album['release_year'],
totalPlays: album['total_plays'],
art: album.art ? `/${album['art']}` : ''
})).sort((a, b) => a['release_year'] - b['release_year']),
concerts: artist['concerts'] || []
}))
}
export default async function () {
try {
const artists = await fetchAllArtists()
return processArtists(artists)
} catch (error) {
console.error('Error fetching and processing artists data:', error)
return []
}
}

View file

@ -14,17 +14,22 @@ const fetchAllBadges = async () => {
if (error) { if (error) {
console.error('Error fetching badge data:', error) console.error('Error fetching badge data:', error)
return null return []
} }
const transformedData = data.map(badge => ({ const transformedData = data.map(badge => ({
...badge, ...badge,
image: badge['image']['filename_disk'], image: badge.image?.filename_disk || '',
})).sort((a, b) => a.sort - b.sort) })).sort((a, b) => a.sort - b.sort)
return transformedData return transformedData
} }
export default async function () { export default async function () {
try {
return await fetchAllBadges() return await fetchAllBadges()
} catch (error) {
console.error('Error fetching and processing badge data:', error)
return []
}
} }

View file

@ -12,13 +12,18 @@ const fetchBlogroll = async () => {
.order('name', { ascending: true }) .order('name', { ascending: true })
if (error) { if (error) {
console.error('Error fetching authors with for the blogroll:', error) console.error('Error fetching authors for the blogroll:', error)
return [] return []
} }
return data.sort((a, b) => a['name'].toLowerCase().localeCompare(b['name'].toLowerCase())) return data.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))
} }
export default async function () { export default async function () {
try {
return await fetchBlogroll() return await fetchBlogroll()
} catch (error) {
console.error('Error fetching and processing the blogroll:', error)
return []
}
} }

View file

@ -1,50 +1,49 @@
import { createClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js'
const { SUPABASE_URL, SUPABASE_KEY } = process.env const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 500
const PAGE_SIZE = 1000 const fetchAllBooks = async () => {
const fetchTagsForBook = async (bookId) => {
const { data, error } = await supabase
.from('books_tags')
.select('tags(id, name)')
.eq('books_id', bookId)
if (error) {
console.error(`Error fetching tags for book ${bookId}:`, error)
return []
}
return data.map(bt => bt['tags']['name'])
}
async function fetchAllBooks() {
let books = [] let books = []
let from = 0 let rangeStart = 0
while (true) { while (true) {
const { data, error } = await supabase const { data, error } = await supabase
.from('books') .from('optimized_books')
.select(`*, art(filename_disk)`) .select(`
.range(from, from + PAGE_SIZE - 1) id,
isbn,
date_finished,
author,
description,
title,
progress,
read_status,
star_rating,
review,
art,
favorite,
tags
`)
.order('date_finished', { ascending: false })
.range(rangeStart, rangeStart + PAGE_SIZE - 1)
if (error) { if (error) {
console.error('Error fetching data from Supabase:', error) console.error('Error fetching data from Supabase:', error)
break break
} }
for (const book of data) {
book['tags'] = await fetchTagsForBook(book['id'])
}
books = books.concat(data) books = books.concat(data)
if (data.length < PAGE_SIZE) break if (data.length < PAGE_SIZE) break
rangeStart += PAGE_SIZE
from += PAGE_SIZE
} }
return books
}
const processBooks = (books) => {
return books.map(book => { return books.map(book => {
const dateFinished = new Date(book['date_finished']) const dateFinished = new Date(book['date_finished'])
const year = dateFinished.getUTCFullYear() const year = dateFinished.getUTCFullYear()
@ -55,12 +54,12 @@ async function fetchAllBooks() {
rating: book['star_rating'] !== 'unrated' ? book['star_rating'] : '', rating: book['star_rating'] !== 'unrated' ? book['star_rating'] : '',
favorite: book['favorite'], favorite: book['favorite'],
description: book['description'], description: book['description'],
image: `/${book?.['art']?.['filename_disk']}`, image: `/${book['art']}`,
url: `/books/${book['isbn']}`, url: `/books/${book['isbn']}`,
date: book['date_finished'], date: book['date_finished'],
status: book['read_status'], status: book['read_status'],
progress: book['progress'], progress: book['progress'],
tags: book['tags'], tags: book['tags'] ? book['tags'].split(',') : [],
isbn: book['isbn'], isbn: book['isbn'],
type: 'book', type: 'book',
year, year,
@ -83,5 +82,6 @@ const sortBooksByYear = (books) => {
export default async function () { export default async function () {
const books = await fetchAllBooks() const books = await fetchAllBooks()
return { all: books, years: sortBooksByYear(books) } const processedBooks = processBooks(books)
return { all: processedBooks, years: sortBooksByYear(processedBooks) }
} }

80
src/data/concerts.js Normal file
View file

@ -0,0 +1,80 @@
import { createClient } from '@supabase/supabase-js'
import { sanitizeMediaString, parseCountryField } from '../../config/utilities/index.js'
const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 500
const fetchAllConcerts = async () => {
let concerts = []
let rangeStart = 0
while (true) {
const { data, error } = await supabase
.from('optimized_concerts')
.select(`
id,
date,
artist_name_string,
venue,
concert_notes,
artist,
venue_name,
latitude,
longitude,
bounding_box,
venue_notes,
artist_name,
artist_mbid,
artist_country
`)
.range(rangeStart, rangeStart + PAGE_SIZE - 1)
if (error) {
console.error('Error fetching concerts:', error)
break
}
concerts = concerts.concat(data)
if (data.length < PAGE_SIZE) break
rangeStart += PAGE_SIZE
}
return concerts
}
const processConcerts = (concerts) => {
return concerts.map(concert => ({
id: concert['id'],
date: concert['date'],
artist_name_string: concert['artist_name_string'],
venue: {
id: concert['venue'],
name: concert['venue_name'],
latitude: concert['latitude'],
longitude: concert['longitude'],
bounding_box: concert['bounding_box'],
notes: concert['venue_notes']
},
notes: concert['concert_notes'],
artist: concert['artist'] ? {
id: concert['artist'],
name: concert['artist_name'],
mbid: concert['artist_mbid'],
country: parseCountryField(concert['artist_country'])
} : null,
url: `/concerts/${concert['id']}`,
artist_url: concert['artist'] ? `/music/artists/${sanitizeMediaString(concert['artist_name'])}-${sanitizeMediaString(parseCountryField(concert['artist_country']))}` : null
}))
}
export default async function () {
try {
const concerts = await fetchAllConcerts()
return processConcerts(concerts)
} catch (error) {
console.error('Error fetching and processing concerts data:', error)
return []
}
}

View file

@ -30,17 +30,21 @@ const fetchGenresWithArtists = async () => {
return [] return []
} }
data.forEach(genre => { return data.map(genre => ({
genre['artists'] = genre['artists'].map(artist => ({ ...genre,
artists: genre['artists'].map(artist => ({
...artist, ...artist,
country: parseCountryField(artist['country']) country: parseCountryField(artist['country'])
})),
url: `/music/genres/${slugify(genre['name'].replace('/', '-').toLowerCase())}`
})) }))
genre['url'] = `/music/genres/${slugify(genre['name'].replace('/', '-').toLowerCase())}`
})
return data
} }
export default async function () { export default async function () {
try {
return await fetchGenresWithArtists() return await fetchGenresWithArtists()
} catch (error) {
console.error('Error fetching and processing genres:', error)
return []
}
} }

View file

@ -6,43 +6,23 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const fetchGlobals = async () => { const fetchGlobals = async () => {
const { data, error } = await supabase const { data, error } = await supabase
.from('globals') .from('optimized_globals')
.select(` .select('*')
*, .single()
favicon_ico(filename_disk),
favicon_svg(filename_disk),
opengraph_default(filename_disk),
feed_image(filename_disk),
apple_touch_icon(filename_disk),
about(filename_disk),
logo_the_claw(filename_disk)
`)
if (error) { if (error) {
console.error('Error fetching globals:', error) console.error('Error fetching globals:', error)
return {} return {}
} }
const globalData = data.pop() return data
const keysToProcess = [
'favicon_ico',
'favicon_svg',
'opengraph_default',
'feed_image',
'apple_touch_icon',
'about',
'logo_the_claw'
]
keysToProcess.forEach(key => {
if (globalData[key] && globalData[key].filename_disk) {
globalData[key] = globalData[key].filename_disk
}
})
return globalData
} }
export default async function () { export default async function () {
try {
return await fetchGlobals() return await fetchGlobals()
} catch (error) {
console.error('Error fetching and processing globals:', error)
return {}
}
} }

View file

@ -6,18 +6,22 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 50 const PAGE_SIZE = 50
const fetchTagsForLink = async (linkId) => { const fetchAllTags = async () => {
const { data, error } = await supabase const { data, error } = await supabase
.from('links_tags') .from('links_tags')
.select('tags(id, name)') .select('links_id, tags(name)')
.eq('links_id', linkId)
if (error) { if (error) {
console.error(`Error fetching tags for link ${linkId}:`, error) console.error('Error fetching all tags from Supabase:', error)
return [] return {}
} }
return data.map((lt) => lt.tags.name) return data.reduce((acc, { links_id, tags }) => {
if (!tags || !tags.name) return acc
if (!acc[links_id]) acc[links_id] = []
acc[links_id].push(tags['name'])
return acc
}, {})
} }
const fetchAllLinks = async () => { const fetchAllLinks = async () => {
@ -39,11 +43,6 @@ const fetchAllLinks = async () => {
if (data.length < PAGE_SIZE) fetchMore = false if (data.length < PAGE_SIZE) fetchMore = false
for (const link of data) {
link['tags'] = await fetchTagsForLink(link.id)
link['type'] = 'link'
}
links = links.concat(data) links = links.concat(data)
page++ page++
} }
@ -51,6 +50,15 @@ const fetchAllLinks = async () => {
return links return links
} }
export default async function () { const processLinks = (links, tagsByLinkId) => {
return await fetchAllLinks() return links.map(link => {
link['tags'] = tagsByLinkId[link['id']] || []
link['type'] = 'link'
return link
})
}
export default async function () {
const [links, tagsByLinkId] = await Promise.all([fetchAllLinks(), fetchAllTags()])
return processLinks(links, tagsByLinkId)
} }

View file

@ -6,27 +6,13 @@ const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 1000 const PAGE_SIZE = 1000
const fetchTagsForMovie = async (movieId) => {
const { data, error } = await supabase
.from('movies_tags')
.select('tags(id, name)')
.eq('movies_id', movieId)
if (error) {
console.error(`Error fetching tags for movie ${movieId}:`, error)
return []
}
return data.map(mt => mt.tags.name)
}
const fetchAllMovies = async () => { const fetchAllMovies = async () => {
let movies = [] let movies = []
let rangeStart = 0 let rangeStart = 0
while (true) { while (true) {
const { data, error } = await supabase const { data, error } = await supabase
.from('movies') .from('optimized_movies')
.select(` .select(`
id, id,
tmdb_id, tmdb_id,
@ -39,8 +25,9 @@ const fetchAllMovies = async () => {
star_rating, star_rating,
description, description,
review, review,
art(filename_disk), art,
backdrop(filename_disk) backdrop,
tags
`) `)
.order('last_watched', { ascending: false }) .order('last_watched', { ascending: false })
.range(rangeStart, rangeStart + PAGE_SIZE - 1) .range(rangeStart, rangeStart + PAGE_SIZE - 1)
@ -50,10 +37,6 @@ const fetchAllMovies = async () => {
break break
} }
for (const movie of data) {
movie.tags = await fetchTagsForMovie(movie.id)
}
movies = movies.concat(data) movies = movies.concat(data)
if (data.length < PAGE_SIZE) break if (data.length < PAGE_SIZE) break
@ -63,41 +46,61 @@ const fetchAllMovies = async () => {
return movies return movies
} }
export default async function () { const processMovies = (movies) => {
return movies.map(item => {
const lastWatched = DateTime.fromISO(item['last_watched'], { zone: 'utc' })
const year = DateTime.now().year const year = DateTime.now().year
const movies = await fetchAllMovies()
const formatMovieData = (movies, watched = true) => movies.map((item) => { return {
const movie = {
title: item['title'], title: item['title'],
lastWatched: item['last_watched'], lastWatched: item['last_watched'],
dateAdded: item['last_watched'], dateAdded: item['last_watched'],
year: item['year'], year: item['year'],
url: `/watching/movies/${item['tmdb_id']}`, url: `/watching/movies/${item['tmdb_id']}`,
description: `${item['title']} (${item['year']})<br/>Watched at: ${DateTime.fromISO(item['last_watched'], { zone: 'utc' }).setZone('America/Los_Angeles').toFormat('MMMM d, yyyy, h:mma')}`, description: item['description'],
image: `/${item?.['art']?.['filename_disk']}`, image: item['art'] ? `/${item['art']}` : '',
backdrop: `/${item?.['backdrop']?.['filename_disk']}`, backdrop: item['backdrop'] ? `/${item['backdrop']}` : '',
plays: item['plays'], plays: item['plays'],
collected: item['collected'], collected: item['collected'],
favorite: item['favorite'], favorite: item['favorite'],
rating: item['star_rating'], rating: item['star_rating'],
description: item['description'],
review: item['review'], review: item['review'],
id: item['tmdb_id'], id: item['tmdb_id'],
type: 'movie', type: 'movie',
tags: item['tags'] tags: item['tags'] ? item['tags'].split(',') : [],
}
})
} }
return movie export default async function () {
}).filter(movie => watched ? movie['lastWatched'] : !movie['lastWatched']) const year = DateTime.now().year
const favoriteMovies = movies.filter(movie => movie['favorite'])
const collectedMovies = movies.filter(movie => movie['collected']) try {
const recentlyWatchedMovies = movies.filter(movie => movie['last_watched'] && year - DateTime.fromISO(movie['last_watched']).year <= 3).sort((a, b) => new Date(b['last_watched']) - new Date(a['last_watched'])) const movies = await fetchAllMovies()
const processedMovies = processMovies(movies)
const filterMovies = (condition) => processedMovies.filter(condition)
const formatMovieData = (movies) => movies.map(movie => movie)
const favoriteMovies = filterMovies(movie => movie['favorite'])
const collectedMovies = filterMovies(movie => movie['collected'])
const recentlyWatchedMovies = filterMovies(movie => movie['lastWatched'] && year - DateTime.fromISO(movie['lastWatched']).year <= 3).sort((a, b) => new Date(b['lastWatched']) - new Date(a['lastWatched']))
return { return {
movies: [...formatMovieData(movies), ...formatMovieData(movies, false)], movies: formatMovieData(processedMovies),
watchHistory: formatMovieData(movies), watchHistory: formatMovieData(filterMovies(movie => movie['lastWatched'])),
recentlyWatched: formatMovieData(recentlyWatchedMovies), recentlyWatched: formatMovieData(recentlyWatchedMovies),
favorites: formatMovieData(favoriteMovies).sort((a, b) => a['title'].localeCompare(b['title'])), favorites: formatMovieData(favoriteMovies).sort((a, b) => a['title'].localeCompare(b['title'])),
collection: formatMovieData(collectedMovies), collection: formatMovieData(collectedMovies),
} }
} catch (error) {
console.error('Error fetching and processing movies data:', error)
return {
movies: [],
watchHistory: [],
recentlyWatched: [],
favorites: [],
collection: [],
}
}
} }

View file

@ -6,8 +6,9 @@ const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env.SUPABASE_KEY const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const fetchDataForPeriod = async (startPeriod, fields, table) => {
const PAGE_SIZE = 1000 const PAGE_SIZE = 1000
const fetchDataForPeriod = async (startPeriod, fields, table) => {
let rows = [] let rows = []
let rangeStart = 0 let rangeStart = 0
@ -20,7 +21,7 @@ const fetchDataForPeriod = async (startPeriod, fields, table) => {
.range(rangeStart, rangeStart + PAGE_SIZE - 1) .range(rangeStart, rangeStart + PAGE_SIZE - 1)
if (error) { if (error) {
console.error(error) console.error(`Error fetching data from ${table}:`, error)
break break
} }
@ -43,70 +44,49 @@ const fetchGenreMapping = async () => {
return {} return {}
} }
return data.reduce((acc, genre) => { return data.reduce((acc, genre) => {
acc[genre.id] = genre.name acc[genre['id']] = genre['name']
return acc return acc
}, {}) }, {})
} }
const aggregateData = async (data, groupByField, groupByType) => { const aggregateData = (data, groupByField, groupByType, genreMapping) => {
const aggregation = {} const aggregation = {}
const genreMapping = await fetchGenreMapping()
data.forEach(item => { data.forEach(item => {
const key = item[groupByField] const key = item[groupByField]
if (!aggregation[key]) { if (!aggregation[key]) {
if (groupByType === 'track') {
aggregation[key] = {
title: item[groupByField],
plays: 0,
mbid: item['albums']['mbid'],
url: `/music/artists/${sanitizeMediaString(item['artist_name'])}-${sanitizeMediaString(parseCountryField(item['artists']['country']))}`,
image: `/${item['albums']?.['art']?.['filename_disk']}` || '',
timestamp: item['listened_at'],
type: groupByType,
genre: genreMapping[item['artists']['genres']] || ''
}
} else {
aggregation[key] = { aggregation[key] = {
title: item[groupByField], title: item[groupByField],
plays: 0, plays: 0,
mbid: item[groupByType]?.['mbid'] || '', mbid: item[groupByType]?.['mbid'] || '',
url: `/music/artists/${sanitizeMediaString(item['artist_name'])}-${sanitizeMediaString(parseCountryField(item['artists']['country']))}`, url: `/music/artists/${sanitizeMediaString(item['artist_name'])}-${sanitizeMediaString(parseCountryField(item['artist_country']))}`,
image: `/${item[groupByType]?.['art']?.['filename_disk']}` || '', image: `/${item[groupByType]}`,
type: groupByType, type: groupByType === 'artist_art' ? 'artist' : groupByType === 'album_art' ? 'album' : groupByType,
genre: genreMapping[item['artists']['genres']] || '' genre: genreMapping[item['artist_genres']] || ''
} }
} if (groupByType === 'track' || groupByType === 'album_art') aggregation[key]['artist'] = item['artist_name']
if (groupByType === 'track' || groupByType === 'albums') aggregation[key]['artist'] = item['artist_name']
} }
aggregation[key].plays++ aggregation[key].plays++
}) })
const aggregatedData = Object.values(aggregation).sort((a, b) => b.plays - a.plays) return Object.values(aggregation).sort((a, b) => b.plays - a.plays).map((item, index) => ({ ...item, rank: index + 1 }))
aggregatedData.forEach((item, index) => {
item.rank = index + 1
})
return aggregatedData.filter(item => item.plays > 0)
} }
const buildRecents = async (data) => { const buildRecents = (data) => {
return data.map(listen => ({ return data.map(listen => ({
title: listen['track_name'], title: listen['track_name'],
artist: listen['artist_name'], artist: listen['artist_name'],
url: `/music/artists/${sanitizeMediaString(listen['artist_name'])}-${sanitizeMediaString(parseCountryField(listen['artists']['country']))}`, url: `/music/artists/${sanitizeMediaString(listen['artist_name'])}-${sanitizeMediaString(parseCountryField(listen['artist_country']))}`,
timestamp: listen['listened_at'], timestamp: listen['listened_at'],
image: `/${listen['albums']?.['art']?.['filename_disk']}` || '' image: `/${listen['album_art']}`
})) })).sort((a, b) => b.timestamp - a.timestamp)
} }
const aggregateGenres = async (data) => { const aggregateGenres = (data, genreMapping) => {
const genreAggregation = {} const genreAggregation = {}
const genreMapping = await fetchGenreMapping()
data.forEach(item => { data.forEach(item => {
const genre = genreMapping[item['artists']['genres']] || '' const genre = genreMapping[item['artist_genres']] || ''
if (!genreAggregation[genre]) genreAggregation[genre] = { genre, plays: 0 } if (!genreAggregation[genre]) genreAggregation[genre] = { genre, plays: 0 }
genreAggregation[genre]['plays']++ genreAggregation[genre]['plays']++
@ -116,36 +96,49 @@ const aggregateGenres = async (data) => {
export default async function () { export default async function () {
const periods = { const periods = {
week: DateTime.now().minus({ days: 7 }).startOf('day'), // last week week: DateTime.now().minus({ days: 7 }).startOf('day'),
month: DateTime.now().minus({ days: 30 }).startOf('day'), // last 30 days month: DateTime.now().minus({ days: 30 }).startOf('day'),
threeMonth: DateTime.now().minus({ months: 3 }).startOf('day'), // last three months threeMonth: DateTime.now().minus({ months: 3 }).startOf('day')
} }
const results = {}
const selectFields = ` const selectFields = `
id,
listened_at,
track_name, track_name,
artist_name, artist_name,
album_name, album_name,
album_key, album_key,
listened_at, artist_mbid,
artists (mbid, art(filename_disk), genres, country), artist_art,
albums (mbid, art(filename_disk)) artist_genres,
artist_country,
album_mbid,
album_art
` `
for (const [period, startPeriod] of Object.entries(periods)) { try {
const periodData = await fetchDataForPeriod(startPeriod, selectFields, 'listens') const genreMapping = await fetchGenreMapping()
results[period] = {
artists: await aggregateData(periodData, 'artist_name', 'artists'), const results = await Promise.all(Object.entries(periods).map(async ([period, startPeriod]) => {
albums: await aggregateData(periodData, 'album_name', 'albums'), const periodData = await fetchDataForPeriod(startPeriod, selectFields, 'optimized_listens')
tracks: await aggregateData(periodData, 'track_name', 'track'), return {
genres: await aggregateGenres(periodData), [period]: {
totalTracks: periodData?.length?.toLocaleString('en-US') artists: aggregateData(periodData, 'artist_name', 'artist_art', genreMapping),
albums: aggregateData(periodData, 'album_name', 'album_art', genreMapping),
tracks: aggregateData(periodData, 'track_name', 'track', genreMapping),
genres: aggregateGenres(periodData, genreMapping),
totalTracks: periodData.length.toLocaleString('en-US')
} }
} }
}))
const recentData = await fetchDataForPeriod(DateTime.now().minus({ days: 7 }), selectFields, 'listens') const recentData = await fetchDataForPeriod(DateTime.now().minus({ days: 7 }), selectFields, 'optimized_listens')
results['recent'] = (await buildRecents(recentData)).sort((a, b) => b.timestamp - a.timestamp) results.push({ recent: buildRecents(recentData) })
return results return Object.assign({}, ...results)
} catch (error) {
console.error('Error in fetching and processing music data:', error)
return {}
}
} }

View file

@ -6,45 +6,43 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const fetchAllNavigation = async () => { const fetchAllNavigation = async () => {
const { data, error } = await supabase const { data, error } = await supabase
.from('navigation') .from('optimized_navigation')
.select(` .select('*')
*,
pages(title, permalink)
`)
if (error) { if (error) {
console.error('Error fetching navigation data:', error) console.error('Error fetching navigation data:', error)
return null return {}
} }
const menu = {} const menu = data.reduce((acc, item) => {
data.forEach(item => { const menuItem = {
const menuItem = item.pages ? { title: item['title'] || item['page_title'],
title: item.pages.title, permalink: item['permalink'] || item ['page_permalink'],
permalink: item.pages.permalink, icon: item['icon'],
icon: item.icon, sort: item['sort']
sort: item.sort
} : {
title: item.title,
permalink: item.permalink,
icon: item.icon,
sort: item.sort
} }
if (!menu[item.menu_location]) { if (!acc[item['menu_location']]) {
menu[item.menu_location] = [menuItem] acc[item['menu_location']] = [menuItem]
} else { } else {
menu[item.menu_location].push(menuItem) acc[item['menu_location']].push(menuItem)
} }
})
return acc
}, {})
Object.keys(menu).forEach(location => { Object.keys(menu).forEach(location => {
menu[location].sort((a, b) => a.sort - b.sort) menu[location].sort((a, b) => a['sort'] - b['sort'])
}) })
return menu return menu
} }
export default async function () { export default async function () {
try {
return await fetchAllNavigation() return await fetchAllNavigation()
} catch (error) {
console.error('Error fetching and processing navigation data:', error)
return {}
}
} }

View file

@ -1,7 +1,7 @@
import { createClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = process.env.SUPABASE_URL const SUPABASE_URL = process.env['SUPABASE_URL']
const SUPABASE_KEY = process.env.SUPABASE_KEY const SUPABASE_KEY = process.env['SUPABASE_KEY']
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 50 const PAGE_SIZE = 50
@ -21,28 +21,23 @@ const fetchBlockData = async (collection, itemId) => {
return data return data
} }
const fetchBlocksForPage = async (pageId) => { const fetchAllBlocks = async () => {
const { data, error } = await supabase const { data, error } = await supabase
.from('pages_blocks') .from('pages_blocks')
.select('collection, item, sort') .select('pages_id, collection, item, sort')
.eq('pages_id', pageId)
if (error) { if (error) {
console.error(`Error fetching blocks for page ${pageId}:`, error) console.error('Error fetching all blocks from Supabase:', error)
return [] return {}
} }
const blocks = await Promise.all(data.map(async block => { return data.reduce((acc, block) => {
const blockData = await fetchBlockData(block.collection, block.item) if (!acc[block['pages_id']]) {
acc[block['pages_id']] = []
return {
type: block['collection'],
sort: block['sort'],
...blockData
} }
})) acc[block['pages_id']].push(block)
return acc
return blocks.sort((a, b) => a.sort - b.sort) }, {})
} }
const fetchAllPages = async () => { const fetchAllPages = async () => {
@ -52,11 +47,8 @@ const fetchAllPages = async () => {
while (fetchMore) { while (fetchMore) {
const { data, error } = await supabase const { data, error } = await supabase
.from('pages') .from('optimized_pages')
.select(` .select('*')
*,
open_graph_image(filename_disk)
`)
.range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1) .range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1)
if (error) { if (error) {
@ -66,18 +58,50 @@ const fetchAllPages = async () => {
if (data.length < PAGE_SIZE) fetchMore = false if (data.length < PAGE_SIZE) fetchMore = false
for (const page of data) { pages = pages.concat(data)
page['blocks'] = await fetchBlocksForPage(page['id'])
if (page['open_graph_image']) page['open_graph_image'] = page['open_graph_image']['filename_disk']
pages.push(page)
}
page++ page++
} }
return pages return pages
} }
export default async function () { const processPages = async (pages, blocksByPageId) => {
return await fetchAllPages() return Promise.all(pages.map(async page => {
const blocks = blocksByPageId[page['id']] || []
page['blocks'] = await Promise.all(blocks.map(async block => {
const blockData = await fetchBlockData(block['collection'], block['item'])
if (!blockData) return {
'type': block['collection'],
'sort': block['sort']
}
return {
'type': block['collection'],
'sort': block['sort'],
...blockData
}
})).then(blocks => blocks.filter(block => block !== null))
page['blocks'].sort((a, b) => a['sort'] - b['sort'])
if (page['open_graph_image']) page['open_graph_image'] = page['open_graph_image']['filename_disk']
return page
}))
}
export default async function () {
try {
const [pages, blocksByPageId] = await Promise.all([
fetchAllPages(),
fetchAllBlocks()
])
return await processPages(pages, blocksByPageId)
} catch (error) {
console.error('Error fetching and processing pages:', error)
return []
}
} }

View file

@ -1,11 +1,47 @@
import { createClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = process.env.SUPABASE_URL const SUPABASE_URL = process.env['SUPABASE_URL']
const SUPABASE_KEY = process.env.SUPABASE_KEY const SUPABASE_KEY = process.env['SUPABASE_KEY']
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 50 const PAGE_SIZE = 50
const fetchAllTags = async () => {
const { data, error } = await supabase
.from('posts_tags')
.select('posts_id, tags(name)')
if (error) {
console.error('Error fetching all tags from Supabase:', error)
return {}
}
return data.reduce((acc, { posts_id, tags }) => {
if (!tags || !tags['name']) return acc
if (!acc[posts_id]) acc[posts_id] = []
acc[posts_id].push(tags['name'])
return acc
}, {})
}
const fetchAllBlocks = async () => {
const { data, error } = await supabase
.from('posts_blocks')
.select('posts_id, collection, item, sort')
if (error) {
console.error('Error fetching all blocks from Supabase:', error)
return {}
}
return data.reduce((acc, block) => {
if (!acc[block['posts_id']]) {
acc[block['posts_id']] = []
}
acc[block['posts_id']].push(block)
return acc
}, {})
}
const fetchBlockData = async (collection, itemId) => { const fetchBlockData = async (collection, itemId) => {
const { data, error } = await supabase const { data, error } = await supabase
.from(collection) .from(collection)
@ -21,44 +57,6 @@ const fetchBlockData = async (collection, itemId) => {
return data return data
} }
const fetchTagsForPost = async (postId) => {
const { data, error } = await supabase
.from('posts_tags')
.select('tags(id, name)')
.eq('posts_id', postId)
if (error) {
console.error(`Error fetching tags for post ${postId}:`, error)
return []
}
return data.map(pt => pt.tags.name)
}
const fetchBlocksForPost = async (postId) => {
const { data, error } = await supabase
.from('posts_blocks')
.select('collection, item, sort')
.eq('posts_id', postId)
if (error) {
console.error(`Error fetching blocks for post ${postId}:`, error)
return []
}
const blocks = await Promise.all(data.map(async block => {
const blockData = await fetchBlockData(block.collection, block.item)
return {
type: block['collection'],
sort: block['sort'],
...blockData
}
}))
return blocks
}
const fetchAllPosts = async () => { const fetchAllPosts = async () => {
let posts = [] let posts = []
let page = 0 let page = 0
@ -67,11 +65,8 @@ const fetchAllPosts = async () => {
while (fetchMore) { while (fetchMore) {
const { data, error } = await supabase const { data, error } = await supabase
.from('posts') .from('optimized_posts')
.select(` .select('*')
*,
image(filename_disk)
`)
.order('date', { ascending: false }) .order('date', { ascending: false })
.range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1) .range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1)
@ -85,18 +80,42 @@ const fetchAllPosts = async () => {
for (const post of data) { for (const post of data) {
if (uniqueSlugs.has(post['slug'])) continue if (uniqueSlugs.has(post['slug'])) continue
uniqueSlugs.add(post.slug) uniqueSlugs.add(post['slug'])
post['tags'] = await fetchTagsForPost(post['id'])
post['blocks'] = await fetchBlocksForPost(post['id'])
if (post?.['image']?.['filename_disk']) post['image'] = post['image']['filename_disk']
posts.push(post) posts.push(post)
} }
page++ page++
} }
return posts return posts
} }
export default async function () { const processPosts = async (posts, tagsByPostId, blocksByPostId) => {
return await fetchAllPosts() return Promise.all(posts.map(async post => {
post['tags'] = tagsByPostId[post['id']] || []
const blocks = blocksByPostId[post['id']] || []
post['blocks'] = await Promise.all(blocks.map(async block => {
const blockData = await fetchBlockData(block['collection'], block['item'])
if (!blockData) return null
return {
'type': block['collection'],
'sort': block['sort'],
...blockData
}
})).then(blocks => blocks.filter(block => block !== null))
if (post['image']) post['image'] = post['image']['filename_disk']
return post
}))
}
export default async function () {
const [posts, tagsByPostId, blocksByPostId] = await Promise.all([
fetchAllPosts(),
fetchAllTags(),
fetchAllBlocks()
])
return await processPosts(posts, tagsByPostId, blocksByPostId)
} }

View file

@ -9,26 +9,31 @@ const PAGE_SIZE = 100
const fetchAllRobots = async () => { const fetchAllRobots = async () => {
let robots = [] let robots = []
let from = 0 let from = 0
let to = PAGE_SIZE - 1
while (true) { while (true) {
const { data, error } = await supabase const { data, error } = await supabase
.from('robots') .from('robots')
.select('user_agent') .select('user_agent')
.range(from, to) .range(from, from + PAGE_SIZE - 1)
if (error) { if (error) {
console.error('Error fetching robot data:', error) console.error('Error fetching robot data:', error)
return null return []
} }
robots = robots.concat(data) robots = robots.concat(data)
if (data.length < PAGE_SIZE) break if (data.length < PAGE_SIZE) break
from += PAGE_SIZE
} }
return robots.map(robot => robot['user_agent']).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) return robots.map(robot => robot.user_agent).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
} }
export default async function () { export default async function () {
try {
return await fetchAllRobots() return await fetchAllRobots()
} catch (error) {
console.error('Error fetching and processing robot data:', error)
return []
}
} }

View file

@ -1,10 +1,9 @@
import { createClient } from '@supabase/supabase-js' import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = process.env['SUPABASE_URL'] const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env['SUPABASE_KEY'] const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const PAGE_SIZE = 500
const PAGE_SIZE = 1000
const fetchAllShows = async () => { const fetchAllShows = async () => {
let shows = [] let shows = []
@ -12,27 +11,25 @@ const fetchAllShows = async () => {
while (true) { while (true) {
const { data, error } = await supabase const { data, error } = await supabase
.from('shows') .from('optimized_shows')
.select(` .select(`
title, id,
tmdb_id, tmdb_id,
last_watched_at,
title,
year,
collected, collected,
favorite, favorite,
year,
description, description,
review, review,
art(filename_disk), art,
backdrop(filename_disk), backdrop,
episodes ( episodes
episode_number,
season_number,
last_watched_at
)
`) `)
.range(rangeStart, rangeStart + PAGE_SIZE - 1) .range(rangeStart, rangeStart + PAGE_SIZE - 1)
if (error) { if (error) {
console.error(error) console.error('Error fetching shows:', error)
break break
} }
@ -44,89 +41,70 @@ const fetchAllShows = async () => {
return shows return shows
} }
const prepareShowData = (show) => { const prepareShowData = (show) => ({
return {
...show, ...show,
image: show['art']?.['filename_disk'] ? `/${show['art']['filename_disk']}` : '', image: show['art'] ? `/${show['art']}` : '',
backdrop: show['backdrop']?.['filename_disk'] ? `/${show['backdrop']['filename_disk']}` : '', backdrop: show['backdrop'] ? `/${show['backdrop']}` : '',
url: `/watching/shows/${show['tmdb_id']}`, url: `/watching/shows/${show['tmdb_id']}`,
} episodes: show['episodes'] || []
} })
const prepareEpisodeData = (show) => { const prepareEpisodeData = (show) => show['episodes'].map(episode => ({
return show['episodes'].map(episode => ({
...episode, ...episode,
show_title: show['title'], show_title: show['title'],
show_tmdb_id: show['tmdb_id'], show_tmdb_id: show['tmdb_id'],
collected: show['collected'], collected: show['collected'],
favorite: show['favorite'], favorite: show['favorite'],
image: show['image'], image: show['image'],
backdrop: show['backdrop'] backdrop: show['backdrop'],
episode_number: episode['episode_number'] || 0,
season_number: episode['season_number'] || 0,
last_watched_at: episode['last_watched_at'] || '1970-01-01T00:00:00Z'
})) }))
}
export default async function () {
const rawShows = await fetchAllShows()
const shows = rawShows.map(prepareShowData)
const episodes = shows.flatMap(prepareEpisodeData)
episodes.sort((a, b) => new Date(b.last_watched_at) - new Date(a.last_watched_at))
const formatEpisodeData = (episodes) => { const formatEpisodeData = (episodes) => {
const showEpisodesMap = {} const showEpisodesMap = {}
episodes.forEach(episode => { episodes.forEach(episode => {
const showTitle = episode['show_title'] const showTmdbId = episode.show_tmdb_id
const showTmdbId = episode['show_tmdb_id']
const episodeNumber = episode['episode_number']
const seasonNumber = episode['season_number']
const lastWatchedAt = episode['last_watched_at']
const collected = episode['collected']
const favorite = episode['favorite']
const image = episode['image']
const backdrop = episode['backdrop']
if (!showEpisodesMap[showTmdbId]) { if (!showEpisodesMap[showTmdbId]) {
showEpisodesMap[showTmdbId] = { showEpisodesMap[showTmdbId] = {
title: showTitle, title: episode['show_title'],
tmdbId: showTmdbId, tmdbId: showTmdbId,
collected, collected: episode['collected'],
favorite, favorite: episode['favorite'],
dateAdded: lastWatchedAt, dateAdded: episode['last_watched_at'],
lastWatchedAt, lastWatchedAt: episode['last_watched_at'],
episodes: [], episodes: [],
image, image: episode['image'],
backdrop backdrop: episode['backdrop'],
} }
} }
showEpisodesMap[showTmdbId].episodes.push({ showEpisodesMap[showTmdbId].episodes.push({
name: showTitle, name: episode['show_title'],
url: `/watching/shows/${showTmdbId}`, url: `/watching/shows/${showTmdbId}`,
subtext: `S${seasonNumber}E${episodeNumber}`, subtext: `S${episode['season_number']}E${episode['episode_number']}`,
episode: episodeNumber, episode: episode['episode_number'],
season: seasonNumber, season: episode['season_number'],
tmdbId: showTmdbId, tmdbId: showTmdbId,
type: 'tv', type: 'tv',
dateAdded: lastWatchedAt, dateAdded: episode['last_watched_at'],
lastWatchedAt, lastWatchedAt: episode['last_watched_at'],
image, image: episode['image'],
backdrop backdrop: episode['backdrop'],
}) })
}) })
const sortedShows = Object.values(showEpisodesMap).sort((a, b) => new Date(b.episodes[0]['lastWatchedAt']) - new Date(a.episodes[0]['lastWatchedAt'])) return Object.values(showEpisodesMap).sort((a, b) => new Date(b['episodes'][0]['lastWatchedAt']) - new Date(a['episodes'][0]['lastWatchedAt'])).flatMap(show => {
const episodeData = []
sortedShows.forEach(show => {
const startingEpisode = show['episodes'][show['episodes'].length - 1]['episode'] const startingEpisode = show['episodes'][show['episodes'].length - 1]['episode']
const startingSeason = show['episodes'][show['episodes'].length - 1]['season'] const startingSeason = show['episodes'][show['episodes'].length - 1]['season']
const endingEpisode = show['episodes'][0]['episode'] const endingEpisode = show['episodes'][0]['episode']
const endingSeason = show['episodes'][0]['season'] const endingSeason = show['episodes'][0]['season']
if (show['episodes'].length > 1) { if (show.episodes.length > 1) {
episodeData.push({ return {
name: show['title'], name: show['title'],
url: `/watching/shows/${show['tmdbId']}`, url: `/watching/shows/${show['tmdbId']}`,
subtext: `S${startingSeason}E${startingEpisode} - S${endingSeason}E${endingEpisode}`, subtext: `S${startingSeason}E${startingEpisode} - S${endingSeason}E${endingEpisode}`,
@ -139,27 +117,35 @@ export default async function () {
favorite: show['favorite'], favorite: show['favorite'],
type: 'tv-range', type: 'tv-range',
image: show['image'], image: show['image'],
backdrop: show['backdrop'] backdrop: show['backdrop'],
}) }
} else { } else {
const singleEpisode = show['episodes'][0] return show['episodes'][0]
singleEpisode.collected = show['collected']
singleEpisode.favorite = show['favorite']
singleEpisode.image = show['image']
singleEpisode.backdrop = show['backdrop']
episodeData.push(singleEpisode)
} }
}) })
return episodeData
} }
const favoriteShows = shows.filter(show => show['favorite']) export default async function () {
try {
const rawShows = await fetchAllShows()
const shows = rawShows.map(prepareShowData)
const episodes = shows.flatMap(prepareEpisodeData).sort((a, b) => new Date(b.last_watched_at) - new Date(a.last_watched_at))
const favoriteShows = shows.filter(show => show.favorite)
return { return {
shows, shows,
watchHistory: formatEpisodeData(episodes), watchHistory: formatEpisodeData(episodes),
recentlyWatched: formatEpisodeData(episodes.slice(0, 225)), recentlyWatched: formatEpisodeData(episodes.slice(0, 225)),
favorites: formatEpisodeData(favoriteShows.flatMap(prepareEpisodeData)).sort((a, b) => a['name'].localeCompare(b['name'])) favorites: formatEpisodeData(favoriteShows.flatMap(prepareEpisodeData)).sort((a, b) => a.name.localeCompare(b.name)),
}
} catch (error) {
console.error('Error fetching and processing shows data:', error)
return {
shows: [],
watchHistory: [],
recentlyWatched: [],
favorites: [],
}
} }
} }

View file

@ -9,7 +9,7 @@ updated: "now"
schema: artist schema: artist
--- ---
{%- capture alt -%} {%- capture alt -%}
{{ artist.name_string }} / {{ artist.country }} {{ artist.name }} / {{ artist.country }}
{%- endcapture -%} {%- endcapture -%}
{% capture js %} {% capture js %}
{% render "../../../../assets/scripts/text-toggle.js" %} {% render "../../../../assets/scripts/text-toggle.js" %}
@ -38,23 +38,23 @@ schema: artist
height="480" height="480"
/> />
<div class="artist-meta"> <div class="artist-meta">
<p class="title"><strong>{{ artist.name_string }}</strong></p> <p class="title"><strong>{{ artist.name }}</strong></p>
{%- if artist.favorite -%} {%- if artist.favorite -%}
<p class="sub-meta favorite flex-centered">{% tablericon "heart" "Favorite" %} This is one of my favorite artists!</p> <p class="sub-meta favorite flex-centered">{% tablericon "heart" "Favorite" %} This is one of my favorite artists!</p>
{%- endif -%} {%- endif -%}
{%- if artist.tattoo -%} {%- if artist.tattoo -%}
<p class="sub-meta tattoo flex-centered">{% tablericon "needle" "Tattoo" %} I have a tattoo inspired by this artist!</p> <p class="sub-meta tattoo flex-centered">{% tablericon "needle" "Tattoo" %} I have a tattoo inspired by this artist!</p>
{%- endif -%} {%- endif -%}
{%- if artist.total_plays > 0 -%} {%- if artist.totalPlays > 0 -%}
<p class="sub-meta"><strong class="highlight-text">{{ artist.total_plays }} plays</strong></p> <p class="sub-meta"><strong class="highlight-text">{{ artist.totalPlays }} plays</strong></p>
{%- endif -%} {%- endif -%}
<p class="sub-meta"> <p class="sub-meta">
<a href="/music/genres/{{ artist.genres | replace: '/', '-' | slugify | downcase }}" title="Learn more about {{ artist.genres | escape }}"> <a href="/music/genres/{{ artist.genre | replace: '/', '-' | slugify | downcase }}" title="Learn more about {{ artist.genre | escape }}">
{{ artist.genres }} {{ artist.genre }}
</a> </a>
</p> </p>
<p class="sub-meta"> <p class="sub-meta">
<a class="brain" href="https://musicbrainz.org/artist/{{ artist.mbid }}" title="View {{ artist.name_string | escape }} on MusicBrainz">{% tablericon "brain" "MusicBrainz" %}</a> <a class="brain" href="https://musicbrainz.org/artist/{{ artist.mbid }}" title="View {{ artist.name | escape }} on MusicBrainz">{% tablericon "brain" "MusicBrainz" %}</a>
</p> </p>
</div> </div>
</div> </div>
@ -71,8 +71,8 @@ schema: artist
{% for album in artist.albums %} {% for album in artist.albums %}
<tr> <tr>
<td>{{ album.name }}</td> <td>{{ album.name }}</td>
<td>{{ album.total_plays }}</td> <td>{{ album.totalPlays }}</td>
<td>{{ album.release_year }}</td> <td>{{ album.releaseYear }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>