diff --git a/bun.lockb b/bun.lockb index 02d6823..19063e0 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 81d267d..df7af74 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,7 @@ "dependencies": { "@hookform/resolvers": "^3.3.1", "@t3-oss/env-nextjs": "^0.9.2", - "@upstash/redis": "^1.29.0", "next": "^14.1.3", - "node-cache": "^5.1.2", "podcast": "git+https://github.com/trevorsharp/node-podcast-edge.git#5ea1727", "react-dom": "^18.2.0", "react-hook-form": "^7.46.1", diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts deleted file mode 100644 index 94cf0cb..0000000 --- a/src/services/cacheService.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Redis } from "@upstash/redis"; -import NodeCache from "node-cache"; -import { env } from "~/env"; - -const redis = - env.UPSTASH_REDIS_REST_URL && env.UPSTASH_REDIS_REST_TOKEN - ? new Redis({ - url: env.UPSTASH_REDIS_REST_URL, - token: env.UPSTASH_REDIS_REST_TOKEN, - }) - : undefined; - -const cache = new NodeCache({ checkperiod: 120 }); - -const get = async (key: string) => { - if (redis) { - return await redis.get(key); - } - - return cache.get(key); -}; - -const set = async (key: string, value: T, ttl: number) => { - if (redis) { - return (await redis.set(key, value, { ex: ttl })) !== null; - } - - return cache.set(key, value, ttl); -}; - -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -const withCache = - Promise>( - { cacheKey, ttl }: { cacheKey: string; ttl: number }, - func: TFunc, - ) => - async (...args: Parameters): Promise>> => { - const fullCacheKey = `${cacheKey}${args.length > 0 ? "-" : ""}${args.join("-")}`; - const cacheResult = await get>>(fullCacheKey); - - if (cacheResult) return cacheResult; - - const result = await func(...args); - - if (result) await set>>(fullCacheKey, result, ttl); - - return result; - }; - -export { get, set, withCache }; diff --git a/src/services/twitchService.ts b/src/services/twitchService.ts index 11e27a0..6057b3d 100644 --- a/src/services/twitchService.ts +++ b/src/services/twitchService.ts @@ -1,5 +1,4 @@ import { env } from "~/env"; -import { withCache } from "./cacheService"; import type { User, Video } from "~/types"; type DataWrapper = { data?: T }; @@ -42,79 +41,76 @@ const getUserData = async (rawUsername: string) => { return user; }; -const getRawUserData = withCache( - { cacheKey: "twitch-api-raw-user-data", ttl: 3 * 24 * 60 * 60 }, - async (username: string) => { - const data = await getTwitch>( - `https://api.twitch.tv/helix/users?login=${username}`, - ); - - if (!data?.data || data.data.length === 0) - throw `Sorry, we could not find the user "${username}" 🙁`; - - const rawUserData = data.data[0]; - - if (!rawUserData) throw `Sorry, we could not find the user "${username}" 🙁`; - - return rawUserData; - }, -); - -const getVideos = withCache( - { cacheKey: "twitch-api-user-videos", ttl: 10 * 60 }, - async (userId: string) => { - const data = await getTwitch>( - `https://api.twitch.tv/helix/videos?user_id=${userId}`, - ); - - if (!data?.data || data.data.length === 0) return []; - - const currentStreamData = await getTwitch>( - `https://api.twitch.tv/helix/streams?user_id=${userId}`, - ); - - const currentStream = - currentStreamData?.data && currentStreamData.data.length > 0 - ? currentStreamData.data[0] - : undefined; - - const videos = data.data - .filter((rawVideo) => rawVideo.type === "upload" || rawVideo.type === "archive") - .map((rawVideo) => { - const video: Video = { - id: rawVideo.id, - title: rawVideo.title, - date: rawVideo.published_at, - url: `https://twitch.tv/videos/${rawVideo.id}`, - duration: - currentStream && - Math.abs( - new Date(currentStream.started_at).getTime() - - new Date(rawVideo.published_at).getTime(), - ) < 900000 - ? undefined - : getDuration(rawVideo.duration), - }; - - return video; - }); - - return videos; - }, -); - -const getTwitchToken = withCache({ cacheKey: "twitch-api-token", ttl: 2 * 60 * 60 }, async () => { +const getRawUserData = async (username: string) => { + const data = await getTwitch>( + `https://api.twitch.tv/helix/users?login=${username}`, + { ttl: 3 * 24 * 60 * 60 }, + ); + + if (!data?.data || data.data.length === 0) + throw `Sorry, we could not find the user "${username}" 🙁`; + + const rawUserData = data.data[0]; + + if (!rawUserData) throw `Sorry, we could not find the user "${username}" 🙁`; + + return rawUserData; +}; + +const getVideos = async (userId: string) => { + const data = await getTwitch>( + `https://api.twitch.tv/helix/videos?user_id=${userId}`, + { ttl: 10 * 60 }, + ); + + if (!data?.data || data.data.length === 0) return []; + + const currentStreamData = await getTwitch>( + `https://api.twitch.tv/helix/streams?user_id=${userId}`, + { ttl: 10 * 60 }, + ); + + const currentStream = + currentStreamData?.data && currentStreamData.data.length > 0 + ? currentStreamData.data[0] + : undefined; + + const videos = data.data + .filter((rawVideo) => rawVideo.type === "upload" || rawVideo.type === "archive") + .map((rawVideo) => { + const video: Video = { + id: rawVideo.id, + title: rawVideo.title, + date: rawVideo.published_at, + url: `https://twitch.tv/videos/${rawVideo.id}`, + duration: + currentStream && + Math.abs( + new Date(currentStream.started_at).getTime() - + new Date(rawVideo.published_at).getTime(), + ) < 900000 + ? undefined + : getDuration(rawVideo.duration), + }; + + return video; + }); + + return videos; +}; + +const getTwitchToken = async () => { const data = await fetch( `https://id.twitch.tv/oauth2/token?client_id=${env.TWITCH_API_CLIENT_ID}&client_secret=${env.TWITCH_API_SECRET}&grant_type=client_credentials`, - { method: "POST", cache: "no-store" }, + { method: "POST", next: { revalidate: 2 * 60 * 60 } }, ).then((response) => response.json() as Promise<{ access_token?: string; expires_in?: number }>); if (!data?.access_token || !data?.expires_in) throw "Could not get Twith API token"; return data.access_token; -}); +}; -const getTwitch = async (url: string) => { +const getTwitch = async (url: string, options?: { ttl?: number }) => { const token = await getTwitchToken(); const headers = { @@ -122,7 +118,7 @@ const getTwitch = async (url: string) => { "Client-Id": env.TWITCH_API_CLIENT_ID, }; - const data = await fetch(url, { headers, cache: "no-store" }).then( + const data = await fetch(url, { headers, next: { revalidate: options?.ttl ?? 0 } }).then( (response) => response.json() as Promise, ); diff --git a/src/services/videoService.ts b/src/services/videoService.ts index 7a16fad..eae4707 100644 --- a/src/services/videoService.ts +++ b/src/services/videoService.ts @@ -1,17 +1,13 @@ import { Quality } from "~/types"; -import { withCache } from "./cacheService"; import { getVodPlaylist } from "./m3u8Service"; -const getStream = withCache( - { cacheKey: "playlist-data", ttl: 5 * 60 }, - async (videoId: string, quality: Quality) => { - const playlistData = await getPlaylistData(videoId, quality); +const getStream = async (videoId: string, quality: Quality) => { + const playlistData = await getPlaylistData(videoId, quality); - if (playlistData === "") throw `Video not found with id ${videoId}`; + if (playlistData === "") throw `Video not found with id ${videoId}`; - return playlistData; - }, -); + return playlistData; +}; const getPlaylistData = async (videoId: string, quality: Quality) => { const [rawPlaylist, playlistData] = await getVodPlaylist(videoId); @@ -35,7 +31,7 @@ const getPlaylistData = async (videoId: string, quality: Quality) => { break; } - const response = await fetch(playlistUrl, { cache: "no-store" }); + const response = await fetch(playlistUrl, { next: { revalidate: 5 * 60 } }); if (response.status !== 200) throw "Failed to fetch content stream"; const baseStreamUrl = playlistUrl.replace(/index[^\.]*\.m3u8/i, "");