feat: Invoke imageDimensions method via API.

This commit is contained in:
Caspian 2023-12-01 15:34:02 +00:00
parent 3fc91b60b7
commit 4d9a760cc4
7 changed files with 186 additions and 153 deletions

View File

@ -1,6 +1,5 @@
import { getQuery, NextServerResponse } from "~/lib/server-helper"
import { getFeed } from "~/models/home.model"
import { decoratePageForImageDimensions } from "~/queries/page.server"
export async function GET(req: Request) {
const query = getQuery(req)
@ -15,13 +14,10 @@ export async function GET(req: Request) {
searchType: query.searchType,
tag: query.tag,
useHTML: false,
useImageDimensions: query.useImageDimensions === "true",
topic: query.topic,
})
for (const item of result.list) {
await decoratePageForImageDimensions(item)
}
const res = new NextServerResponse()
return res.status(200).json(result)
}

View File

@ -0,0 +1,132 @@
import AsyncLock from "async-lock"
import sizeOf from "image-size"
import { Metadata } from "@prisma/client"
import { toGateway } from "~/lib/ipfs-parser"
import prisma from "~/lib/prisma.server"
import { cacheGet } from "~/lib/redis.server"
import { getQuery, NextServerResponse } from "~/lib/server-helper"
const lock = new AsyncLock()
export const getImageDimensionByUri = async (
uri: string,
useFullSize = false,
): Promise<{ width: number; height: number; uri: string } | null> => {
const headers: Record<string, string> = {}
if (!useFullSize) {
headers["Range"] = "bytes=0-10240"
}
try {
const response = await fetch(uri, { headers })
const arrayBuffer = await response.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const dimensions = sizeOf(buffer)
if (dimensions.width && dimensions.height) {
return {
width: dimensions.width,
height: dimensions.height,
uri,
}
} else {
throw new Error("Could not determine image dimensions.")
}
} catch (e) {
if (!useFullSize) {
return getImageDimensionByUri(uri, true)
}
return null
}
}
export const getCoverDimensions = async ({
cid,
uris,
}: {
cid: string
uris: string[]
}) => {
if (!uris.length) return
const dimensions = (await cacheGet({
key: ["coverDimensions", cid],
allowEmpty: true,
noUpdate: true,
noExpire: true,
getValueFun: async () => {
let result
await lock.acquire(`coverDimensions_${cid}`, async () => {
const meta = await prisma.metadata.findFirst({
where: {
uri: `ipfs://${cid}`,
},
})
const key: keyof Metadata = "image_dimensions"
const dimensions = meta?.image_dimensions
if (dimensions) {
result = dimensions
} else {
const newDimensions: Record<
string,
{ width: number; height: number }
> = {}
console.time(`get image dimensions ${cid}`)
for (const uri of uris) {
const dimension = await getImageDimensionByUri(
toGateway(uri),
).catch(() => null)
if (dimension) {
newDimensions[uri] = dimension
}
}
console.timeEnd(`get image dimensions ${cid}`)
if (meta) {
await prisma.metadata.update({
where: { uri: `ipfs://${cid}` },
data: {
[key]: newDimensions,
},
})
} else {
await prisma.metadata.create({
data: {
uri: `ipfs://${cid}`,
[key]: newDimensions,
},
})
}
result = newDimensions
}
})
return result
},
})) as Record<string, { width: number; height: number }> | undefined
return dimensions
}
export async function GET(req: Request): Promise<Response> {
let { cid, uris: _uris } = getQuery(req)
const uris = _uris.split(",")
const res = new NextServerResponse()
if (!cid) {
return res.status(400).send("Bad Request")
}
if (uris.length === 0) return res.status(200).json({})
return res.status(200).json({
data: await getCoverDimensions({ cid, uris }),
})
}

View File

@ -1,7 +1,6 @@
import { getQuery, NextServerResponse } from "~/lib/server-helper"
import { PagesSortTypes, PageVisibilityEnum } from "~/lib/types"
import { getPagesBySite } from "~/models/page.model"
import { decoratePageForImageDimensions } from "~/queries/page.server"
export async function GET(req: Request) {
const query = getQuery(req)
@ -13,6 +12,7 @@ export async function GET(req: Request) {
limit: query.limit ? parseInt(query.limit as string) : undefined,
cursor: query.cursor as string,
useStat: true,
useImageDimensions: query.useImageDimensions === "true",
skipExpansion: query.skipExpansion === "true",
sortType: query.sortType as PagesSortTypes,
...(query.tags && {
@ -22,10 +22,6 @@ export async function GET(req: Request) {
}),
})
for (const item of result.list) {
await decoratePageForImageDimensions(item)
}
const res = new NextServerResponse()
return res.status(200).json(result)
}

View File

@ -13,6 +13,7 @@ export const expandCrossbellNote = async ({
note,
useStat,
useScore,
useImageDimensions,
keyword,
useHTML,
disableAutofill,
@ -26,6 +27,7 @@ export const expandCrossbellNote = async ({
}
useStat?: boolean
useScore?: boolean
useImageDimensions?: boolean
keyword?: string
useHTML?: boolean
disableAutofill?: boolean
@ -100,6 +102,40 @@ export const expandCrossbellNote = async ({
...new Set(expandedNote.metadata.content.images),
]
if (useScore) {
try {
const score = (
await (
await fetch(
`${SCORE_API_DOMAIN || SITE_URL}/api/score?cid=${toCid(
expandedNote.metadata?.uri || "",
)}`,
)
).json()
).data
expandedNote.metadata.content.score = score
} catch (e) {
// do nothing
}
}
if (useImageDimensions) {
try {
const imageDimensions = await (
await fetch(
`${SITE_URL}/api/image-dimensions?` +
new URLSearchParams({
cid: toCid(expandedNote.metadata.uri || ""),
uris: expandedNote.metadata.content.images || [],
} as any),
)
).json()
expandedNote.metadata.content.imageDimensions = imageDimensions.data
} catch (e) {
// do nothing
}
}
expandedNote.metadata.content.slug = getNoteSlug(
expandedNote,
disableAutofill,

View File

@ -35,6 +35,7 @@ export async function getFeed({
searchType,
tag,
useHTML,
useImageDimensions,
topic,
}: {
type?: FeedType
@ -46,6 +47,7 @@ export async function getFeed({
searchType?: SearchType
tag?: string
useHTML?: boolean
useImageDimensions?: boolean
topic?: string
}) {
if (type === "search" && !searchKeyword) {
@ -141,6 +143,7 @@ export async function getFeed({
note: page,
useScore: true,
useHTML,
useImageDimensions,
}),
),
)
@ -235,6 +238,7 @@ export async function getFeed({
const expand = await expandCrossbellNote({
note: page,
useHTML,
useImageDimensions,
})
if (expand.toNote) {
if (expand.toNote.toNote) {
@ -251,6 +255,7 @@ export async function getFeed({
expand.toNote = await expandCrossbellNote({
note: expand.toNote,
useHTML,
useImageDimensions,
})
delete expand.toNote.toNote
}
@ -321,6 +326,7 @@ export async function getFeed({
useScore: true,
useStat: true,
useHTML,
useImageDimensions,
}),
),
)
@ -359,6 +365,7 @@ export async function getFeed({
expandCrossbellNote({
note: page,
useHTML,
useImageDimensions,
}),
),
)
@ -465,6 +472,7 @@ export async function getFeed({
expandCrossbellNote({
note: page,
useHTML,
useImageDimensions,
}),
),
)
@ -540,6 +548,7 @@ export async function getFeed({
expandCrossbellNote({
note: page,
useHTML,
useImageDimensions,
}),
),
)
@ -644,6 +653,7 @@ export async function getFeed({
note: page,
useHTML,
useStat: true,
useImageDimensions,
})
return expand
},
@ -769,6 +779,7 @@ export async function getFeed({
note: page,
useHTML,
useStat: true,
useImageDimensions,
})
return expand
},
@ -813,6 +824,7 @@ export async function getFeed({
useScore: false,
keyword: searchKeyword,
useHTML,
useImageDimensions,
}),
),
)
@ -850,6 +862,7 @@ export async function getFeed({
useStat: false,
useScore: true,
useHTML,
useImageDimensions,
}),
),
)

View File

@ -122,6 +122,7 @@ export async function getPagesBySite(input: {
tags?: string[]
useStat?: boolean
useHTML?: boolean
useImageDimensions?: boolean
keepBody?: boolean
handle?: string // In order to be compatible with old drafts
skipExpansion?: boolean
@ -311,6 +312,7 @@ export async function getPagesBySite(input: {
useStat: input.useStat,
useHTML: input.useHTML,
useScore: false,
useImageDimensions: input.useImageDimensions,
})
}

View File

@ -1,7 +1,4 @@
import https from "https"
import url from "url"
import AsyncLock from "async-lock"
import sizeOf from "image-size"
import { AnalyzeDocumentChain, loadSummarizationChain } from "langchain/chains"
import { OpenAI } from "langchain/llms/openai"
import { PromptTemplate } from "langchain/prompts"
@ -10,7 +7,7 @@ import removeMarkdown from "remove-markdown"
import { Metadata } from "@prisma/client"
import { QueryClient } from "@tanstack/react-query"
import { toCid, toGateway } from "~/lib/ipfs-parser"
import { toGateway } from "~/lib/ipfs-parser"
import prisma from "~/lib/prisma.server"
import { cacheGet } from "~/lib/redis.server"
import * as pageModel from "~/models/page.model"
@ -38,145 +35,6 @@ export const fetchGetPage = async (
})
}
export const getImageDimensionByUri = async (uri: string) => {
return new Promise<{ width: number; height: number; uri: string }>(
(resolve, reject) => {
const options = url.parse(uri)
https.get(options, function (response) {
const chunks: Buffer[] = []
let totalLength = 0
const MAX_BUFFER_LENGTH = 10240
response
.on("data", function (chunk) {
chunks.push(chunk)
totalLength += chunk.length
if (totalLength > MAX_BUFFER_LENGTH) {
response.destroy()
const buffer = Buffer.concat(chunks)
try {
const dimension = sizeOf(buffer)
if (dimension.width && dimension.height) {
resolve({
width: dimension.width,
height: dimension.height,
uri,
})
}
} catch (e) {
reject("Not enough data for image size detection.")
}
}
})
.on("end", function () {
if (totalLength <= MAX_BUFFER_LENGTH) {
const buffer = Buffer.concat(chunks)
try {
const dimension = sizeOf(buffer)
if (dimension.width && dimension.height) {
resolve({
width: dimension.width,
height: dimension.height,
uri,
})
}
} catch (e) {
reject(e)
}
}
})
})
},
)
}
export const cacheCoverDimensions = async ({
cid,
uris,
}: {
cid: string
uris: string[]
}) => {
if (!uris.length) return
const dimensions = (await cacheGet({
key: ["coverDimensions", cid],
allowEmpty: true,
noUpdate: true,
noExpire: true,
getValueFun: async () => {
let result
await lock.acquire(`coverDimensions_${cid}`, async () => {
const meta = await prisma.metadata.findFirst({
where: {
uri: `ipfs://${cid}`,
},
})
const key: keyof Metadata = "image_dimensions"
const dimensions = meta?.image_dimensions
if (dimensions) {
result = dimensions
} else {
const newDimensions: Record<
string,
{ width: number; height: number }
> = {}
console.time(`get image dimensions ${cid}`)
for (const uri of uris) {
const dimension = await getImageDimensionByUri(toGateway(uri))
if (dimension) {
newDimensions[uri] = dimension
}
}
console.timeEnd(`get image dimensions ${cid}`)
if (meta) {
await prisma.metadata.update({
where: { uri: `ipfs://${cid}` },
data: {
[key]: newDimensions,
},
})
} else {
await prisma.metadata.create({
data: {
uri: `ipfs://${cid}`,
[key]: newDimensions,
},
})
}
result = newDimensions
}
})
return result
},
})) as Record<string, { width: number; height: number }> | undefined
return dimensions
}
export const decoratePageForImageDimensions = async (
page?: Awaited<ReturnType<typeof pageModel.getPage>> | null,
) => {
if (!page) return
const dimensions = await cacheCoverDimensions({
cid: toCid(page?.metadata?.uri || ""),
uris: page?.metadata?.content?.images || [],
})
if (dimensions && Object.keys(dimensions).length && page?.metadata?.content) {
page.metadata.content["imageDimensions"] = dimensions
}
}
export const prefetchGetPagesBySite = async (
input: Parameters<typeof pageModel.getPagesBySite>[0],
queryClient: QueryClient,