feat: implement preview endpoints for draft documents; refs #176

This commit is contained in:
Hossein Mehrabi 2023-09-07 04:08:13 +03:30
parent d1c5ead6e0
commit 3437da4297
No known key found for this signature in database
GPG Key ID: 45C04964191AFAA1
11 changed files with 238 additions and 19 deletions

View File

@ -15,6 +15,7 @@ type Metadata = {
pagePath?: string
date?: string | null
contentType?: LPE.PostType
noIndex?: boolean
}
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://press.logos.co'
@ -32,6 +33,7 @@ export default function SEO({
pagePath = '',
date,
contentType,
noIndex = false,
}: Metadata) {
const ogSearchParams = new URLSearchParams()
@ -69,6 +71,11 @@ export default function SEO({
<meta name="twitter:site" content={`@${siteConfigs.xHandle}`} />
<meta property="twitter:image" content={ogUrl} />
<link rel="canonical" href={`${SITE_URL}${pagePath}`} />
{noIndex && (
<>
<meta name="robots" content="noindex, nofollow" />
</>
)}
</Head>
)
}

View File

@ -28,6 +28,7 @@ const Page: CustomNextPage<PageProps> = ({
<SEO
title={data.page.title}
description={data.page.subtitle}
noIndex={data.page.isDraft}
pagePath={`/${data.page.slug}`}
/>
<StaticPage data={data} />
@ -40,7 +41,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: data.map((page) => ({
params: {
slug: page.slug,
path: [page.slug],
},
})),
fallback: true,
@ -48,8 +49,24 @@ export const getStaticPaths: GetStaticPaths = async () => {
}
export const getStaticProps: GetStaticProps<PageProps> = async (ctx) => {
const { path } = ctx.params || {}
const [slug, idProp, id] = (Array.isArray(path) && path) || []
if (idProp && (idProp !== 'id' || !id)) {
return {
notFound: true,
props: {},
}
}
const { data, errors } = await unbodyApi.getStaticPage({
slug: ctx.params!.slug as string,
slug: slug as string,
...(id
? {
id,
includeDrafts: true,
}
: {}),
})
if (!data) {

View File

@ -24,6 +24,7 @@ const ArticlePage = ({ data, errors, why }: ArticleProps) => {
<SEO
title={data.data.title}
description={data.data.summary}
noIndex={data.data.isDraft}
image={data.data.coverImage}
pagePath={`/article/${slug}`}
date={data.data.createdAt}
@ -49,13 +50,21 @@ export async function getStaticPaths() {
return {
paths: errors
? []
: posts.map((post) => ({ params: { slug: `${post.slug}` } })),
: posts.map((post) => ({ params: { path: [post.slug] } })),
fallback: true,
}
}
export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
const { slug } = params!
const { path } = params || {}
const [slug, idProp, id] = (Array.isArray(path) && path) || []
if (idProp && (idProp !== 'id' || !id)) {
return {
notFound: true,
props: {},
}
}
if (!slug) {
return {
@ -67,6 +76,7 @@ export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
const { data, errors } = await unbodyApi.getArticle({
parseContent: true,
slug: slug as string,
...(id ? { id, includeDrafts: true } : {}),
})
if (!data) {

View File

@ -26,6 +26,7 @@ const EpisodePage = ({ episode, relatedEpisodes, errors }: EpisodeProps) => {
<SEO
title={episode.title}
description={episode.description}
noIndex={episode.isDraft}
image={episode.coverImage}
imageUrl={undefined}
pagePath={getPostLink('podcast', {
@ -53,7 +54,7 @@ export async function getStaticPaths() {
return {
params: {
showSlug: show.slug,
epSlug: episode.slug,
path: [episode.slug],
},
}
})
@ -62,12 +63,20 @@ export async function getStaticPaths() {
return {
paths: paths,
fallback: false,
fallback: 'blocking',
}
}
export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
const { showSlug, epSlug } = params!
const { showSlug, path } = params!
const [epSlug, idProp, id] = (Array.isArray(path) && path) || []
if (idProp && (idProp !== 'id' || !id)) {
return {
notFound: true,
props: {},
}
}
if (!epSlug || !showSlug) {
return {
@ -82,6 +91,12 @@ export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
showSlug: showSlug as string,
slug: epSlug as string,
textBlocks: true,
...(id
? {
id: id as string,
includeDraft: true,
}
: {}),
})
// TODO : error handlings

48
src/pages/preview.tsx Normal file
View File

@ -0,0 +1,48 @@
import { CustomNextPage, GetServerSideProps } from 'next'
import SEO from '../components/SEO/SEO'
import unbodyApi from '../services/unbody/unbody.service'
import { getPostLink } from '../utils/route.utils'
type PageProps = {}
const Page: CustomNextPage<PageProps> = (props) => {
return (
<>
<SEO />
</>
)
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const id = Array.isArray(ctx.query.id) ? ctx.query.id[0] : ctx.query.id
if (!id)
return {
notFound: true,
}
const { data, errors } = await unbodyApi.getDocById({
id,
includeDrafts: true,
})
if (!data || errors) {
return {
notFound: typeof errors === 'string' && errors.includes('Not found'),
props: {},
}
}
return {
props: {},
redirect: {
destination: getPostLink(data.type, {
id,
postSlug: data.slug,
showSlug: (data.type === 'podcast' && data.show?.slug) || null,
}),
permanent: false,
},
}
}
export default Page

View File

@ -63,6 +63,7 @@ export const ArticleDataType: UnbodyDataTypeConfig<
toc: data.tocObj ?? [],
featured: data.path.includes('featured'),
highlighted: data.path.includes('highlighted'),
isDraft: data.path.includes('draft'),
type: LPE.PostTypes.Article,
}
},

View File

@ -114,6 +114,7 @@ export const PodcastEpisodeDataType: UnbodyDataTypeConfig<
...(show ? { showId: show.id } : {}),
...(coverImage ? { coverImage } : {}),
highlighted: data.highlighted,
isDraft: data.isDraft,
type: LPE.PostTypes.Podcast,
}
},

View File

@ -38,6 +38,7 @@ export const StaticPageDataType: UnbodyDataTypeConfig<
modifiedAt: data.modifiedAt || null,
content: blocks,
type: 'static_page',
isDraft: data.pathString.includes('/draft/'),
}
},
}

View File

@ -306,6 +306,7 @@ export class UnbodyService {
}, [])
findStaticPages = ({
id,
skip = 0,
limit = 10,
slug,
@ -315,6 +316,7 @@ export class UnbodyService {
textBlocks = false,
includeDrafts = false,
}: {
id?: string
slug?: string
skip?: number
limit?: number
@ -352,6 +354,15 @@ export class UnbodyService {
'Static pages',
includeDrafts ? 'published|draft' : 'published',
]),
...(id
? [
{
operator: 'Equal',
path: ['remoteId'],
valueString: id,
} as GetObjectsGoogleDocWhereInpObj,
]
: []),
...(slug
? [
{
@ -384,15 +395,18 @@ export class UnbodyService {
}, [])
getStaticPage = ({
id,
slug,
includeDrafts = false,
}: {
id?: string
slug: string
includeDrafts?: boolean
}) =>
this.handleRequest<LPE.StaticPage.Document | null>(async () => {
const { data } = await this.findStaticPages({
limit: 1,
id,
slug,
includeDrafts,
textBlocks: true,
@ -407,6 +421,7 @@ export class UnbodyService {
findPostDocs = ({
skip = 0,
limit = 10,
id,
slug,
toc = false,
filter,
@ -416,6 +431,7 @@ export class UnbodyService {
nearText,
sort,
}: {
id?: string
slug?: string
skip?: number
limit?: number
@ -454,6 +470,15 @@ export class UnbodyService {
filter: {
operator: 'And',
operands: [
...(id
? [
{
operator: 'Equal',
path: ['remoteId'],
valueString: id,
} as GetObjectsGoogleDocWhereInpObj,
]
: []),
...(slug
? [
{
@ -484,6 +509,7 @@ export class UnbodyService {
getArticles = ({
skip = 0,
limit = 10,
id,
slug,
toc = false,
filter,
@ -492,6 +518,7 @@ export class UnbodyService {
includeDrafts = false,
highlighted = 'include',
}: {
id?: string
slug?: string
skip?: number
limit?: number
@ -529,10 +556,19 @@ export class UnbodyService {
this.helpers.args.wherePath([
'Articles',
highlighted !== 'only' && '|published',
highlighted !== 'only' && includeDrafts && '|drafts',
highlighted !== 'only' && includeDrafts && '|draft',
(highlighted === 'include' || highlighted === 'only') &&
'|highlighted',
]),
...(id
? [
{
operator: 'Equal',
path: ['remoteId'],
valueString: id,
} as GetObjectsGoogleDocWhereInpObj,
]
: []),
...(slug
? [
{
@ -565,9 +601,11 @@ export class UnbodyService {
}, [])
getArticle = ({
id,
slug,
includeDrafts = false,
}: {
id?: string
slug?: string
parseContent?: boolean
includeDrafts?: boolean
@ -576,6 +614,7 @@ export class UnbodyService {
async () =>
this.getArticles({
limit: 1,
id,
slug,
toc: true,
includeDrafts,
@ -698,6 +737,7 @@ export class UnbodyService {
getPodcastEpisodes = ({
limit = 10,
skip = 0,
id,
slug,
showSlug = '',
toc = false,
@ -709,6 +749,7 @@ export class UnbodyService {
includeDrafts = false,
highlighted = 'include',
}: {
id?: string
slug?: string
showSlug?: string
skip?: number
@ -749,10 +790,19 @@ export class UnbodyService {
'Podcasts',
showSlug,
highlighted !== 'only' && '|published',
highlighted !== 'only' && includeDrafts && '|drafts',
highlighted !== 'only' && includeDrafts && '|draft',
(highlighted === 'include' || highlighted === 'only') &&
'|highlighted',
]),
...(id
? [
{
operator: 'Equal',
path: ['remoteId'],
valueString: id,
} as GetObjectsGoogleDocWhereInpObj,
]
: []),
...(slug
? [
{
@ -802,12 +852,14 @@ export class UnbodyService {
}, [])
getPodcastEpisode = ({
id,
slug,
showSlug = '',
toc = false,
includeDraft = false,
textBlocks = false,
}: {
id?: string
slug: string
showSlug?: string
toc?: boolean
@ -816,6 +868,7 @@ export class UnbodyService {
}) =>
this.handleRequest<LPE.Podcast.Document | null>(async () => {
const { data } = await this.getPodcastEpisodes({
id,
slug,
showSlug,
skip: 0,
@ -1285,6 +1338,65 @@ export class UnbodyService {
return [...blocks].sort((a, b) => (a.score > b.score ? -1 : 1))
}, [])
getDocById = ({
id,
includeDrafts = false,
}: {
id: string
includeDrafts?: boolean
}) =>
this.handleRequest<LPE.Post.Document | LPE.StaticPage.Document | null>(
async () => {
const { data, errors } = await this.findPostDocs({
toc: true,
skip: 0,
limit: 1,
textBlocks: false,
filter: {
operator: 'And',
operands: [
{
operator: 'Equal',
valueString: id,
path: ['remoteId'],
},
this.helpers.args.wherePath([
'published|highlighted',
includeDrafts && '|draft',
]),
],
},
})
if (errors) throw errors
const [doc] = data
if (!doc) throw 'Not found!'
const { data: shows } = await this.getPodcastShows({
populateEpisodes: false,
})
const transformers = unbodyDataTypes.get({
classes: ['document'],
objectType: 'GoogleDoc',
})
const transformed = await unbodyDataTypes.transform(
transformers,
doc,
undefined,
{
shows,
parseContent: false,
},
)
return transformed
},
null,
)
getTopics = async (published: boolean = true) =>
this.handleRequest(async () => {
const { data } = await this.client.query({

View File

@ -116,6 +116,7 @@ export namespace LPE {
title: string
summary: string
subtitle: string
isDraft?: boolean
createdAt: string | null
modifiedAt: string | null
@ -147,6 +148,7 @@ export namespace LPE {
authors: Author.Document[]
tags: string[]
highlighted?: boolean
isDraft?: boolean
createdAt: string | null
modifiedAt: string | null
@ -217,6 +219,7 @@ export namespace LPE {
episodeNumber: number
showId?: string
highlighted?: boolean
isDraft?: boolean
coverImage?: Post.ImageBlock
show?: Show
type: typeof LPE.PostTypes.Podcast

View File

@ -1,29 +1,33 @@
import { LPE } from '../types/lpe.types'
export const getPostLink = (
postType: LPE.PostType,
postType: LPE.PostType | LPE.StaticPage.Metadata['type'],
{
id,
block,
showSlug,
postSlug,
blockType,
}: {
id?: string
block?: string | number | null
showSlug?: string | number | null
postSlug?: string | number | null
blockType?: LPE.Post.ContentBlockType | null
} = {},
) => {
const basePath =
postType === 'article' ? `/article` : `/podcasts/${showSlug || ''}`
let path =
postType === 'article'
? `/article`
: postType === 'podcast'
? `/podcasts/${showSlug || ''}`
: ''
if (!postSlug) return basePath
if (postSlug) path += `/${postSlug}`
if (id) path += `/id/${id}`
const postPath = `${basePath}/${postSlug}`
if (blockType && block)
path += `#${blockType === 'text' ? 'p' : 'i'}-${block}`
if (!blockType && !block) return postPath
const blockPath = `${postPath}/#${blockType === 'text' ? 'p' : 'i'}-${block}`
return blockPath
return path
}