feat: Invoke imageDimensions method via API.
This commit is contained in:
parent
3fc91b60b7
commit
4d9a760cc4
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue