astro-loader-hashnode
Version:
Astro content loader for seamlessly integrating Hashnode blog posts into your Astro website using the Content Layer API
359 lines (358 loc) • 9.62 kB
JavaScript
/**
* Zod schemas for content validation
*/
import { z } from 'zod';
// Author schema
export const authorSchema = z.object({
id: z.string(),
name: z.string(),
username: z.string(),
profilePicture: z.string().url().optional(),
bio: z.string().optional(),
url: z.string().url().optional(),
social: z
.object({
website: z.string().url().optional(),
github: z.string().optional(),
twitter: z.string().optional(),
linkedin: z.string().optional(),
})
.optional(),
followersCount: z.number().optional(),
});
// Tag schema
export const tagSchema = z.object({
id: z.string().optional(),
name: z.string(),
slug: z.string(),
logo: z.string().url().optional(),
tagline: z.string().optional(),
followersCount: z.number().optional(),
});
// Cover image schema
export const coverImageSchema = z.object({
url: z.string().url(),
alt: z.string().optional(),
attribution: z.string().optional(),
isPortrait: z.boolean().optional(),
isAttributionHidden: z.boolean().optional(),
});
// Table of contents schema
export const tableOfContentsSchema = z.object({
isEnabled: z.boolean(),
items: z.array(z.object({
id: z.string(),
level: z.number(),
parentId: z.string().optional(),
slug: z.string(),
title: z.string(),
})),
});
// Comment schema (recursive type for replies)
export const commentSchema = z.lazy(() => z.object({
id: z.string(),
dateAdded: z.string(),
totalReactions: z.number().optional(),
content: z.object({
html: z.string(),
markdown: z.string().optional(),
}),
author: authorSchema,
replies: z.array(commentSchema).optional(),
}));
// SEO schema
export const seoSchema = z.object({
title: z.string(),
description: z.string(),
});
// Content schema
export const contentSchema = z.object({
html: z.string(),
markdown: z.string().optional(),
});
// Publication schema
export const publicationSchema = z.object({
id: z.string(),
title: z.string(),
displayTitle: z.string().optional(),
url: z.string().url(),
isTeam: z.boolean().optional(),
favicon: z.string().url().optional(),
about: z.string().optional(),
features: z
.object({
newsletter: z.object({ isEnabled: z.boolean() }).optional(),
readTime: z.object({ isEnabled: z.boolean() }).optional(),
textSelectionSharer: z.object({ isEnabled: z.boolean() }).optional(),
audioBlog: z
.object({
isEnabled: z.boolean(),
voiceType: z.string().optional(),
})
.optional(),
customCSS: z.object({ isEnabled: z.boolean() }).optional(),
})
.optional(),
});
// Post schema
export const postSchema = z.object({
// Core content
id: z.string(),
cuid: z.string().optional(),
title: z.string(),
subtitle: z.string().optional(),
brief: z.string(),
slug: z.string(),
url: z.string().url(),
content: contentSchema,
// Publishing metadata
publishedAt: z.date(),
updatedAt: z.date().optional(),
// Media
coverImage: coverImageSchema.optional(),
// Taxonomies
tags: z.array(tagSchema),
// Author information
author: authorSchema,
coAuthors: z.array(authorSchema).optional(),
// SEO
seo: seoSchema,
ogMetaData: z
.object({
image: z.string().url().optional(),
})
.optional(),
// Reading metadata
readingTime: z.number(),
wordCount: z.number().optional(),
// Engagement metrics
views: z.number().default(0),
reactions: z.number().default(0),
comments: z.number().default(0),
replies: z.number().default(0),
// Status
isDraft: z.boolean().default(false),
hasLatex: z.boolean().default(false),
// Series info
series: z
.object({
id: z.string(),
name: z.string().optional(),
slug: z.string().optional(),
})
.optional(),
// Table of contents
tableOfContents: tableOfContentsSchema.optional(),
// Preferences
preferences: z
.object({
disableComments: z.boolean().optional(),
stickCoverToBottom: z.boolean().optional(),
pinnedToBlog: z.boolean().optional(),
isDelisted: z.boolean().optional(),
})
.optional(),
// Comments data
commentsData: z
.object({
totalCount: z.number(),
comments: z.array(commentSchema),
})
.optional(),
// Publication info
publication: publicationSchema.optional(),
// Hashnode-specific
hashnodeId: z.string(),
hashnodeUrl: z.string().url(),
});
// Series schema
export const seriesSchema = z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
description: z.string().optional(),
descriptionMarkdown: z.string().optional(),
descriptionText: z.string().optional(),
coverImage: z.string().url().optional(),
cuid: z.string().optional(),
author: authorSchema,
postsCount: z.number(),
posts: z.array(z.string()), // Array of post slugs
createdAt: z.string(),
updatedAt: z.string(),
});
// Static page schema
export const staticPageSchema = z.object({
id: z.string(),
slug: z.string(),
title: z.string(),
content: contentSchema,
seo: seoSchema,
ogMetaData: z
.object({
image: z.string().url().optional(),
})
.optional(),
wordCount: z.number().optional(),
updatedAt: z.string(),
});
// Tag collection schema
export const tagCollectionSchema = z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
logo: z.string().url().optional(),
tagline: z.string().optional(),
followersCount: z.number(),
postsCount: z.number(),
posts: z.array(z.string()), // Array of post slugs
createdAt: z.string(),
updatedAt: z.string(),
});
// RSS feed schema
export const rssFeedSchema = z.object({
title: z.string(),
description: z.string(),
link: z.string().url(),
posts: z.array(z.object({
title: z.string(),
description: z.string(),
link: z.string().url(),
pubDate: z.string(),
author: z.string(),
guid: z.string(),
})),
});
// Sitemap schema
export const sitemapSchema = z.object({
urls: z.array(z.object({
url: z.string().url(),
lastmod: z.string(),
priority: z.string(),
})),
totalUrls: z.number(),
generatedAt: z.string(),
});
// Newsletter schema
export const newsletterSchema = z.object({
id: z.string(),
title: z.string(),
displayTitle: z.string().optional(),
url: z.string().url(),
isNewsletterEnabled: z.boolean(),
isNewsletterPageEnabled: z.boolean(),
followersCount: z.number(),
description: z.string().optional(),
about: z.string().optional(),
author: authorSchema,
recentPosts: z.array(z.object({
id: z.string(),
title: z.string(),
slug: z.string(),
publishedAt: z.string(),
brief: z.string().optional(),
})),
updatedAt: z.string(),
});
// Search results schema
export const searchResultsSchema = z.object({
query: z.string(),
totalResults: z.number(),
results: z.array(z.object({
id: z.string(),
title: z.string(),
brief: z.string(),
slug: z.string(),
url: z.string().url(),
publishedAt: z.string(),
author: z.object({
id: z.string(),
name: z.string(),
}),
coverImage: z
.object({
url: z.string().url(),
})
.optional(),
reactionCount: z.number(),
})),
searchedAt: z.string(),
});
// Individual search result schema
export const searchResultSchema = z.object({
id: z.string(),
title: z.string(),
brief: z.string(),
slug: z.string(),
url: z.string().url(),
searchTerm: z.string(),
searchRelevance: z.number(),
publishedAt: z.date(),
reactionCount: z.number(),
views: z.number(),
author: z.object({
id: z.string(),
name: z.string(),
username: z.string(),
profilePicture: z.string(),
}),
coverImage: z
.object({
url: z.string().url(),
})
.optional(),
publication: z
.object({
title: z.string(),
url: z.string().url(),
})
.optional(),
raw: z.object({
cuid: z.string().optional(),
}),
});
// Draft schema
export const draftSchema = z.object({
id: z.string(),
title: z.string(),
subtitle: z.string().optional(),
content: z.string(),
canonicalUrl: z.string().optional(),
updatedAt: z.date(),
createdAt: z.date(),
author: z.object({
id: z.string(),
name: z.string(),
username: z.string(),
profilePicture: z.string(),
}),
coverImage: z
.object({
url: z.string().url(),
})
.optional(),
tags: z.array(tagSchema),
tableOfContents: z.array(z.object({
id: z.string(),
level: z.number(),
title: z.string(),
slug: z.string(),
parentId: z.string().optional(),
})),
isDraft: z.boolean(),
lastSaved: z.date(),
raw: z.object({
id: z.string(),
}),
});
// Union schema for all content types
export const contentUnionSchema = z.union([
postSchema,
seriesSchema,
staticPageSchema,
tagCollectionSchema,
rssFeedSchema,
sitemapSchema,
newsletterSchema,
searchResultsSchema,
]);