diff --git a/src/routes/api/psn/+server.ts b/src/routes/api/psn/+server.ts index 1bfddd9..7f7d3ab 100644 --- a/src/routes/api/psn/+server.ts +++ b/src/routes/api/psn/+server.ts @@ -1,34 +1,38 @@ import 'dotenv/config' -import type { AuthTokensResponse, RecentlyPlayedGamesResponse } from 'psn-api' +import Module from 'node:module' +import redis from '../redis-client' + +import type { + AuthTokensResponse, + GetUserPlayedTimeResponse, + RecentlyPlayedGamesResponse +} from 'psn-api' import type { RequestHandler } from './$types' -import Module from 'node:module' const require = Module.createRequire(import.meta.url) const { exchangeNpssoForCode, exchangeCodeForAccessToken, - getRecentlyPlayedGames + getRecentlyPlayedGames, + getUserPlayedTime } = require('psn-api') +const CACHE_TTL = 60 * 60 * 24 const PSN_NPSSO_TOKEN = process.env.PSN_NPSSO_TOKEN +const PSN_ID = '1275018559140296533' export const GET: RequestHandler = async ({ url }) => { - let authorization = await authorize(PSN_NPSSO_TOKEN || '') - - const response: RecentlyPlayedGamesResponse = await getRecentlyPlayedGames(authorization, { - limit: 5, - categories: ['ps4_game', 'ps5_native_game'] - }) - - const games: SerializableGameInfo[] = response.data.gameLibraryTitlesRetrieve.games.map( - (game) => ({ - id: game.productId, - name: game.name, - playtime: undefined, - lastPlayed: new Date(game.lastPlayedDateTime), - coverURL: game.image.url + // Check if data is in cache + const cachedData = await redis.get(`psn:${PSN_ID}`) + if (cachedData) { + console.log('Using cached PSN data') + return new Response(cachedData, { + headers: { 'Content-Type': 'application/json' } }) - ) + } + + // If not in cache, fetch and cache the data + const games = await getSerializedGames(PSN_ID) return new Response(JSON.stringify(games), { headers: { 'Content-Type': 'application/json' } @@ -40,3 +44,25 @@ async function authorize(npsso: string): Promise { const authorization = await exchangeCodeForAccessToken(accessCode) return authorization } + +async function getSerializedGames(psnId: string): Promise { + // Authorize with PSN and get games sorted by last played time + let authorization = await authorize(PSN_NPSSO_TOKEN || '') + const response = await getUserPlayedTime(authorization, PSN_ID, { + limit: 5, + categories: ['ps4_game', 'ps5_native_game'] + }) + + // Map the games to a serializable format that the frontend understands. + const games: SerializableGameInfo[] = response.titles.map((game: GetUserPlayedTimeResponse) => ({ + id: game.concept.id, + name: game.name, + playtime: game.playDuration, + lastPlayed: game.lastPlayedDateTime, + coverURL: game.imageUrl, + platform: 'psn' + })) + + await redis.setex(`psn:${PSN_ID}`, CACHE_TTL, JSON.stringify(games)) + return games +} diff --git a/src/routes/api/steam/+server.ts b/src/routes/api/steam/+server.ts index b178d07..36c954b 100644 --- a/src/routes/api/steam/+server.ts +++ b/src/routes/api/steam/+server.ts @@ -1,31 +1,30 @@ import { error, json } from '@sveltejs/kit' -import type { RequestHandler } from './$types' +import redis from '../redis-client' import SteamAPI, { Game, GameInfo, GameInfoExtended, UserPlaytime } from 'steamapi' +import type { RequestHandler } from './$types' + +const CACHE_TTL = 60 * 60 * 24 // 24 hours in seconds +const STEAM_ID = '76561197997279808' + export const GET: RequestHandler = async ({ params }) => { - const steam = new SteamAPI(process.env.STEAM_API_KEY || '') try { - const steamId = '76561197997279808' + // Check if data is in cache + const cachedData = await redis.get(`steam:${STEAM_ID}`) + if (cachedData) { + console.log('Using cached Steam data') + return new Response(cachedData, { + headers: { 'Content-Type': 'application/json' } + }) + } - const ownedGames = await steam.getUserOwnedGames(steamId, { includeExtendedAppInfo: true }) + // If not in cache, fetch and cache the data + const games = await getSerializedGames(STEAM_ID) - const sortedGames = sortUserPlaytimes(ownedGames).slice(0, 5) - const extendedGames = sortedGames.filter( - (game): game is UserPlaytime => 'coverURL' in game.game - ) - const serializableGames: SerializableGameInfo[] = extendedGames.map((game) => ({ - id: game.game.id, - name: game.game.name, - playtime: game.minutes, - lastPlayed: game.lastPlayedAt, - coverURL: game.game.coverURL - })) - - return new Response(JSON.stringify(serializableGames), { + return new Response(JSON.stringify(games), { headers: { 'Content-Type': 'application/json' } }) } catch (err) { - console.log('Catching here') console.error('Error fetching recent game:', err) throw error(500, 'Error fetching recent game data') } @@ -51,3 +50,30 @@ function sortUserPlaytimes( return b.minutes - a.minutes }) } + +async function getSerializedGames(steamId: string): Promise { + // Fetch all owned games from Steam + // This is necessary because the recently played API only returns games played in the last 14 days. + const steam = new SteamAPI(process.env.STEAM_API_KEY || '') + const steamGames = await steam.getUserOwnedGames(steamId, { includeExtendedAppInfo: true }) + + // Sort games based on when they were last played and take the first five games. + // Then, ensure that we use the getter method to fetch the cover URL. + const sortedGames = sortUserPlaytimes(steamGames).slice(0, 5) + const extendedGames = sortedGames.filter( + (game): game is UserPlaytime => 'coverURL' in game.game + ) + + // Map the games to a serializable format that the frontend understands. + let games: SerializableGameInfo[] = extendedGames.map((game) => ({ + id: game.game.id, + name: game.game.name, + playtime: game.minutes, + lastPlayed: game.lastPlayedAt, + coverURL: game.game.coverURL, + platform: 'steam' + })) + + await redis.setex(`steam:${STEAM_ID}`, CACHE_TTL, JSON.stringify(games)) + return games +}