Add caching to PSN and Steam API endpoints

This commit is contained in:
Justin Edmund 2024-08-05 23:38:31 -07:00
parent 1bde04c2f7
commit e98532daaf
2 changed files with 88 additions and 36 deletions

View file

@ -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<AuthTokensResponse> {
const authorization = await exchangeCodeForAccessToken(accessCode)
return authorization
}
async function getSerializedGames(psnId: string): Promise<SerializableGameInfo[]> {
// 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
}

View file

@ -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<GameInfoExtended> => '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<SerializableGameInfo[]> {
// 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<GameInfoExtended> => '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
}