344 lines
8.2 KiB
TypeScript
344 lines
8.2 KiB
TypeScript
import { nanoid } from "nanoid"
|
|
import {
|
|
prismaPrimary,
|
|
Prisma,
|
|
prismaRead,
|
|
MembershipRole,
|
|
PageEmailStatus,
|
|
} from "~/lib/db.server"
|
|
import { type Gate } from "~/lib/gate.server"
|
|
import { Rendered, renderPageContent } from "~/markdown"
|
|
import { notFound } from "~/lib/server-side-props"
|
|
import { PageVisibilityEnum } from "~/lib/types"
|
|
import { isUUID } from "~/lib/uuid"
|
|
import { getSite } from "./site.model"
|
|
import { stripHTML } from "~/lib/utils"
|
|
import { sendEmailForNewPost } from "~/lib/mailgun.server"
|
|
import unidata from "~/lib/unidata"
|
|
import { Notes } from "unidata.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
|
|
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"],
|
|
})
|
|
}
|
|
|
|
export async function scheduleEmailForPost(
|
|
gate: Gate,
|
|
input: { pageId: string; emailSubject?: string },
|
|
) {
|
|
// const page = await getPage(gate, { page: input.pageId })
|
|
|
|
// if (!gate.allows({ type: "can-update-page", siteId: page.siteId })) {
|
|
// throw gate.permissionError()
|
|
// }
|
|
|
|
// if (page.emailStatus) {
|
|
// throw new Error("Email already scheduled or sent")
|
|
// }
|
|
|
|
// await prismaPrimary.page.update({
|
|
// where: {
|
|
// id: page.id,
|
|
// },
|
|
// data: {
|
|
// emailStatus: "PENDING",
|
|
// emailSubject: input.emailSubject,
|
|
// },
|
|
// })
|
|
|
|
// await notifySubscribersForNewPost(gate, {
|
|
// pageId: page.id,
|
|
// })
|
|
}
|
|
|
|
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 = await unidata.notes.get({
|
|
source: 'Crossbell Note',
|
|
identity: input.site,
|
|
platform: 'Crossbell',
|
|
limit: input.take || 1000,
|
|
});
|
|
|
|
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.tags?.includes(input.type))
|
|
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'
|
|
}
|
|
}
|
|
}
|
|
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
|
|
site?: string
|
|
render?: TRender
|
|
includeAuthors?: boolean
|
|
},
|
|
) {
|
|
if (!input.site) {
|
|
throw notFound(`site not found`)
|
|
}
|
|
|
|
const isPageUUID = isUUID(input.page)
|
|
if (!isPageUUID && !input.site) {
|
|
throw new Error("input.site is required because input.page is a slug")
|
|
}
|
|
|
|
const pages = await unidata.notes.get({
|
|
source: 'Crossbell Note',
|
|
identity: input.site,
|
|
platform: 'Crossbell',
|
|
filter: {
|
|
id: input.page,
|
|
}
|
|
})
|
|
|
|
const page = pages?.list[0]
|
|
|
|
if (!page) {
|
|
throw notFound(`page ${input.page} not found`)
|
|
}
|
|
|
|
if (new Date(page.date_published) > new Date()) {
|
|
throw notFound()
|
|
}
|
|
|
|
if (pages?.list) {
|
|
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'
|
|
}
|
|
}
|
|
}
|
|
return 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'
|
|
}
|
|
}
|
|
}
|
|
|
|
return page
|
|
}
|
|
|
|
export const notifySubscribersForNewPost = async (
|
|
gate: Gate,
|
|
input: {
|
|
pageId: string
|
|
},
|
|
) => {
|
|
// const page = await getPage(gate, { page: input.pageId, render: true })
|
|
// const site = await getSite(page.siteId)
|
|
|
|
// if (page.emailStatus !== PageEmailStatus.PENDING) {
|
|
// return
|
|
// }
|
|
|
|
// if (!page.published || page.publishedAt > new Date()) {
|
|
// return
|
|
// }
|
|
|
|
// if (page.type === "PAGE") {
|
|
// throw new Error("You can only notify subscribers for post updates")
|
|
// }
|
|
|
|
// if (!gate.allows({ type: "can-notify-site-subscribers", site })) {
|
|
// throw gate.permissionError()
|
|
// }
|
|
|
|
// await prismaPrimary.page.update({
|
|
// where: {
|
|
// id: page.id,
|
|
// },
|
|
// data: {
|
|
// emailStatus: PageEmailStatus.RUNNING,
|
|
// },
|
|
// })
|
|
|
|
// const memberships = await prismaPrimary.membership.findMany({
|
|
// where: {
|
|
// role: MembershipRole.SUBSCRIBER,
|
|
// siteId: site.id,
|
|
// },
|
|
// include: {
|
|
// user: true,
|
|
// },
|
|
// })
|
|
|
|
// const emailSubscribers = memberships
|
|
// .filter((member) => (member.config as any).email)
|
|
// .map((member) => member.user)
|
|
|
|
// try {
|
|
// await sendEmailForNewPost({
|
|
// post: page,
|
|
// site,
|
|
// subscribers: emailSubscribers,
|
|
// })
|
|
// await prismaPrimary.page.update({
|
|
// where: {
|
|
// id: page.id,
|
|
// },
|
|
// data: {
|
|
// emailStatus: PageEmailStatus.SUCCESS,
|
|
// },
|
|
// })
|
|
// } catch (error) {
|
|
// await prismaPrimary.page.update({
|
|
// where: {
|
|
// id: page.id,
|
|
// },
|
|
// data: {
|
|
// emailStatus: PageEmailStatus.FAILED,
|
|
// },
|
|
// })
|
|
// console.error("failed to send email", error)
|
|
// }
|
|
}
|