feat: initial commit
This commit is contained in:
commit
0ff7457679
192 changed files with 24379 additions and 0 deletions
46
src/utils/albumReleasesCalendar.js
Normal file
46
src/utils/albumReleasesCalendar.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { parseISO, format, isValid } from "date-fns";
|
||||
import { toZonedTime } from "date-fns-tz";
|
||||
import { createEvents } from "ics";
|
||||
|
||||
const nowUTC = toZonedTime(new Date(), "UTC");
|
||||
const formattedDate = format(nowUTC, "yyyyMMdd'T'HHmmss'Z'");
|
||||
|
||||
export async function albumReleasesCalendar(albumReleases) {
|
||||
if (!albumReleases || albumReleases.length === 0) return "";
|
||||
|
||||
const events = albumReleases
|
||||
.map((album) => {
|
||||
const date = parseISO(album["release_date"]);
|
||||
if (!isValid(date)) return null;
|
||||
|
||||
return {
|
||||
start: [
|
||||
date.getFullYear(),
|
||||
date.getMonth() + 1,
|
||||
date.getDate(),
|
||||
],
|
||||
startInputType: "local",
|
||||
startOutputType: "local",
|
||||
title: `Release: ${album["artist"]["name"]} - ${album["title"]}`,
|
||||
description: `Check out this new album release: ${album["url"]}. Read more about ${album["artist"]["name"]} at https://coryd.dev${album["artist"]["url"]}`,
|
||||
url: album["url"],
|
||||
uid: `${format(date, "yyyyMMdd")}-${album["artist"]["name"]}-${album["title"]}@coryd.dev`,
|
||||
timestamp: formattedDate,
|
||||
};
|
||||
})
|
||||
.filter((event) => event !== null);
|
||||
|
||||
const { error, value } = createEvents(events, {
|
||||
calName: "Album releases calendar / coryd.dev",
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error("Error creating events: ", error);
|
||||
events.forEach((event, index) => {
|
||||
console.error(`Event ${index}:`, event);
|
||||
});
|
||||
return "";
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
1
src/utils/constants/index.js
Normal file
1
src/utils/constants/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export const CACHE_DURATION = 120 * 1000;
|
38
src/utils/data/activity.js
Normal file
38
src/utils/data/activity.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedActivity = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchActivity() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedActivity && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedActivity;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_all_activity")
|
||||
.select("feed");
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching activity data:", error);
|
||||
return cachedActivity || [];
|
||||
}
|
||||
|
||||
const [{ feed } = {}] = data || [];
|
||||
const filteredFeed = feed?.filter((item) => item.feed !== null) || [];
|
||||
|
||||
cachedActivity = filteredFeed;
|
||||
lastFetchTime = now;
|
||||
|
||||
return filteredFeed;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchActivity:", error);
|
||||
return cachedActivity || [];
|
||||
}
|
||||
}
|
56
src/utils/data/albumReleases.js
Normal file
56
src/utils/data/albumReleases.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { format, startOfDay, isAfter, getTime } from "date-fns";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedAlbumReleases = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchAlbumReleases() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedAlbumReleases && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedAlbumReleases;
|
||||
|
||||
const today = getTime(startOfDay(new Date()));
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_album_releases")
|
||||
.select("*");
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching album releases:", error);
|
||||
return { all: [], upcoming: [] };
|
||||
}
|
||||
|
||||
const all = data
|
||||
.map((album) => {
|
||||
const releaseDate = startOfDay(
|
||||
new Date(album.release_timestamp * 1000)
|
||||
);
|
||||
return {
|
||||
...album,
|
||||
description: album.artist.description,
|
||||
date: format(releaseDate, "PPPP"),
|
||||
timestamp: getTime(releaseDate) / 1000,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
const upcoming = all.filter((album) =>
|
||||
isAfter(new Date(album.release_timestamp * 1000), new Date(today))
|
||||
);
|
||||
|
||||
cachedAlbumReleases = { all, upcoming };
|
||||
lastFetchTime = now;
|
||||
|
||||
return { all, upcoming };
|
||||
} catch (error) {
|
||||
console.error("Error in fetchAlbumReleases:", error);
|
||||
return cachedAlbumReleases || { all: [], upcoming: [] };
|
||||
}
|
||||
}
|
43
src/utils/data/analytics.js
Normal file
43
src/utils/data/analytics.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedPages = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchAnalyticsData() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedPages && now - lastFetchTime < CACHE_DURATION) return cachedPages;
|
||||
|
||||
const API_KEY_PLAUSIBLE = import.meta.env.API_KEY_PLAUSIBLE;
|
||||
const url =
|
||||
"https://plausible.io/api/v1/stats/breakdown?site_id=coryd.dev&period=6mo&property=event:page&limit=30";
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY_PLAUSIBLE}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 429) {
|
||||
console.warn("Rate limit reached: Too Many Requests");
|
||||
throw new Error("Too many requests. Please try again later.");
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`Error fetching Plausible data: ${res.statusText}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const pages = await res.json();
|
||||
const filteredPages = pages.results.filter((p) => p.page.includes("posts"));
|
||||
|
||||
cachedPages = filteredPages;
|
||||
lastFetchTime = now;
|
||||
|
||||
return filteredPages;
|
||||
} catch (error) {
|
||||
console.error("Error fetching Plausible data:", error);
|
||||
return cachedPages || [];
|
||||
}
|
||||
}
|
53
src/utils/data/artists.js
Normal file
53
src/utils/data/artists.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { parseCountryField } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedArtists = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchArtists() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedArtists && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedArtists;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let artists = [];
|
||||
let rangeStart = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_artists")
|
||||
.select("*")
|
||||
.range(rangeStart, rangeStart + PAGE_SIZE - 1);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching artists:", error);
|
||||
break;
|
||||
}
|
||||
|
||||
artists = artists.concat(
|
||||
data.map((artist) => ({
|
||||
...artist,
|
||||
country: parseCountryField(artist["country"]),
|
||||
}))
|
||||
);
|
||||
|
||||
if (data.length < PAGE_SIZE) break;
|
||||
rangeStart += PAGE_SIZE;
|
||||
}
|
||||
|
||||
cachedArtists = artists;
|
||||
lastFetchTime = now;
|
||||
|
||||
return artists;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchArtists:", error);
|
||||
return cachedArtists || [];
|
||||
}
|
||||
}
|
41
src/utils/data/blogroll.js
Normal file
41
src/utils/data/blogroll.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedBlogroll = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchBlogroll() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedBlogroll && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedBlogroll;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("authors")
|
||||
.select("*")
|
||||
.eq("blogroll", true)
|
||||
.order("name", { ascending: true });
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching blogroll:", error);
|
||||
return [];
|
||||
}
|
||||
|
||||
const sortedData = data.sort((a, b) =>
|
||||
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
);
|
||||
|
||||
cachedBlogroll = sortedData;
|
||||
lastFetchTime = now;
|
||||
|
||||
return sortedData;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchBlogroll:", error);
|
||||
return cachedBlogroll || [];
|
||||
}
|
||||
}
|
79
src/utils/data/books.js
Normal file
79
src/utils/data/books.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedBooks = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchBooks() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedBooks && now - lastFetchTime < CACHE_DURATION) return cachedBooks;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let books = [];
|
||||
let rangeStart = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_books")
|
||||
.select("*")
|
||||
.order("date_finished", { ascending: false })
|
||||
.range(rangeStart, rangeStart + PAGE_SIZE - 1);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching books:", error);
|
||||
break;
|
||||
}
|
||||
|
||||
books = books.concat(data);
|
||||
if (data.length < PAGE_SIZE) break;
|
||||
rangeStart += PAGE_SIZE;
|
||||
}
|
||||
|
||||
const years = {};
|
||||
books.forEach((book) => {
|
||||
const year = book.year;
|
||||
if (!years[year]) {
|
||||
years[year] = { value: year, data: [book] };
|
||||
} else {
|
||||
years[year].data.push(book);
|
||||
}
|
||||
});
|
||||
|
||||
const sortedByYear = Object.values(years).filter(
|
||||
(year) => year.value > 2017
|
||||
);
|
||||
const currentYear = new Date().getFullYear();
|
||||
const booksForCurrentYear =
|
||||
sortedByYear
|
||||
.find((yearGroup) => yearGroup.value === currentYear)
|
||||
?.data.filter((book) => book["status"] === "finished") || [];
|
||||
|
||||
const result = {
|
||||
all: books,
|
||||
years: sortedByYear,
|
||||
currentYear: booksForCurrentYear,
|
||||
feed: books.filter((book) => book.feed),
|
||||
};
|
||||
|
||||
cachedBooks = result;
|
||||
lastFetchTime = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchBooks:", error);
|
||||
return (
|
||||
cachedBooks || {
|
||||
all: [],
|
||||
years: [],
|
||||
currentYear: [],
|
||||
feed: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
51
src/utils/data/concerts.js
Normal file
51
src/utils/data/concerts.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedConcerts = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchConcerts() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedConcerts && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedConcerts;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let concerts = [];
|
||||
let rangeStart = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_concerts")
|
||||
.select("*")
|
||||
.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;
|
||||
}
|
||||
|
||||
const result = concerts.map((concert) => ({
|
||||
...concert,
|
||||
artist: concert.artist || { name: concert.artist_name_string, url: null },
|
||||
}));
|
||||
|
||||
cachedConcerts = result;
|
||||
lastFetchTime = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchConcerts:", error);
|
||||
return cachedConcerts || [];
|
||||
}
|
||||
}
|
33
src/utils/data/dynamic/artistByUrl.js
Normal file
33
src/utils/data/dynamic/artistByUrl.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { removeTrailingSlash } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const cache = {};
|
||||
const cacheTimestamps = {};
|
||||
|
||||
export async function fetchArtistByUrl(env, url) {
|
||||
const normalizedUrl = removeTrailingSlash(url);
|
||||
const now = Date.now();
|
||||
|
||||
if (
|
||||
cache[normalizedUrl] &&
|
||||
now - cacheTimestamps[normalizedUrl] < CACHE_DURATION
|
||||
)
|
||||
return cache[normalizedUrl];
|
||||
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const { data: artist, error } = await supabase
|
||||
.from("optimized_artists")
|
||||
.select("*")
|
||||
.eq("url", normalizedUrl)
|
||||
.limit(1);
|
||||
|
||||
if (error || !artist.length) return null;
|
||||
|
||||
cache[normalizedUrl] = artist[0];
|
||||
cacheTimestamps[normalizedUrl] = now;
|
||||
|
||||
return artist[0];
|
||||
}
|
33
src/utils/data/dynamic/bookByUrl.js
Normal file
33
src/utils/data/dynamic/bookByUrl.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { removeTrailingSlash } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const cache = {};
|
||||
const cacheTimestamps = {};
|
||||
|
||||
export async function fetchBookByUrl(env, url) {
|
||||
const normalizedUrl = removeTrailingSlash(url);
|
||||
const now = Date.now();
|
||||
|
||||
if (
|
||||
cache[normalizedUrl] &&
|
||||
now - cacheTimestamps[normalizedUrl] < CACHE_DURATION
|
||||
)
|
||||
return cache[normalizedUrl];
|
||||
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const { data: book, error } = await supabase
|
||||
.from("optimized_books")
|
||||
.select("*")
|
||||
.eq("url", normalizedUrl)
|
||||
.limit(1);
|
||||
|
||||
if (error || !book.length) return null;
|
||||
|
||||
cache[normalizedUrl] = book[0];
|
||||
cacheTimestamps[normalizedUrl] = now;
|
||||
|
||||
return book[0];
|
||||
}
|
32
src/utils/data/dynamic/genreByUrl.js
Normal file
32
src/utils/data/dynamic/genreByUrl.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { removeTrailingSlash } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedGenres = {};
|
||||
let lastFetchTime = {};
|
||||
|
||||
export async function fetchGenreByUrl(env, url) {
|
||||
const normalizedUrl = removeTrailingSlash(url);
|
||||
|
||||
if (
|
||||
cachedGenres[normalizedUrl] &&
|
||||
Date.now() - lastFetchTime[normalizedUrl] < CACHE_DURATION
|
||||
)
|
||||
return cachedGenres[normalizedUrl];
|
||||
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const { data: genre, error } = await supabase
|
||||
.from("optimized_genres")
|
||||
.select("*")
|
||||
.eq("url", normalizedUrl)
|
||||
.limit(1);
|
||||
|
||||
if (error || !genre?.length) return null;
|
||||
|
||||
cachedGenres[normalizedUrl] = genre[0];
|
||||
lastFetchTime[normalizedUrl] = Date.now();
|
||||
|
||||
return genre[0];
|
||||
}
|
32
src/utils/data/dynamic/movieByUrl.js
Normal file
32
src/utils/data/dynamic/movieByUrl.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { removeTrailingSlash } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedMovies = {};
|
||||
let lastFetchTime = {};
|
||||
|
||||
export async function fetchMovieByUrl(env, url) {
|
||||
const normalizedUrl = removeTrailingSlash(url);
|
||||
|
||||
if (
|
||||
cachedMovies[normalizedUrl] &&
|
||||
Date.now() - lastFetchTime[normalizedUrl] < CACHE_DURATION
|
||||
)
|
||||
return cachedMovies[normalizedUrl];
|
||||
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const { data: movie, error } = await supabase
|
||||
.from("optimized_movies")
|
||||
.select("*")
|
||||
.eq("url", normalizedUrl)
|
||||
.limit(1);
|
||||
|
||||
if (error || !movie?.length) return null;
|
||||
|
||||
cachedMovies[normalizedUrl] = movie[0];
|
||||
lastFetchTime[normalizedUrl] = Date.now();
|
||||
|
||||
return movie[0];
|
||||
}
|
32
src/utils/data/dynamic/showByUrl.js
Normal file
32
src/utils/data/dynamic/showByUrl.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { removeTrailingSlash } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedShows = {};
|
||||
let lastFetchTime = {};
|
||||
|
||||
export async function fetchShowByUrl(env, url) {
|
||||
const normalizedUrl = removeTrailingSlash(url);
|
||||
|
||||
if (
|
||||
cachedShows[normalizedUrl] &&
|
||||
Date.now() - lastFetchTime[normalizedUrl] < CACHE_DURATION
|
||||
)
|
||||
return cachedShows[normalizedUrl];
|
||||
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const { data: tv, error } = await supabase
|
||||
.from("optimized_shows")
|
||||
.select("*")
|
||||
.eq("url", normalizedUrl)
|
||||
.limit(1);
|
||||
|
||||
if (error || !tv?.length) return null;
|
||||
|
||||
cachedShows[normalizedUrl] = tv[0];
|
||||
lastFetchTime[normalizedUrl] = Date.now();
|
||||
|
||||
return tv[0];
|
||||
}
|
32
src/utils/data/genres.js
Normal file
32
src/utils/data/genres.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedGenres = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchGenres() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedGenres && now - lastFetchTime < CACHE_DURATION) return cachedGenres;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase.from("optimized_genres").select("*");
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching genres:", error);
|
||||
return [];
|
||||
}
|
||||
|
||||
cachedGenres = data;
|
||||
lastFetchTime = now;
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchGenres:", error);
|
||||
return cachedGenres || [];
|
||||
}
|
||||
}
|
79
src/utils/data/global/index.js
Normal file
79
src/utils/data/global/index.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { fetchGlobals } from "@utils/data/globals.js";
|
||||
import { fetchNavigation } from "@utils/data/nav.js";
|
||||
import { fetchArtistByUrl } from "@utils/data/dynamic/artistByUrl.js";
|
||||
import { fetchBookByUrl } from "@utils/data/dynamic/bookByUrl.js";
|
||||
import { fetchGenreByUrl } from "@utils/data/dynamic/genreByUrl.js";
|
||||
import { fetchMovieByUrl } from "@utils/data/dynamic/movieByUrl.js";
|
||||
import { fetchShowByUrl } from "@utils/data/dynamic/showByUrl.js";
|
||||
import { isbnRegex } from "@utils/helpers/media.js";
|
||||
import { isExcludedPath } from "@utils/helpers/general.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedGlobals = null;
|
||||
let cachedNav = null;
|
||||
let lastFetchTimeGlobals = 0;
|
||||
let lastFetchTimeNav = 0;
|
||||
|
||||
export async function fetchGlobalData(Astro, urlPath) {
|
||||
const now = Date.now();
|
||||
|
||||
if (Astro?.locals) {
|
||||
const data = {
|
||||
globals: Astro.locals.globals,
|
||||
nav: Astro.locals.nav,
|
||||
};
|
||||
|
||||
if (urlPath?.startsWith("/music/artists/"))
|
||||
data.artist = Astro.locals.artist;
|
||||
if (isbnRegex.test(urlPath)) data.book = Astro.locals.book;
|
||||
if (urlPath?.startsWith("/music/genres/")) data.genre = Astro.locals.genre;
|
||||
if (
|
||||
urlPath?.startsWith("/watching/movies/") &&
|
||||
!isExcludedPath(urlPath, ["/favorites", "/recent"])
|
||||
)
|
||||
data.movie = Astro.locals.movie;
|
||||
if (
|
||||
urlPath?.startsWith("/watching/shows/") &&
|
||||
!isExcludedPath(urlPath, ["/favorites", "/recent"])
|
||||
)
|
||||
data.show = Astro.locals.show;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const globals =
|
||||
cachedGlobals && now - lastFetchTimeGlobals < CACHE_DURATION
|
||||
? cachedGlobals
|
||||
: ((cachedGlobals = await fetchGlobals()), (lastFetchTimeGlobals = now));
|
||||
|
||||
const nav =
|
||||
cachedNav && now - lastFetchTimeNav < CACHE_DURATION
|
||||
? cachedNav
|
||||
: ((cachedNav = await fetchNavigation()), (lastFetchTimeNav = now));
|
||||
|
||||
let artist, book, genre, movie, show;
|
||||
|
||||
try {
|
||||
if (urlPath?.startsWith("/music/artists/")) {
|
||||
artist = await fetchArtistByUrl(urlPath);
|
||||
} else if (isbnRegex.test(urlPath)) {
|
||||
book = await fetchBookByUrl(urlPath);
|
||||
} else if (urlPath?.startsWith("/music/genres/")) {
|
||||
genre = await fetchGenreByUrl(urlPath);
|
||||
} else if (
|
||||
urlPath?.startsWith("/watching/movies/") &&
|
||||
!isExcludedPath(urlPath, ["/favorites", "/recent"])
|
||||
) {
|
||||
movie = await fetchMovieByUrl(urlPath);
|
||||
} else if (
|
||||
urlPath?.startsWith("/watching/shows/") &&
|
||||
!isExcludedPath(urlPath, ["/favorites", "/recent"])
|
||||
) {
|
||||
show = await fetchShowByUrl(urlPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
|
||||
return { globals, nav, artist, book, genre, movie, show };
|
||||
}
|
35
src/utils/data/globals.js
Normal file
35
src/utils/data/globals.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedGlobals = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchGlobals(env) {
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedGlobals && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedGlobals;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_globals")
|
||||
.select("*")
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching globals:", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
cachedGlobals = data;
|
||||
lastFetchTime = now;
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchGlobals:", error);
|
||||
return cachedGlobals || {};
|
||||
}
|
||||
}
|
47
src/utils/data/links.js
Normal file
47
src/utils/data/links.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedLinks = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchLinks() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedLinks && now - lastFetchTime < CACHE_DURATION) return cachedLinks;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let links = [];
|
||||
let page = 0;
|
||||
let fetchMore = true;
|
||||
|
||||
try {
|
||||
while (fetchMore) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_links")
|
||||
.select("*")
|
||||
.range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching links:", error);
|
||||
return cachedLinks || links;
|
||||
}
|
||||
|
||||
if (data.length < PAGE_SIZE) fetchMore = false;
|
||||
|
||||
links = links.concat(data);
|
||||
page++;
|
||||
}
|
||||
|
||||
cachedLinks = links;
|
||||
lastFetchTime = now;
|
||||
|
||||
return links;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchLinks:", error);
|
||||
return cachedLinks || [];
|
||||
}
|
||||
}
|
80
src/utils/data/movies.js
Normal file
80
src/utils/data/movies.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { parseISO, subMonths } from "date-fns";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedMovies = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchMovies() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedMovies && now - lastFetchTime < CACHE_DURATION) return cachedMovies;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let movies = [];
|
||||
let rangeStart = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_movies")
|
||||
.select("*")
|
||||
.range(rangeStart, rangeStart + PAGE_SIZE - 1);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching movies:", error);
|
||||
return (
|
||||
cachedMovies || {
|
||||
movies: [],
|
||||
watchHistory: [],
|
||||
recentlyWatched: [],
|
||||
favorites: [],
|
||||
feed: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
movies = movies.concat(data);
|
||||
if (data.length < PAGE_SIZE) break;
|
||||
rangeStart += PAGE_SIZE;
|
||||
}
|
||||
|
||||
const favoriteMovies = movies.filter((movie) => movie.favorite);
|
||||
|
||||
const recentlyWatchedMovies = movies.filter((movie) => {
|
||||
if (!movie.last_watched) return false;
|
||||
const lastWatchedDate = parseISO(movie.last_watched);
|
||||
const threeMonthsAgo = subMonths(new Date(), 3);
|
||||
|
||||
return lastWatchedDate >= threeMonthsAgo;
|
||||
});
|
||||
|
||||
const result = {
|
||||
movies,
|
||||
watchHistory: movies.filter((movie) => movie.last_watched),
|
||||
recentlyWatched: recentlyWatchedMovies,
|
||||
favorites: favoriteMovies.sort((a, b) => a.title.localeCompare(b.title)),
|
||||
feed: movies.filter((movie) => movie.feed),
|
||||
};
|
||||
|
||||
cachedMovies = result;
|
||||
lastFetchTime = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchMovies:", error);
|
||||
return (
|
||||
cachedMovies || {
|
||||
movies: [],
|
||||
watchHistory: [],
|
||||
recentlyWatched: [],
|
||||
favorites: [],
|
||||
feed: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
59
src/utils/data/music/month.js
Normal file
59
src/utils/data/music/month.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { fetchDataFromView, calculateTotalPlays } from "@utils/data/music/utils.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedMonthData = null;
|
||||
let lastFetchTimeMonth = 0;
|
||||
|
||||
export async function fetchMusicMonth() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedMonthData && now - lastFetchTimeMonth < CACHE_DURATION)
|
||||
return cachedMonthData;
|
||||
|
||||
try {
|
||||
const [
|
||||
monthTracks,
|
||||
monthArtists,
|
||||
monthAlbums,
|
||||
monthGenres,
|
||||
] = await Promise.all([
|
||||
fetchDataFromView("month_tracks", supabase),
|
||||
fetchDataFromView("month_artists", supabase),
|
||||
fetchDataFromView("month_albums", supabase),
|
||||
fetchDataFromView("month_genres", supabase),
|
||||
]);
|
||||
|
||||
const result = {
|
||||
month: {
|
||||
tracks: monthTracks,
|
||||
artists: monthArtists,
|
||||
albums: monthAlbums,
|
||||
genres: monthGenres,
|
||||
totalTracks: calculateTotalPlays(monthTracks),
|
||||
},
|
||||
};
|
||||
|
||||
cachedMonthData = result;
|
||||
lastFetchTimeMonth = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchMonthData:", error);
|
||||
return (
|
||||
cachedMonthData || {
|
||||
month: {
|
||||
tracks: [],
|
||||
artists: [],
|
||||
albums: [],
|
||||
genres: [],
|
||||
totalTracks: "0",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
28
src/utils/data/music/utils.js
Normal file
28
src/utils/data/music/utils.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
const PAGE_SIZE = 1000;
|
||||
|
||||
export const fetchDataFromView = async (viewName, supabase) => {
|
||||
let rows = [];
|
||||
let rangeStart = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from(viewName)
|
||||
.select("*")
|
||||
.range(rangeStart, rangeStart + PAGE_SIZE - 1);
|
||||
|
||||
if (error || data.length === 0) break;
|
||||
|
||||
rows = [...rows, ...data];
|
||||
if (data.length < PAGE_SIZE) break;
|
||||
rangeStart += PAGE_SIZE;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data from view: ${viewName}`, error);
|
||||
}
|
||||
|
||||
return rows;
|
||||
};
|
||||
|
||||
export const calculateTotalPlays = (tracks) =>
|
||||
tracks.reduce((acc, track) => acc + track.plays, 0).toLocaleString("en-US");
|
58
src/utils/data/music/week.js
Normal file
58
src/utils/data/music/week.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { fetchDataFromView, calculateTotalPlays } from "@utils/data/music/utils.js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedWeekData = null;
|
||||
let lastFetchTimeWeek = 0;
|
||||
|
||||
export async function fetchMusicWeek() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedWeekData && now - lastFetchTimeWeek < CACHE_DURATION)
|
||||
return cachedWeekData;
|
||||
|
||||
try {
|
||||
const [recentTracks, weekTracks, weekArtists, weekAlbums, weekGenres] =
|
||||
await Promise.all([
|
||||
fetchDataFromView("recent_tracks", supabase),
|
||||
fetchDataFromView("week_tracks", supabase),
|
||||
fetchDataFromView("week_artists", supabase),
|
||||
fetchDataFromView("week_albums", supabase),
|
||||
fetchDataFromView("week_genres", supabase),
|
||||
]);
|
||||
|
||||
const result = {
|
||||
recent: recentTracks,
|
||||
week: {
|
||||
tracks: weekTracks,
|
||||
artists: weekArtists,
|
||||
albums: weekAlbums,
|
||||
genres: weekGenres,
|
||||
totalTracks: calculateTotalPlays(weekTracks),
|
||||
},
|
||||
};
|
||||
|
||||
cachedWeekData = result;
|
||||
lastFetchTimeWeek = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchWeekData:", error);
|
||||
return (
|
||||
cachedWeekData || {
|
||||
recent: [],
|
||||
week: {
|
||||
tracks: [],
|
||||
artists: [],
|
||||
albums: [],
|
||||
genres: [],
|
||||
totalTracks: "0",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
51
src/utils/data/nav.js
Normal file
51
src/utils/data/nav.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
let cachedNavigation = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchNavigation(env) {
|
||||
const SUPABASE_URL = import.meta.env?.SUPABASE_URL || env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env?.SUPABASE_KEY || env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedNavigation && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedNavigation;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_navigation")
|
||||
.select("*");
|
||||
if (error) {
|
||||
console.error("Error fetching navigation:", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
const menu = data.reduce((acc, item) => {
|
||||
const menuItem = {
|
||||
title: item["title"] || item["page_title"],
|
||||
permalink: item["permalink"] || item["page_permalink"],
|
||||
icon: item["icon"],
|
||||
sort: item["sort"],
|
||||
};
|
||||
|
||||
if (!acc[item["menu_location"]]) acc[item["menu_location"]] = [menuItem];
|
||||
else acc[item["menu_location"]].push(menuItem);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.keys(menu).forEach((location) => {
|
||||
menu[location].sort((a, b) => a["sort"] - b["sort"]);
|
||||
});
|
||||
|
||||
cachedNavigation = menu;
|
||||
lastFetchTime = now;
|
||||
|
||||
return menu;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchNavigation:", error);
|
||||
return cachedNavigation || {};
|
||||
}
|
||||
}
|
32
src/utils/data/pages.js
Normal file
32
src/utils/data/pages.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedPages = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchPages() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedPages && now - lastFetchTime < CACHE_DURATION) return cachedPages;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase.from("optimized_pages").select("*");
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching pages:", error);
|
||||
return cachedPages || [];
|
||||
}
|
||||
|
||||
cachedPages = data;
|
||||
lastFetchTime = now;
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchPages:", error);
|
||||
return cachedPages || [];
|
||||
}
|
||||
}
|
68
src/utils/data/posts.js
Normal file
68
src/utils/data/posts.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedPosts = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchAllPosts() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedPosts && now - lastFetchTime < CACHE_DURATION) return cachedPosts;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let posts = [];
|
||||
let page = 0;
|
||||
let fetchMore = true;
|
||||
|
||||
while (fetchMore) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_posts")
|
||||
.select("*")
|
||||
.order("date", { ascending: false })
|
||||
.range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
|
||||
|
||||
if (error) return posts;
|
||||
|
||||
if (data.length < PAGE_SIZE) fetchMore = false;
|
||||
|
||||
posts = posts.concat(data);
|
||||
page++;
|
||||
}
|
||||
|
||||
cachedPosts = posts;
|
||||
lastFetchTime = now;
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
||||
let cachedPostByUrl = {};
|
||||
let lastFetchTimeByUrl = {};
|
||||
|
||||
export async function fetchPostByUrl(url) {
|
||||
const now = Date.now();
|
||||
|
||||
if (
|
||||
cachedPostByUrl[url] &&
|
||||
lastFetchTimeByUrl[url] &&
|
||||
now - lastFetchTimeByUrl[url] < CACHE_DURATION
|
||||
) {
|
||||
return cachedPostByUrl[url];
|
||||
}
|
||||
|
||||
const { data: post, error } = await supabase
|
||||
.from("optimized_posts")
|
||||
.select("*")
|
||||
.eq("url", url)
|
||||
.limit(1);
|
||||
|
||||
if (error || !post.length) return null;
|
||||
|
||||
cachedPostByUrl[url] = post[0];
|
||||
lastFetchTimeByUrl[url] = now;
|
||||
|
||||
return post[0];
|
||||
}
|
47
src/utils/data/robots.js
Normal file
47
src/utils/data/robots.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedRobots = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchAllRobots() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedRobots && now - lastFetchTime < CACHE_DURATION) return cachedRobots;
|
||||
|
||||
const PAGE_SIZE = 500;
|
||||
let robots = [];
|
||||
let from = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("robots")
|
||||
.select("user_agent")
|
||||
.range(from, from + PAGE_SIZE - 1);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching robots:", error);
|
||||
return cachedRobots || [];
|
||||
}
|
||||
|
||||
robots = robots.concat(data);
|
||||
if (data.length < PAGE_SIZE) break;
|
||||
from += PAGE_SIZE;
|
||||
}
|
||||
|
||||
const result = robots.map((robot) => robot["user_agent"]).sort();
|
||||
|
||||
cachedRobots = result;
|
||||
lastFetchTime = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchAllRobots:", error);
|
||||
return cachedRobots || [];
|
||||
}
|
||||
}
|
39
src/utils/data/syndication.js
Normal file
39
src/utils/data/syndication.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedSyndication = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export async function fetchSyndication() {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedSyndication && now - lastFetchTime < CACHE_DURATION)
|
||||
return cachedSyndication;
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_syndication")
|
||||
.select("syndication");
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching syndication:", error);
|
||||
return cachedSyndication || [];
|
||||
}
|
||||
|
||||
const [{ syndication } = {}] = data;
|
||||
const result =
|
||||
syndication?.filter((item) => item.syndication !== null) || [];
|
||||
|
||||
cachedSyndication = result;
|
||||
lastFetchTime = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchSyndication:", error);
|
||||
return cachedSyndication || [];
|
||||
}
|
||||
}
|
80
src/utils/data/tv.js
Normal file
80
src/utils/data/tv.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { createClient } from "@supabase/supabase-js";
|
||||
import { CACHE_DURATION } from "@utils/constants/index.js";
|
||||
|
||||
const SUPABASE_URL = import.meta.env.SUPABASE_URL;
|
||||
const SUPABASE_KEY = import.meta.env.SUPABASE_KEY;
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
let cachedShows = null;
|
||||
let lastFetchTime = 0;
|
||||
|
||||
export const fetchShows = async () => {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedShows && now - lastFetchTime < CACHE_DURATION) return cachedShows;
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
let shows = [];
|
||||
let rangeStart = 0;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { data, error } = await supabase
|
||||
.from("optimized_shows")
|
||||
.select("*")
|
||||
.range(rangeStart, rangeStart + PAGE_SIZE - 1);
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching shows:", error);
|
||||
return (
|
||||
cachedShows || {
|
||||
shows: [],
|
||||
recentlyWatched: [],
|
||||
favorites: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
shows = shows.concat(data);
|
||||
if (data.length < PAGE_SIZE) break;
|
||||
rangeStart += PAGE_SIZE;
|
||||
}
|
||||
|
||||
const watchedShows = shows.filter(
|
||||
(show) => show["last_watched_at"] !== null
|
||||
);
|
||||
const episodes = watchedShows.map((show) => ({
|
||||
title: show["episode"]["title"],
|
||||
year: show["year"],
|
||||
formatted_episode: show["episode"]["formatted_episode"],
|
||||
url: show["episode"]["url"],
|
||||
image: show["episode"]["image"],
|
||||
backdrop: show["episode"]["backdrop"],
|
||||
last_watched_at: show["episode"]["last_watched_at"],
|
||||
grid: show["grid"],
|
||||
type: "tv",
|
||||
}));
|
||||
|
||||
const result = {
|
||||
shows,
|
||||
recentlyWatched: episodes.slice(0, 75),
|
||||
favorites: shows
|
||||
.filter((show) => show.favorite)
|
||||
.sort((a, b) => a.title.localeCompare(b.title)),
|
||||
};
|
||||
|
||||
cachedShows = result;
|
||||
lastFetchTime = now;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error in fetchShows:", error);
|
||||
return (
|
||||
cachedShows || {
|
||||
shows: [],
|
||||
recentlyWatched: [],
|
||||
favorites: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
13
src/utils/getPopularPosts.js
Normal file
13
src/utils/getPopularPosts.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export function getPopularPosts(posts, analytics) {
|
||||
const filteredPosts = posts.filter((post) =>
|
||||
analytics.some((p) => p.page.includes(post.url))
|
||||
);
|
||||
|
||||
const sortedPosts = filteredPosts.sort((a, b) => {
|
||||
const visitors = (page) =>
|
||||
analytics.find((p) => p.page.includes(page.url))?.visitors || 0;
|
||||
return visitors(b) - visitors(a);
|
||||
});
|
||||
|
||||
return sortedPosts;
|
||||
}
|
111
src/utils/helpers/general.js
Normal file
111
src/utils/helpers/general.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
import { convert } from "html-to-text";
|
||||
import { format } from "date-fns-tz";
|
||||
import markdownIt from "markdown-it";
|
||||
import markdownItAnchor from "markdown-it-anchor";
|
||||
import markdownItFootnote from "markdown-it-footnote";
|
||||
import hljs from "highlight.js";
|
||||
import "highlight.js/styles/github-dark.min.css";
|
||||
import truncateHtml from "truncate-html";
|
||||
|
||||
// arrays
|
||||
export const shuffleArray = (array) => {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
};
|
||||
|
||||
// countries
|
||||
const regionNames = new Intl.DisplayNames(["en"], { type: "region" });
|
||||
|
||||
export const getCountryName = (countryCode) => {
|
||||
try {
|
||||
return regionNames.of(countryCode.trim()) || countryCode.trim();
|
||||
} catch {
|
||||
return countryCode.trim();
|
||||
}
|
||||
};
|
||||
|
||||
export const parseCountryField = (countryField) => {
|
||||
if (!countryField) return null;
|
||||
|
||||
const delimiters = [",", "/", "&", "and"];
|
||||
return delimiters
|
||||
.reduce(
|
||||
(countries, delimiter) =>
|
||||
countries.flatMap((country) => country.split(delimiter)),
|
||||
[countryField]
|
||||
)
|
||||
.map(getCountryName)
|
||||
.join(", ");
|
||||
};
|
||||
|
||||
// html
|
||||
export const htmlTruncate = (content, limit = 50) =>
|
||||
truncateHtml(content, limit, {
|
||||
byWords: true,
|
||||
ellipsis: "...",
|
||||
});
|
||||
|
||||
export const htmlToText = (html) =>
|
||||
convert(html, {
|
||||
wordwrap: false,
|
||||
selectors: [
|
||||
{ selector: "a", options: { ignoreHref: true } },
|
||||
{ selector: "h1", options: { uppercase: false } },
|
||||
{ selector: "h2", options: { uppercase: false } },
|
||||
{ selector: "h3", options: { uppercase: false } },
|
||||
{ selector: "*", format: "block" },
|
||||
],
|
||||
});
|
||||
|
||||
export const escapeHtml = (input) =>
|
||||
typeof input === "string"
|
||||
? input.replace(/[&<>"']/g, (char) => {
|
||||
const map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return map[char];
|
||||
})
|
||||
: "";
|
||||
|
||||
// markdown
|
||||
const markdown = markdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
breaks: true,
|
||||
highlight: (code, lang) => {
|
||||
if (lang && hljs.getLanguage(lang))
|
||||
return hljs.highlight(code, { language: lang }).value;
|
||||
return hljs.highlightAuto(code).value;
|
||||
},
|
||||
});
|
||||
markdown
|
||||
.use(markdownItAnchor, {
|
||||
level: [1, 2],
|
||||
permalink: markdownItAnchor.permalink.headerLink({
|
||||
safariReaderFix: true,
|
||||
}),
|
||||
})
|
||||
.use(markdownItFootnote);
|
||||
|
||||
export const md = (string) => markdown.render(string);
|
||||
|
||||
// urls
|
||||
export const encodeAmp = (url) => url.replace(/&/g, "&");
|
||||
export const removeTrailingSlash = (url) => url.replace(/\/$/, "");
|
||||
|
||||
export const isExcludedPath = (path, exclusions) =>
|
||||
exclusions.some((exclusion) => path.includes(exclusion));
|
||||
|
||||
// dates
|
||||
export const dateToRFC822 = (date) =>
|
||||
format(date, "EEE, dd MMM yyyy HH:mm:ss XXX", {
|
||||
timeZone: "America/Los_Angeles",
|
||||
});
|
73
src/utils/helpers/media.js
Normal file
73
src/utils/helpers/media.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import countries from "i18n-iso-countries";
|
||||
import enLocale from "i18n-iso-countries/langs/en.json";
|
||||
|
||||
countries.registerLocale(enLocale);
|
||||
|
||||
export const filterBooksByStatus = (books, status) =>
|
||||
books.filter((book) => book["status"] === status);
|
||||
|
||||
export const findFavoriteBooks = (books) =>
|
||||
books.filter((book) => book["favorite"] === true);
|
||||
|
||||
export const bookYearLinks = (years) =>
|
||||
years
|
||||
.sort((a, b) => b["value"] - a["value"])
|
||||
.map(
|
||||
(year, index) =>
|
||||
`<a href="/books/years/${year["value"]}">${year["value"]}</a>${
|
||||
index < years.length - 1 ? " / " : ""
|
||||
}`
|
||||
)
|
||||
.join("");
|
||||
|
||||
export const mediaLinks = (data, type, count = 10) => {
|
||||
if (!data || !type) return "";
|
||||
|
||||
const dataSlice = data.slice(0, count);
|
||||
if (dataSlice.length === 0) return null;
|
||||
|
||||
const buildLink = (item) => {
|
||||
switch (type) {
|
||||
case "genre":
|
||||
return `<a href="${item["genre_url"]}">${item["genre_name"]}</a>`;
|
||||
case "artist":
|
||||
return `<a href="${item["url"]}">${item["name"]}</a>`;
|
||||
case "book":
|
||||
return `<a href="${item["url"]}">${item["title"]}</a>`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
if (dataSlice.length === 1) return buildLink(dataSlice[0]);
|
||||
|
||||
const links = dataSlice.map(buildLink);
|
||||
const allButLast = links.slice(0, -1).join(", ");
|
||||
const last = links[links.length - 1];
|
||||
|
||||
return `${allButLast} and ${last}`;
|
||||
};
|
||||
|
||||
export const parseCountries = (input) => {
|
||||
if (!input) return null;
|
||||
|
||||
const countryCodes = input
|
||||
.split(/\s+and\s+|,\s*|\s+/)
|
||||
.map((code) => code.trim().toUpperCase())
|
||||
.filter((code) => code.length > 0);
|
||||
|
||||
const countryNames = countryCodes.map((code) => {
|
||||
const countryName = countries.getName(code, "en");
|
||||
|
||||
if (!countryName) {
|
||||
console.warn(`Country code "${code}" is not valid.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return countryName;
|
||||
});
|
||||
|
||||
return countryNames.filter((name) => name !== null).join(", ");
|
||||
};
|
||||
|
||||
export const isbnRegex = /^\/books\/(\d{10}|\d{13})$/;
|
Reference in a new issue