feat: cache for slug to id query
This commit is contained in:
parent
ae8e4d330d
commit
86fe9078db
|
|
@ -64,5 +64,7 @@
|
|||
"jowno",
|
||||
"walt",
|
||||
"adazz",
|
||||
"ark"
|
||||
"ark",
|
||||
"haoz0x",
|
||||
"hans"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue