mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Write behing cache for reading progress
This commit is contained in:
parent
7289480ca5
commit
86c80d991f
5 changed files with 172 additions and 15 deletions
|
|
@ -25,6 +25,7 @@ import { tracer } from './tracing'
|
|||
import { getClaimsByToken, setAuthInCookie } from './utils/auth'
|
||||
import { SetClaimsRole } from './utils/dictionary'
|
||||
import { logger } from './utils/logger'
|
||||
import { ReadingProgressDataSource } from './datasources/reading_progress_data_source'
|
||||
|
||||
const signToken = promisify(jwt.sign)
|
||||
const pubsub = createPubSubClient()
|
||||
|
|
@ -84,6 +85,9 @@ const contextFunc: ContextFunction<ExpressContext, ResolverContext> = async ({
|
|||
return cb(tx)
|
||||
}),
|
||||
tracingSpan: tracer.startSpan('apollo.request'),
|
||||
dataSources: {
|
||||
readingProgress: new ReadingProgressDataSource(),
|
||||
},
|
||||
}
|
||||
|
||||
return ctx
|
||||
|
|
|
|||
86
packages/api/src/datasources/reading_progress_data_source.ts
Normal file
86
packages/api/src/datasources/reading_progress_data_source.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { redisDataSource } from '../redis_data_source'
|
||||
|
||||
type ReadingProgressCacheItem = {
|
||||
readingProgressPercent: number
|
||||
readingProgressTopPercent: number | undefined
|
||||
readingProgressAnchorIndex: number | undefined
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export class ReadingProgressDataSource {
|
||||
private cacheItems: { [id: string]: ReadingProgressCacheItem } = {}
|
||||
|
||||
constructor() {}
|
||||
|
||||
async getReadingProgress(
|
||||
libraryItemID: string
|
||||
): Promise<ReadingProgressCacheItem | undefined> {
|
||||
const cacheKey = `omnivore:reading-progress:${libraryItemID}`
|
||||
const cached = this.cacheItems[cacheKey]
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
return this.valueFromRedis(libraryItemID)
|
||||
}
|
||||
|
||||
async updateReadingProgress(
|
||||
libraryItemID: string,
|
||||
progress: {
|
||||
readingProgressPercent: number
|
||||
readingProgressTopPercent: number | undefined | null
|
||||
readingProgressAnchorIndex: number | undefined | null
|
||||
}
|
||||
): Promise<void> {
|
||||
const cacheKey = `omnivore:reading-progress:${libraryItemID}`
|
||||
const existingItem = await this.valueFromRedis(cacheKey)
|
||||
const cacheItem = {
|
||||
readingProgressPercent: Math.max(
|
||||
progress.readingProgressPercent,
|
||||
existingItem?.readingProgressPercent ?? 0
|
||||
),
|
||||
readingProgressTopPercent: Math.max(
|
||||
progress.readingProgressTopPercent ?? 0,
|
||||
existingItem?.readingProgressTopPercent ?? 0
|
||||
),
|
||||
readingProgressAnchorIndex: Math.max(
|
||||
progress.readingProgressAnchorIndex ?? 0,
|
||||
existingItem?.readingProgressAnchorIndex ?? 0
|
||||
),
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
|
||||
this.cacheItems[cacheKey] = cacheItem
|
||||
if (await redisDataSource.redisClient?.hmset(cacheKey, cacheItem)) {
|
||||
console.log('cached reading progress')
|
||||
} else {
|
||||
console.log('failed to cache reading progress')
|
||||
}
|
||||
}
|
||||
|
||||
async valueFromRedis(
|
||||
libraryItemID: string
|
||||
): Promise<ReadingProgressCacheItem | undefined> {
|
||||
const cacheKey = `omnivore:reading-progress:${libraryItemID}`
|
||||
const redisCached = await redisDataSource.redisClient?.hgetall(cacheKey)
|
||||
if (redisCached) {
|
||||
const readingProgressPercent = parseInt(
|
||||
redisCached.readingProgressPercent,
|
||||
10
|
||||
)
|
||||
const updatedAt = new Date(parseInt(redisCached.updatedAt, 10))
|
||||
if (!Number.isNaN(readingProgressPercent) && updatedAt) {
|
||||
return {
|
||||
readingProgressPercent,
|
||||
readingProgressTopPercent: redisCached.readingProgressTopPercent
|
||||
? parseInt(redisCached.readingProgressTopPercent, 10)
|
||||
: undefined,
|
||||
readingProgressAnchorIndex: redisCached.readingProgressAnchorIndex
|
||||
? parseInt(redisCached.readingProgressAnchorIndex, 10)
|
||||
: undefined,
|
||||
updatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
@ -607,7 +607,7 @@ export const saveArticleReadingProgressResolver = authorized<
|
|||
force,
|
||||
},
|
||||
},
|
||||
{ log, pubsub, uid }
|
||||
{ log, pubsub, uid, dataSources }
|
||||
) => {
|
||||
if (
|
||||
readingProgressPercent < 0 ||
|
||||
|
|
@ -640,6 +640,11 @@ export const saveArticleReadingProgressResolver = authorized<
|
|||
}
|
||||
}
|
||||
|
||||
dataSources.readingProgress.updateReadingProgress(id, {
|
||||
readingProgressPercent,
|
||||
readingProgressTopPercent,
|
||||
readingProgressAnchorIndex,
|
||||
})
|
||||
// update reading progress only if the current value is lower
|
||||
const updatedItem = await updateLibraryItemReadingProgress(
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -312,20 +312,6 @@ export const functionResolvers = {
|
|||
publishedAt(article: { publishedAt: Date }) {
|
||||
return validatedDate(article.publishedAt)
|
||||
},
|
||||
// async shareInfo(
|
||||
// article: { id: string; sharedBy?: User; shareInfo?: LinkShareInfo },
|
||||
// __: unknown,
|
||||
// ctx: WithDataSourcesContext
|
||||
// ): Promise<LinkShareInfo | undefined> {
|
||||
// if (article.shareInfo) return article.shareInfo
|
||||
// if (!ctx.claims?.uid) return undefined
|
||||
// return getShareInfoForArticle(
|
||||
// ctx.kx,
|
||||
// ctx.claims?.uid,
|
||||
// article.id,
|
||||
// ctx.models
|
||||
// )
|
||||
// },
|
||||
image(article: { image?: string }): string | undefined {
|
||||
return article.image && createImageProxyUrl(article.image, 320, 320)
|
||||
},
|
||||
|
|
@ -342,6 +328,42 @@ export const functionResolvers = {
|
|||
|
||||
return findLabelsByLibraryItemId(article.id, ctx.uid)
|
||||
},
|
||||
async readingProgressPercent(
|
||||
article: { id: string; readingProgressPercent?: number },
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
const readingProgress =
|
||||
await ctx.dataSources.readingProgress.getReadingProgress(article.id)
|
||||
if (readingProgress) {
|
||||
return readingProgress.readingProgressPercent
|
||||
}
|
||||
return article.readingProgressPercent
|
||||
},
|
||||
async readingProgressAnchorIndex(
|
||||
article: { id: string; readingProgressAnchorIndex?: number },
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
const readingProgress =
|
||||
await ctx.dataSources.readingProgress.getReadingProgress(article.id)
|
||||
if (readingProgress) {
|
||||
return readingProgress.readingProgressAnchorIndex
|
||||
}
|
||||
return article.readingProgressAnchorIndex
|
||||
},
|
||||
async readingProgressTopPercent(
|
||||
article: { id: string; readingProgressTopPercent?: number },
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
const readingProgress =
|
||||
await ctx.dataSources.readingProgress.getReadingProgress(article.id)
|
||||
if (readingProgress) {
|
||||
return readingProgress.readingProgressTopPercent
|
||||
}
|
||||
return article.readingProgressTopPercent
|
||||
},
|
||||
},
|
||||
Highlight: {
|
||||
// async reactions(
|
||||
|
|
@ -447,6 +469,42 @@ export const functionResolvers = {
|
|||
const highlights = await findHighlightsByLibraryItemId(item.id, ctx.uid)
|
||||
return highlights.map(highlightDataToHighlight)
|
||||
},
|
||||
async readingProgressPercent(
|
||||
article: { id: string; readingProgressPercent?: number },
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
const readingProgress =
|
||||
await ctx.dataSources.readingProgress.getReadingProgress(article.id)
|
||||
if (readingProgress) {
|
||||
return readingProgress.readingProgressPercent
|
||||
}
|
||||
return article.readingProgressPercent
|
||||
},
|
||||
async readingProgressAnchorIndex(
|
||||
article: { id: string; readingProgressAnchorIndex?: number },
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
const readingProgress =
|
||||
await ctx.dataSources.readingProgress.getReadingProgress(article.id)
|
||||
if (readingProgress) {
|
||||
return readingProgress.readingProgressAnchorIndex
|
||||
}
|
||||
return article.readingProgressAnchorIndex
|
||||
},
|
||||
async readingProgressTopPercent(
|
||||
article: { id: string; readingProgressTopPercent?: number },
|
||||
_: unknown,
|
||||
ctx: WithDataSourcesContext
|
||||
) {
|
||||
const readingProgress =
|
||||
await ctx.dataSources.readingProgress.getReadingProgress(article.id)
|
||||
if (readingProgress) {
|
||||
return readingProgress.readingProgressTopPercent
|
||||
}
|
||||
return article.readingProgressTopPercent
|
||||
},
|
||||
},
|
||||
Subscription: {
|
||||
newsletterEmail(subscription: Subscription) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import * as jwt from 'jsonwebtoken'
|
|||
import { EntityManager } from 'typeorm'
|
||||
import winston from 'winston'
|
||||
import { PubsubClient } from '../pubsub'
|
||||
import { ReadingProgressDataSource } from '../datasources/reading_progress_data_source'
|
||||
|
||||
export interface Claims {
|
||||
uid: string
|
||||
|
|
@ -37,6 +38,9 @@ export interface RequestContext {
|
|||
userRole?: string
|
||||
) => Promise<TResult>
|
||||
tracingSpan: Span
|
||||
dataSources: {
|
||||
readingProgress: ReadingProgressDataSource
|
||||
}
|
||||
}
|
||||
|
||||
export type ResolverContext = ApolloContext<RequestContext>
|
||||
|
|
|
|||
Loading…
Reference in a new issue