Skip to content

Commit

Permalink
feat(client, imgur): adds caching for calls to imgur.getAlbum to impr…
Browse files Browse the repository at this point in the history
…ove performance
  • Loading branch information
KenEucker committed Nov 1, 2023
1 parent 9c81f6c commit 0b74002
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 67 deletions.
4 changes: 2 additions & 2 deletions examples/node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ const get10SettingsAsync = async (pre, client, out = false, opts = {}) => {
}

const runTests = async (out = false) => {
if (biketagDefaultInstance) {
if (false) {
console.log(pretty("Default BikeTag Client Instantiated"), biketagDefaultInstanceOpts)
await getGameAsync("BikeTag", biketagDefaultInstance, out)
await getTag1Async("BikeTag", biketagDefaultInstance, out)
Expand All @@ -167,7 +167,7 @@ const runTests = async (out = false) => {
await get10PlayersAsync("Imgur", bikeTagImgurInstance, out)
}

if (bikeTagSanityInstance) {
if (false) {
console.log(pretty("Sanity BikeTag Client Instantiated"), sanityInstanceOpts)
// await getTag1Async("Sanity", bikeTagSanityInstance, out)
// await get10TagsAsync("Sanity", bikeTagSanityInstance, out)
Expand Down
40 changes: 26 additions & 14 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ import { EventEmitter } from 'events'
import { setup } from 'axios-cache-adapter'
import { isEqual } from 'lodash'
import { getAuthorizationHeader, getClaims } from './common/auth'
import TinyCache from 'tinycache'

const apiCache = new TinyCache()

// export const USERAGENT =
// 'biketag-api (https://github.com/keneucker/biketag-api)'
Expand Down Expand Up @@ -109,16 +112,18 @@ export class BikeTagClient extends EventEmitter {
responseType,
})

const authenticationInterceptor = async (config) => {
config.headers = config.headers ? config.headers : {}
config.headers.authorization = await getAuthorizationHeader(this)
return config
}

this.fetcher = axios.create({
headers,
responseType,
})
this.fetcher.interceptors.request.use(
async (config: AxiosRequestConfig) => {
config.headers = config.headers ? config.headers : {}
config.headers.authorization = await getAuthorizationHeader(this)
return config
},
authenticationInterceptor,
(e: Error) => Promise.reject(e)
)

Expand All @@ -139,6 +144,10 @@ export class BikeTagClient extends EventEmitter {
headers,
responseType,
})
this.cachedFetcher.interceptors.request.use(
authenticationInterceptor,
(e: Error) => Promise.reject(e)
)
}

/// **************************** protected Class Methods ******************************** ///
Expand Down Expand Up @@ -232,7 +241,9 @@ export class BikeTagClient extends EventEmitter {
options.tagnumber = options.tagnumbers[0]
} else if (options.slug && options.slug !== 'current') {
options.tagnumber = BikeTagGetters.getTagnumberFromSlug(
options.slug
options.slug,
undefined,
apiCache
)
}
}
Expand Down Expand Up @@ -529,7 +540,7 @@ export class BikeTagClient extends EventEmitter {
const clientMethod = api.getGame

if (clientMethod) {
return clientMethod(client, options)
return clientMethod(client, options, apiCache)
.then((retrievedGameResponse) => {
if (retrievedGameResponse.success && retrievedGameResponse.data) {
/// Set the most important game data (hash, etc)
Expand Down Expand Up @@ -585,6 +596,7 @@ export class BikeTagClient extends EventEmitter {
}

return clientMethod(client, options).catch((e) => {
/// TODO: invalidate cache
return Promise.resolve({
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -639,7 +651,7 @@ export class BikeTagClient extends EventEmitter {
break
}

return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return {
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -742,7 +754,7 @@ export class BikeTagClient extends EventEmitter {

// }

return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return {
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -785,7 +797,7 @@ export class BikeTagClient extends EventEmitter {
// break
// }

return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return {
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -993,7 +1005,7 @@ export class BikeTagClient extends EventEmitter {

/// If the client adapter implements a direct way to retrieve a single player
if (clientMethod) {
return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return {
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -1039,7 +1051,7 @@ export class BikeTagClient extends EventEmitter {
break
}

return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return Promise.resolve({
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -1087,7 +1099,7 @@ export class BikeTagClient extends EventEmitter {

/// If the client adapter implements a direct way to retrieve a single ambassador
if (clientMethod) {
return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return {
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down Expand Up @@ -1137,7 +1149,7 @@ export class BikeTagClient extends EventEmitter {
break
}

return clientMethod(client, options).catch((e) => {
return clientMethod(client, options, apiCache).catch((e) => {
return Promise.resolve({
status: HttpStatusCode.InternalServerError,
data: null,
Expand Down
3 changes: 2 additions & 1 deletion src/common/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { Tag, Game, Player, Ambassador, Setting } from './schema'
export const cacheKeys = {
sanityUrlText: `sanity::`,
imageHashText: `hash::`,
albumHash: `imgur::`,
albumHash: `album::`,
bikeTagImage: `biketag::`,
bikeTagsByUser: `userTags::`,
hintText: `hint::`,
playerText: `player::`,
playerData: `playerData::`,
playerIdText: `playerId::`,
gameIdText: `gameId::`,
gameSlugText: `slug::`,
gameText: `game::`,
locationText: `location::`,
Expand Down
17 changes: 5 additions & 12 deletions src/common/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,16 +452,13 @@ export const getImgurFoundImageHashFromBikeTagData = (

export const getImgurFoundDescriptionFromBikeTagData = (
tag: Tag,
includeCredit = true,
cache?: typeof TinyCache
includeCredit = true
): string =>
`#${tag.tagnumber} proof${
tag.foundLocation ? ` found at (${tag.foundLocation})` : ''
}${includeCredit ? ` by ${tag.foundPlayer}` : ''}`
export const getImgurFoundTitleFromBikeTagData = (
tag: Tag,
cache?: typeof TinyCache
): string =>

export const getImgurFoundTitleFromBikeTagData = (tag: Tag): string =>
`${
!tag.gps || (tag.gps.lat === 0 && tag.gps.long === 0)
? ''
Expand All @@ -478,10 +475,7 @@ export const getImgurMysteryImageHashFromBikeTagData = (
): string => {
return getImageHashFromText(tag.mysteryImageUrl, cache)
}
export const getImgurMysteryTitleFromBikeTagData = (
tag: Tag,
cache?: typeof TinyCache
): string =>
export const getImgurMysteryTitleFromBikeTagData = (tag: Tag): string =>
`${
!tag.gps || (tag.gps.lat === 0 && tag.gps.long === 0)
? ''
Expand All @@ -491,8 +485,7 @@ export const getImgurMysteryTitleFromBikeTagData = (
export const getImgurMysteryDescriptionFromBikeTagData = (
tag: Tag,
includeCredit = true,
includeHint = true,
cache?: typeof TinyCache
includeHint = true
): string =>
`#${tag.tagnumber} tag ${
includeHint && tag.hint ? `(hint: ${tag.hint})` : ''
Expand Down
20 changes: 20 additions & 0 deletions src/common/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import FormData from 'form-data'
import TinyCache from 'tinycache'
import { Tag, Game, Player, Ambassador, Setting } from './schema'
import { ApiAvailability } from './enums'
import { cacheKeys } from './data'

export const putCacheIfExists = (
key: string,
Expand Down Expand Up @@ -442,3 +443,22 @@ export const sortSettings = (

return limit !== 0 ? sorted.slice(0, limit) : sorted
}

export const getGameAlbumFromCache = async (
gameAlbumHash: string,
cache?: typeof TinyCache,
fallback?: any
) => {
const cacheKey = `imgur::${cacheKeys.albumHash}${gameAlbumHash}`
const existsInCache = getCacheIfExists(cacheKey, cache)
if (existsInCache) {
return existsInCache
}

if (fallback) {
const putIntoCache = await fallback()
putCacheIfExists(cacheKey, putIntoCache, cache)

return putIntoCache
}
}
9 changes: 5 additions & 4 deletions src/imgur/archiveTag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function archiveTag(
let data
let error
let success = true
let mysteryTagDeleteResponse
// let mysteryTagDeleteResponse
let archiveFoundImageResponse
let deleteFoundImageResponse
let existingFoundImageResponse
Expand Down Expand Up @@ -52,9 +52,10 @@ export async function archiveTag(
payload as Tag
)
const existingMysteryImageResponse = await client.getImage(mysteryImageHash)
mysteryTagDeleteResponse = existingMysteryImageResponse.success
? await client.deleteImage(existingMysteryImageResponse.data.deletehash)
: { success: false, data: 'delete of existing mystery image failed' }
if (existingMysteryImageResponse.success) {
await client.deleteImage(existingMysteryImageResponse.data.deletehash)
}
// : { success: false, data: 'delete of existing mystery image failed' }
}

if (archiveFoundImageResponse?.success && deleteFoundImageResponse?.success) {
Expand Down
40 changes: 35 additions & 5 deletions src/imgur/getGame.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import type { ImgurClient } from 'imgur'
import { createGameObject } from '../common/data'
import { createGameObject, cacheKeys } from '../common/data'
import { getGamePayload } from '../common/payloads'
import { BikeTagApiResponse } from '../common/types'
import { Game } from '../common/schema'
import { AvailableApis, HttpStatusCode } from '../common/enums'
import { getGameDataFromText, getGameSlugFromText } from './helpers'
import {
getCacheIfExists,
getGameAlbumFromCache,
putCacheIfExists,
} from '../common/methods'
import TinyCache from 'tinycache'

export async function getGame(
client: ImgurClient,
payload: getGamePayload
payload: getGamePayload,
cache?: typeof TinyCache
): Promise<BikeTagApiResponse<Game>> {
let game
const cacheKey = `imgur::${cacheKeys.gameIdText}${
payload.slug ?? payload.name ?? 'biketag'
}`
let game = getCacheIfExists(cacheKey, cache)
let error
let success = true

if (client) {
if (game) {
/// First do nothing
} else if (client) {
const hashes = payload.hash ? [payload.hash] : []
if (!hashes.length) {
let stillSearching = true
Expand Down Expand Up @@ -42,7 +54,9 @@ export async function getGame(
}

for (const hash of hashes) {
const albumInfo = await client.getAlbum(hash)
const albumInfo = await getGameAlbumFromCache(hash, cache, () =>
client.getAlbum(hash)
)

if (albumInfo.data?.images?.length > 0) {
/// TODO: save all game settings into the title of the image (serialized)
Expand All @@ -58,6 +72,22 @@ export async function getGame(

if (games.length) {
game = createGameObject(games[0])
} else if (
albumInfo.data.title
.toLocaleLowerCase()
.indexOf(`${payload.slug} biketag`) !== -1 ||
albumInfo.data.title
.toLocaleLowerCase()
.indexOf(`${payload.slug} bike tag`) !== -1
) {
game = createGameObject({
name: payload.slug,
mainhash: albumInfo.data.id,
})
}

if (game) {
putCacheIfExists(cacheKey, game, cache)
break
}
} else if (!albumInfo.success) {
Expand Down
11 changes: 7 additions & 4 deletions src/imgur/getPlayers.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import type { ImgurClient } from 'imgur'
import { createPlayerObject, createTagObject } from '../common/data'
import { sortPlayers } from '../common/methods'
import { getGameAlbumFromCache, sortPlayers } from '../common/methods'
import { getPlayersPayload } from '../common/payloads'
import { BikeTagApiResponse } from '../common/types'
import { Player } from '../common/schema'
import { AvailableApis, HttpStatusCode } from '../common/enums'
import { getPlayerDataFromText } from './helpers'
import TinyCache from 'tinycache'

export async function getPlayers(
client: ImgurClient,
payload: getPlayersPayload
payload: getPlayersPayload,
cache?: typeof TinyCache
): Promise<BikeTagApiResponse<Player[]>> {
const playersData: Player[] = []
const playerNames: string[] = []

if (client) {
const { data: tags } = await this.getTags({ sort: 'relevance' })
/// TODO: this better be cached because it's being called twice now
const albumInfo = await (client.getAlbum(payload.hash) as any)
const albumInfo = await getGameAlbumFromCache(payload.hash, cache, () =>
client.getAlbum(payload.hash)
)
const playerImages = albumInfo.data?.images?.reduce((o, i) => {
const player = getPlayerDataFromText(i.description)
if (player) {
Expand Down
13 changes: 9 additions & 4 deletions src/imgur/getQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@ import { BikeTagApiResponse } from '../common/types'
import { Tag } from '../common/schema'
import { getGroupedImagesByTagnumber, getGroupedTagsByPlayer } from './helpers'
import { AvailableApis, HttpStatusCode } from '../common/enums'
import { sortTags } from '../common/methods'
import { getGameAlbumFromCache, sortTags } from '../common/methods'
import TinyCache from 'tinycache'

export async function getQueue(
client: ImgurClient,
payload: getQueuePayload
payload: getQueuePayload,
cache?: typeof TinyCache
): Promise<BikeTagApiResponse<Tag[]>> {
if (!payload.queuehash) {
const game = await this.getGame(payload.game)
if (game.data) {
payload.queuehash = game.data.queuehash
}
}
const albumInfo = await getGameAlbumFromCache(payload.queuehash, cache, () =>
client.getAlbum(payload.queuehash)
)

const albumInfo = await client.getAlbum(payload.queuehash)
const images = getGroupedImagesByTagnumber(albumInfo?.data?.images)
const images = getGroupedImagesByTagnumber(albumInfo?.data?.images, cache)
const queuedTags = getGroupedTagsByPlayer(images, payload)

return {
data: sortTags(queuedTags, 'relevance'),
success: true,
Expand Down
Loading

0 comments on commit 0b74002

Please sign in to comment.