feat: cache for slug to id query

This commit is contained in:
DIYgod 2022-11-17 14:49:23 +00:00
parent ae8e4d330d
commit 86fe9078db
No known key found for this signature in database
GPG Key ID: D159328F47A80DCA
6 changed files with 144 additions and 31 deletions

View File

@ -64,5 +64,7 @@
"jowno",
"walt",
"adazz",
"ark"
"ark",
"haoz0x",
"hans"
]

View File

@ -19,6 +19,7 @@ if (REDIS_URL) {
export async function cacheGet(
key: string | (Record<string, any> | string | undefined)[],
getValueFun: () => Promise<any>,
noUpdate?: boolean,
) {
if (redis && redis.status === "ready") {
let redisKey: string
@ -31,11 +32,13 @@ export async function cacheGet(
}
const cacheValue = await redis.get(redisKey)
if (cacheValue) {
setTimeout(() => {
getValueFun().then((value) => {
redis.set(redisKey, JSON.stringify(value), "EX", REDIS_EXPIRE)
})
}, Math.random() * REDIS_REFRESH)
if (!noUpdate) {
setTimeout(() => {
getValueFun().then((value) => {
redis.set(redisKey, JSON.stringify(value), "EX", REDIS_EXPIRE)
})
}, Math.random() * REDIS_REFRESH)
}
return JSON.parse(cacheValue)
} else {
const value = await getValueFun()
@ -47,3 +50,19 @@ export async function cacheGet(
return await getValueFun()
}
}
export function cacheDelete(
key: string | (Record<string, any> | string | undefined)[],
) {
if (redis && redis.status === "ready") {
let redisKey: string
if (Array.isArray(key)) {
redisKey = key
.map((k) => (typeof k === "string" ? k : JSON.stringify(k)))
.join(":")
} else {
redisKey = key
}
redis.del(redisKey)
}
}

View File

@ -304,7 +304,22 @@ export async function getPage<TRender extends boolean = false>(
customUnidata?: Unidata,
) {
if (!input.site || !(input.page || input.pageId)) {
return undefined
return null
}
if (!input.pageId) {
const slug2Id = (
await axios.get("/api/slug2id", {
params: {
handle: input.site,
slug: input.page,
},
})
).data
input.pageId = `${slug2Id.characterId}-${slug2Id.noteId}`
if (!input.pageId) {
return null
}
}
const local = getLocalPages({
@ -322,27 +337,12 @@ export async function getPage<TRender extends boolean = false>(
source: "Crossbell Note",
identity: input.site,
platform: "Crossbell",
limit: 1000,
...(input.pageId && {
filter: {
id: input.pageId,
},
}),
filter: {
id: input.pageId,
},
})
let page
if (input.page) {
page = pages?.list.find((item) => {
item.slug =
item.attributes?.find((a) => a.trait_type === "xlog_slug")?.value ||
item.metadata?.raw?._xlog_slug ||
item.metadata?.raw?._crosslog_slug ||
item.id
return item.slug === input.page || item.id === input.page
})
} else {
page = pages?.list[0]
}
let page = pages?.list[0] || null
if (localPage) {
if (page) {
@ -361,8 +361,6 @@ export async function getPage<TRender extends boolean = false>(
if (page) {
expandPage(page, input.render || false)
} else {
page = null
}
return page

View File

@ -1,12 +1,9 @@
import { useRouter } from "next/router"
import { SitePage } from "~/components/site/SitePage"
import { SITE_URL } from "~/lib/env"
import { SiteLayout } from "~/components/site/SiteLayout"
import { useState } from "react"
export default function Custom404() {
const router = useRouter()
const [siteId, setSiteId] = useState("")
try {

85
src/pages/api/slug2id.ts Normal file
View File

@ -0,0 +1,85 @@
import { NextApiRequest, NextApiResponse } from "next"
import { cacheGet, cacheDelete } from "~/lib/redis.server"
const getSlug = (note: any) => {
return (
note.metadata?.content?.attributes?.find(
(a: any) => a?.trait_type === "xlog_slug",
)?.value ||
note.metadata?.content?._xlog_slug ||
note.metadata?.content?._crosslog_slug
)?.toLowerCase?.()
}
export async function getIdBySlug(slug: string, handle: string) {
slug = (slug as string)?.toLowerCase?.()
const result = await cacheGet(
["slug2id", handle, slug],
async () => {
let note
let cursor = ""
const characterRes = await (
await fetch(
`https://indexer.crossbell.io/v1/handles/${handle}/character`,
)
).json()
const cid = characterRes?.characterId
do {
const response = await (
await fetch(
`https://indexer.crossbell.io/v1/notes?characterId=${cid}&sources=xlog&cursor=${cursor}&limit=100`,
)
).json()
cursor = response.cursor
note = response?.list?.find(
(item: any) => slug === (getSlug(item) || `${cid}-${item.noteId}`),
)
} while (!note && cursor)
if (note?.noteId) {
return {
noteId: note?.noteId,
characterId: cid,
}
}
},
true,
)
if (result) {
fetch(
`https://indexer.crossbell.io/v1/characters/${result.characterId}/notes/${result.noteId}`,
)
.then((res) => res.json())
.then((note) => {
if (note) {
const currentSlug =
getSlug(note) || `${result.characterId}-${note.noteId}`
if (currentSlug !== slug) {
cacheDelete(["slug2id", handle, slug])
}
} else {
cacheDelete(["slug2id", handle, slug])
}
})
}
return result
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
let { handle, slug } = req.query
if (!slug || !handle) {
res.status(400).send("Bad Request")
return
}
res.status(200).send(await getIdBySlug(slug as string, handle as string))
}

View File

@ -1,6 +1,7 @@
import * as pageModel from "~/models/page.model"
import { QueryClient } from "@tanstack/react-query"
import { cacheGet } from "~/lib/redis.server"
import { getIdBySlug } from "~/pages/api/slug2id"
export const fetchGetPage = async (
input: Parameters<typeof pageModel.getPage>[0],
@ -8,6 +9,17 @@ export const fetchGetPage = async (
) => {
const key = ["getPage", input.page, input]
return await queryClient.fetchQuery(key, async () => {
if (!input.pageId) {
if (!input.page || !input.site) {
return null
}
const slug2Id = await getIdBySlug(input.page, input.site)
input.pageId = `${slug2Id.characterId}-${slug2Id.noteId}`
if (!input.pageId) {
return null
}
}
delete input.page
return cacheGet(key, () => pageModel.getPage(input))
})
}