Add caching to PSN and Steam API endpoints
This commit is contained in:
parent
1bde04c2f7
commit
e98532daaf
2 changed files with 88 additions and 36 deletions
|
|
@ -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']
|
||||
// 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' }
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
)
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue