xLog/src/models/page.model.ts

425 lines
9.4 KiB
TypeScript

import { Rendered, renderPageContent } from "~/markdown"
import { notFound } from "~/lib/server-side-props"
import { PageVisibilityEnum, Notes, Note } from "~/lib/types"
import unidata from "~/lib/unidata"
import { indexer, getContract } from "~/lib/crossbell"
import { NoteEntity, CharacterEntity } from "crossbell.js"
const checkPageSlug = async ({
slug,
excludePage,
siteId,
}: {
slug: string
excludePage?: string
siteId: string
}) => {
// if (!slug) {
// throw new Error("Missing page slug")
// }
// const page = await prismaPrimary.page.findFirst({
// where: {
// siteId,
// slug,
// id: excludePage && {
// not: excludePage,
// },
// },
// })
// if (!page) return
// if (page.deletedAt) {
// await prismaPrimary.page.delete({
// where: {
// id: page.id,
// },
// })
// return
// }
// throw new Error("Page slug already used")
}
export async function createOrUpdatePage(input: {
pageId?: string
siteId: string
slug?: string
title?: string
content?: string
published?: boolean
publishedAt?: string
excerpt?: string
/** Only needed when creating page */
isPost?: boolean
}) {
return await unidata.notes.set(
{
source: "Crossbell Note",
identity: input.siteId,
platform: "Crossbell",
action: input.pageId ? "update" : "add",
},
{
...(input.pageId && { id: input.pageId }),
...(input.title && { title: input.title }),
...(input.content && {
body: {
content: input.content,
mime_type: "text/markdown",
},
}),
...(input.publishedAt && {
date_published: input.published
? input.publishedAt
: new Date("9999-01-01").toISOString(),
}),
...(input.excerpt && {
summary: {
content: input.excerpt,
mime_type: "text/markdown",
},
}),
tags: [input.isPost ? "post" : "page"],
applications: ["xlog"],
...(input.slug && {
attributes: [
{
trait_type: "xlog_slug",
value: input.slug,
},
],
}),
},
)
}
export async function getPagesBySite(input: {
site?: string
type: "post" | "page"
visibility?: PageVisibilityEnum | null
take?: number | null
cursor?: string | null
includeContent?: boolean
includeExcerpt?: boolean
render?: boolean
}) {
if (!input.site) {
return {
total: 0,
list: [],
}
}
const visibility = input.visibility || PageVisibilityEnum.All
let pages: Notes = (await unidata.notes.get({
source: "Crossbell Note",
identity: input.site,
platform: "Crossbell",
limit: input.take || 1000,
})) || {
total: 0,
list: [],
}
if (pages?.list) {
switch (visibility) {
case PageVisibilityEnum.Published:
pages.list = pages.list.filter(
(page) => +new Date(page.date_published) <= +new Date(),
)
break
case PageVisibilityEnum.Draft:
pages.list = pages.list.filter(
(page) =>
page.date_published === new Date("9999-01-01").toISOString(),
)
break
case PageVisibilityEnum.Scheduled:
pages.list = pages.list.filter(
(page) =>
+new Date(page.date_published) > +new Date() &&
page.date_published !== new Date("9999-01-01").toISOString(),
)
break
}
pages.list = pages.list
.filter(
(page) =>
page.applications?.includes("Crosslog") ||
page.applications?.includes("xlog"),
)
.filter((page) => page.tags?.includes(input.type))
.sort((a, b) => +new Date(b.date_published) - +new Date(a.date_published))
pages.total = pages.list.length
pages.list = await Promise.all(
pages?.list.map(async (page) => {
if (
page.body?.content &&
page.body?.mime_type === "text/markdown" &&
input.render
) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: "text/html",
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: "text/html",
}
}
}
page.slug =
page.attributes?.find((a) => a.trait_type === "xlog_slug")?.value ||
page.metadata?.raw?._xlog_slug ||
page.metadata?.raw?._crosslog_slug ||
page.id
return page
}),
)
}
return pages
}
export async function deletePage({ site, id }: { site: string; id: string }) {
return await unidata.notes.set(
{
source: "Crossbell Note",
identity: site,
platform: "Crossbell",
action: "remove",
},
{
id,
},
)
}
export async function getPage<TRender extends boolean = false>(input: {
/** page slug or id, `site` is needed when `page` is a slug */
page?: string
pageId?: string
site?: string
render?: TRender
includeAuthors?: boolean
}) {
if (!input.site || !(input.page || input.pageId)) {
return undefined
}
const pages: Notes | null = await unidata.notes.get({
source: "Crossbell Note",
identity: input.site,
platform: "Crossbell",
limit: 1000,
...(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
})
} else {
page = pages?.list[0]
}
if (!page) {
throw notFound(`page ${input.page} not found`)
}
if (
page.body?.content &&
page.body?.mime_type === "text/markdown" &&
input.render
) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: "text/html",
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: "text/html",
}
}
}
return page
}
async function getPrimaryCharacter(address: string) {
const character = await indexer.getPrimaryCharacter(address)
return character?.characterId
}
export async function likePage({
address,
pageId,
}: {
address: string
pageId: string
}) {
const characterId = await getPrimaryCharacter(address)
if (!characterId) {
throw notFound(`character not found`)
} else {
return (await getContract())?.linkNote(
characterId,
pageId.split("-")[0],
pageId.split("-")[1],
"like",
)
}
}
export async function unlikePage({
address,
pageId,
}: {
address: string
pageId: string
}) {
const characterId = await getPrimaryCharacter(address)
if (!characterId) {
throw notFound(`character not found`)
} else {
return (await getContract())?.unlinkNote(
characterId,
pageId.split("-")[0],
pageId.split("-")[1],
"like",
)
}
}
export async function getLikes({ pageId }: { pageId: string }) {
return indexer.getBacklinksOfNote(
pageId.split("-")[0],
pageId.split("-")[1],
{
linkType: "like",
},
)
}
export async function checkLike({
address,
pageId,
}: {
address: string
pageId: string
}) {
const characterId = await getPrimaryCharacter(address)
if (!characterId) {
throw notFound(`character not found`)
} else {
return indexer.getLinks(characterId, {
linkType: "like",
toCharacterId: pageId.split("-")[0],
toNoteId: pageId.split("-")[1],
})
}
}
export async function mintPage({
address,
pageId,
}: {
address: string
pageId: string
}) {
return (await getContract())?.mintNote(
pageId.split("-")[0],
pageId.split("-")[1],
address,
)
}
export async function getMints({ pageId }: { pageId: string }) {
const data = await indexer.getMintedNotesOfNote(
pageId.split("-")[0],
pageId.split("-")[1],
)
await Promise.all(
data.list.map(async (item: any) => {
const owner = item.owner
item.character = await indexer.getPrimaryCharacter(owner)
}),
)
return data
}
export async function checkMint({
address,
pageId,
}: {
address: string
pageId: string
}) {
return indexer.getMintedNotesOfAddress(address, {
noteCharacterId: pageId.split("-")[0],
noteId: pageId.split("-")[1],
})
}
export async function commentPage({
address,
pageId,
content,
}: {
address: string
pageId: string
content: string
}) {
const characterId = await getPrimaryCharacter(address)
if (!characterId) {
throw notFound(`character not found`)
} else {
return (await getContract())?.postNoteForNote(
characterId,
{
content,
sources: ["xlog"],
tags: ["comment"],
},
pageId.split("-")[0],
pageId.split("-")[1],
)
}
}
export async function getComments({ pageId }: { pageId: string }) {
const list: (NoteEntity & {
character?: CharacterEntity | null
})[] = (
await indexer.getNotes({
toCharacterId: pageId.split("-")[0],
toNoteId: pageId.split("-")[1],
})
).list
await Promise.all(
list.map(async (item) => {
const characterId = item.characterId
item.character = await indexer.getCharacter(characterId)
}),
)
return list
}