Create a service for interacting with cached read positions

This commit is contained in:
Jackson Harper 2024-01-31 10:17:51 +08:00
parent fbfa934479
commit a40f5bed55
2 changed files with 118 additions and 26 deletions

View file

@ -1,17 +1,14 @@
import { redisDataSource } from '../redis_data_source'
type ReadingProgressCacheItem = {
readingProgressPercent: number
readingProgressTopPercent: number | undefined
readingProgressAnchorIndex: number | undefined
updatedAt: string
}
import {
ReadingProgressCacheItem,
fetchCachedReadingPosition,
keyForCachedReadingPosition,
pushCachedReadingPosition,
} from '../services/cached_reading_position'
export class ReadingProgressDataSource {
private cacheItems: { [id: string]: ReadingProgressCacheItem } = {}
constructor() {}
async getReadingProgress(
uid: string,
libraryItemID: string
@ -21,7 +18,7 @@ export class ReadingProgressDataSource {
if (cached) {
return cached
}
return this.valueFromRedis(cacheKey)
return fetchCachedReadingPosition(uid, libraryItemID)
}
async updateReadingProgress(
@ -33,11 +30,14 @@ export class ReadingProgressDataSource {
readingProgressAnchorIndex: number | undefined
}
): Promise<void> {
const cacheKey = `omnivore:reading-progress:${uid}:${libraryItemID}`
const cacheItem: ReadingProgressCacheItem = {
...progress,
uid,
libraryItemID,
updatedAt: new Date().toISOString(),
...progress,
}
const cacheKey = keyForCachedReadingPosition(uid, libraryItemID)
pushCachedReadingPosition(uid, libraryItemID, cacheItem)
this.cacheItems[cacheKey] = cacheItem
if (
@ -51,18 +51,4 @@ export class ReadingProgressDataSource {
console.log('failed to cache reading progress')
}
}
async valueFromRedis(
cacheKey: string
): Promise<ReadingProgressCacheItem | undefined> {
const redisCached = await redisDataSource.redisClient?.lrange(
cacheKey,
0,
0
)
if (redisCached && redisCached.length > 0) {
return JSON.parse(redisCached[0])
}
return undefined
}
}

View file

@ -0,0 +1,106 @@
import { redisDataSource } from '../redis_data_source'
import { logger } from '../utils/logger'
export type ReadingProgressCacheItem = {
uid: string
libraryItemID: string
readingProgressPercent: number
readingProgressTopPercent: number | undefined
readingProgressAnchorIndex: number | undefined
updatedAt: string | undefined
}
export const keyForCachedReadingPosition = (
uid: string,
libraryItemID: string
): string => {
return `omnivore:reading-progress:${uid}:${libraryItemID}`
}
// Reading positions are cached as an array of positions, when
// we fetch them from the cache we find the maximum values
export const clearCachedReadingPosition = async (
uid: string,
libraryItemID: string
): Promise<boolean> => {
const cacheKey = keyForCachedReadingPosition(uid, libraryItemID)
try {
const res = await redisDataSource.redisClient?.del(cacheKey)
return res ? res > 0 : false
} catch (error) {
logger.error('exception clearing cached reading position', {
cacheKey,
error,
})
}
return false
}
export const pushCachedReadingPosition = async (
uid: string,
libraryItemID: string,
position: ReadingProgressCacheItem
): Promise<boolean> => {
const cacheKey = keyForCachedReadingPosition(uid, libraryItemID)
try {
const result = await redisDataSource.redisClient?.lpush(
cacheKey,
JSON.stringify(position)
)
return result ? result > 0 : false
} catch (error) {
logger.error('error writing cached reading position', { cacheKey, error })
}
return false
}
// Reading positions are cached as an array of positions, when
// we fetch them from the cache we find the maximum values
export const fetchCachedReadingPosition = async (
uid: string,
libraryItemID: string
): Promise<ReadingProgressCacheItem | undefined> => {
const cacheKey = keyForCachedReadingPosition(uid, libraryItemID)
try {
const cacheItemList = await redisDataSource.redisClient?.lrange(
cacheKey,
0,
-1
)
const items = cacheItemList?.map((item) => JSON.parse(item))
if (!items || items.length < 1) {
return undefined
}
const percent = Math.max(
...items.map((o) =>
'readingProgressPercent' in o ? o.readingProgressPercent : 0
)
)
const top = Math.max(
...items.map((o) =>
'readingProgressTopPercent' in o ? o.readingProgressTopPercent : 0
)
)
const anchor = Math.max(
...items.map((o) =>
'readingProgressAnchorIndex' in o ? o.readingProgressAnchorIndex : 0
)
)
return {
uid,
libraryItemID,
readingProgressPercent: percent,
readingProgressTopPercent: top,
readingProgressAnchorIndex: anchor,
updatedAt: undefined,
}
} catch (error) {
logger.error('exception looking up cached reading position', {
cacheKey,
error,
})
}
return undefined
}