* Add status property to content item structure This commit introduces a new `status` property to the content item structure, allowing content items to maintain a status such as 'draft', 'published', 'review', 'pending'. This status is mapped to the corresponding status in Wordpress and Keystatic clients to ensure consistent usage across platforms. Content retrieval methods now also include a status filter. * Refactor code for style and readability improvements This commit includes changes in various files across different packages to improve code readability, including adjusting spacing and breaking down complex lines into simpler parts. In addition, the switch statement in `wp-client.ts` has been refactored and status values are now held in variables, and the CSS classes in `global-loader.tsx` have been reorganized.
419 lines
10 KiB
TypeScript
419 lines
10 KiB
TypeScript
import type {
|
|
WP_Post_Status_Name,
|
|
WP_REST_API_Category,
|
|
WP_REST_API_Post,
|
|
WP_REST_API_Tag,
|
|
} from 'wp-types';
|
|
|
|
import { Cms, CmsClient } from '@kit/cms';
|
|
|
|
import GetTagsOptions = Cms.GetTagsOptions;
|
|
|
|
export function createWordpressClient(
|
|
apiUrl = process.env.WORDPRESS_API_URL as string,
|
|
) {
|
|
return new WordpressClient(apiUrl);
|
|
}
|
|
|
|
/**
|
|
* @name WordpressClient
|
|
* @description Represents a client for interacting with a Wordpress CMS.
|
|
* Implements the CmsClient interface.
|
|
*/
|
|
class WordpressClient implements CmsClient {
|
|
constructor(private readonly apiUrl: string) {}
|
|
|
|
/**
|
|
* Retrieves content items from a CMS based on the provided options.
|
|
*
|
|
* @param {Cms.GetContentItemsOptions} options - The options to customize the retrieval of content items.
|
|
*/
|
|
async getContentItems(options: Cms.GetContentItemsOptions) {
|
|
const queryParams = new URLSearchParams({
|
|
_embed: 'true',
|
|
});
|
|
|
|
if (options?.limit) {
|
|
queryParams.append('per_page', options.limit.toString());
|
|
}
|
|
|
|
if (options?.offset) {
|
|
queryParams.append('offset', options.offset.toString());
|
|
}
|
|
|
|
if (options.sortBy) {
|
|
const sortBy = mapSortByParam(options.sortBy);
|
|
|
|
if (sortBy) {
|
|
queryParams.append('orderby', sortBy);
|
|
}
|
|
}
|
|
|
|
if (options.sortDirection) {
|
|
queryParams.append('order', options.sortDirection);
|
|
}
|
|
|
|
if (options?.categories) {
|
|
const ids = await this.getCategories({
|
|
slugs: options.categories,
|
|
}).then((categories) => categories.map((category) => category.id));
|
|
|
|
if (ids.length) {
|
|
queryParams.append('categories', ids.join(','));
|
|
} else {
|
|
console.warn(
|
|
'No categories found for the provided slugs',
|
|
options.categories,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (options?.tags) {
|
|
const allTags = [
|
|
...options.tags,
|
|
options.language ? options.language : '',
|
|
].filter(Boolean);
|
|
|
|
const ids = await this.getTags({
|
|
slugs: allTags,
|
|
}).then((tags) => tags.map((tag) => tag.id));
|
|
|
|
if (ids.length) {
|
|
queryParams.append('tags', ids.join(','));
|
|
} else {
|
|
console.warn('No tags found for the provided slugs', options.tags);
|
|
}
|
|
}
|
|
|
|
if (options?.parentIds && options.parentIds.length > 0) {
|
|
queryParams.append('parent', options.parentIds.join(','));
|
|
}
|
|
|
|
const endpoints = [
|
|
`/wp-json/wp/v2/posts?${queryParams.toString()}`,
|
|
`/wp-json/wp/v2/pages?${queryParams.toString()}`,
|
|
];
|
|
|
|
const endpoint =
|
|
options.collection === 'posts' ? endpoints[0] : endpoints[1];
|
|
|
|
const url = `${this.apiUrl}${endpoint}`;
|
|
|
|
const response = await fetch(url);
|
|
const totalHeader = response.headers.get('X-WP-Total');
|
|
|
|
const total = totalHeader ? Number(totalHeader) : 0;
|
|
const results = (await response.json()) as WP_REST_API_Post[];
|
|
|
|
const status = options.status ?? 'published';
|
|
|
|
const postsResults = await Promise.allSettled(
|
|
results.map(async (item: WP_REST_API_Post) => {
|
|
let parentId: string | undefined;
|
|
|
|
if (!item) {
|
|
throw new Error('Failed to fetch content items');
|
|
}
|
|
|
|
if (item.parent) {
|
|
parentId = item.parent.toString();
|
|
}
|
|
|
|
const mappedStatus = mapToStatus(item.status as WP_Post_Status_Name);
|
|
|
|
if (mappedStatus !== status) {
|
|
throw new Error('Status does not match');
|
|
}
|
|
|
|
const categories = await this.getCategoriesByIds(item.categories ?? []);
|
|
const tags = await this.getTagsByIds(item.tags ?? []);
|
|
const image = item.featured_media ? this.getFeaturedMedia(item) : '';
|
|
const order = item.menu_order ?? 0;
|
|
|
|
return {
|
|
id: item.id.toString(),
|
|
title: item.title.rendered,
|
|
content: item.content.rendered,
|
|
description: item.excerpt.rendered,
|
|
image,
|
|
url: item.link,
|
|
slug: item.slug,
|
|
publishedAt: item.date,
|
|
status: mappedStatus ?? 'draft',
|
|
categories: categories,
|
|
tags: tags,
|
|
parentId,
|
|
order,
|
|
children: [],
|
|
};
|
|
}),
|
|
);
|
|
|
|
const posts = postsResults
|
|
.filter((item) => item.status === 'fulfilled')
|
|
.map((item) => item.value);
|
|
|
|
return {
|
|
total,
|
|
items: posts,
|
|
};
|
|
}
|
|
|
|
async getContentItemBySlug({
|
|
slug,
|
|
collection,
|
|
status,
|
|
}: {
|
|
slug: string;
|
|
collection: string;
|
|
status?: Cms.ContentItemStatus;
|
|
}) {
|
|
const searchParams = new URLSearchParams({
|
|
_embed: 'true',
|
|
slug,
|
|
});
|
|
|
|
const endpoints = [
|
|
`/wp-json/wp/v2/posts?${searchParams.toString()}`,
|
|
`/wp-json/wp/v2/pages?${searchParams.toString()}`,
|
|
];
|
|
|
|
const endpoint = collection === 'posts' ? endpoints[0] : endpoints[1];
|
|
|
|
const responses = await fetch(this.apiUrl + endpoint).then(
|
|
(res) => res.json() as Promise<WP_REST_API_Post[]>,
|
|
);
|
|
|
|
const item = responses[0];
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
const mappedStatus = status
|
|
? mapToStatus(item.status as WP_Post_Status_Name)
|
|
: undefined;
|
|
const statusMatch = status ? mappedStatus === status : true;
|
|
|
|
if (!statusMatch) {
|
|
return;
|
|
}
|
|
|
|
const categories = await this.getCategoriesByIds(item.categories ?? []);
|
|
const tags = await this.getTagsByIds(item.tags ?? []);
|
|
const image = item.featured_media ? this.getFeaturedMedia(item) : '';
|
|
|
|
return {
|
|
id: item.id.toString(),
|
|
image,
|
|
order: item.menu_order ?? 0,
|
|
url: item.link,
|
|
description: item.excerpt.rendered,
|
|
children: [],
|
|
title: item.title.rendered,
|
|
content: item.content.rendered,
|
|
slug: item.slug,
|
|
publishedAt: item.date,
|
|
status: mappedStatus ?? 'draft',
|
|
categories,
|
|
tags,
|
|
parentId: item.parent?.toString(),
|
|
};
|
|
}
|
|
|
|
async getCategoryBySlug(slug: string) {
|
|
const url = `${this.apiUrl}/wp-json/wp/v2/categories?slug=${slug}`;
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const item = data[0] as WP_REST_API_Category;
|
|
|
|
return {
|
|
id: item.id.toString(),
|
|
name: item.name,
|
|
slug: item.slug,
|
|
};
|
|
}
|
|
|
|
async getTagBySlug(slug: string) {
|
|
const url = `${this.apiUrl}/wp-json/wp/v2/tags?slug=${slug}`;
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const item = data[0] as WP_REST_API_Tag;
|
|
|
|
return {
|
|
id: item.id.toString(),
|
|
name: item.name,
|
|
slug: item.slug,
|
|
};
|
|
}
|
|
|
|
async getCategories(options?: Cms.GetCategoriesOptions) {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (options?.limit) {
|
|
queryParams.append('per_page', options.limit.toString());
|
|
}
|
|
|
|
if (options?.offset) {
|
|
queryParams.append('offset', options.offset.toString());
|
|
}
|
|
|
|
if (options?.slugs) {
|
|
const slugs = options.slugs.join(',');
|
|
|
|
queryParams.append('slug', slugs);
|
|
}
|
|
|
|
const response = await fetch(
|
|
`${this.apiUrl}/wp-json/wp/v2/categories?${queryParams.toString()}`,
|
|
);
|
|
|
|
if (!response.ok) {
|
|
console.error('Failed to fetch categories', await response.json());
|
|
|
|
throw new Error('Failed to fetch categories');
|
|
}
|
|
|
|
const data = (await response.json()) as WP_REST_API_Category[];
|
|
|
|
return data.map((item) => ({
|
|
id: item.id.toString(),
|
|
name: item.name,
|
|
slug: item.slug,
|
|
}));
|
|
}
|
|
|
|
async getTags(options: GetTagsOptions) {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (options?.limit) {
|
|
queryParams.append('per_page', options.limit.toString());
|
|
}
|
|
|
|
if (options?.offset) {
|
|
queryParams.append('offset', options.offset.toString());
|
|
}
|
|
|
|
if (options?.slugs) {
|
|
const slugs = options.slugs.join(',');
|
|
queryParams.append('slug', slugs);
|
|
}
|
|
|
|
const response = await fetch(
|
|
`${this.apiUrl}/wp-json/wp/v2/tags?${queryParams.toString()}`,
|
|
);
|
|
|
|
if (!response.ok) {
|
|
console.error('Failed to fetch tags', await response.json());
|
|
|
|
throw new Error('Failed to fetch tags');
|
|
}
|
|
|
|
const data = (await response.json()) as WP_REST_API_Tag[];
|
|
|
|
return data.map((item) => ({
|
|
id: item.id.toString(),
|
|
name: item.name,
|
|
slug: item.slug,
|
|
}));
|
|
}
|
|
|
|
private async getTagsByIds(ids: number[]) {
|
|
const promises = ids.map((id) =>
|
|
fetch(`${this.apiUrl}/wp-json/wp/v2/tags/${id}`),
|
|
);
|
|
|
|
const responses = await Promise.all(promises);
|
|
|
|
const data = (await Promise.all(
|
|
responses.map((response) => response.json()),
|
|
)) as WP_REST_API_Tag[];
|
|
|
|
return data.map((item) => ({
|
|
id: item.id.toString(),
|
|
name: item.name,
|
|
slug: item.slug,
|
|
}));
|
|
}
|
|
|
|
private async getCategoriesByIds(ids: number[]) {
|
|
const promises = ids.map((id) =>
|
|
fetch(`${this.apiUrl}/wp-json/wp/v2/categories/${id}`),
|
|
);
|
|
|
|
const responses = await Promise.all(promises);
|
|
|
|
const data = (await Promise.all(
|
|
responses.map((response) => response.json()),
|
|
)) as WP_REST_API_Category[];
|
|
|
|
return data.map((item) => ({
|
|
id: item.id.toString(),
|
|
name: item.name,
|
|
slug: item.slug,
|
|
}));
|
|
}
|
|
|
|
private getFeaturedMedia(post: WP_REST_API_Post) {
|
|
const embedded = post._embedded ?? {
|
|
'wp:featuredmedia': [],
|
|
};
|
|
|
|
const media = embedded['wp:featuredmedia'] ?? [];
|
|
const item = media?.length > 0 ? media[0] : null;
|
|
|
|
return item
|
|
? (
|
|
item as {
|
|
source_url: string;
|
|
}
|
|
).source_url
|
|
: '';
|
|
}
|
|
}
|
|
|
|
function mapSortByParam(sortBy: string) {
|
|
switch (sortBy) {
|
|
case 'publishedAt':
|
|
return 'date';
|
|
case 'title':
|
|
return 'title';
|
|
case 'slug':
|
|
return 'slug';
|
|
case 'order':
|
|
return 'menu_order';
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
function mapToStatus(status: WP_Post_Status_Name): Cms.ContentItemStatus {
|
|
const Draft = 'draft' as WP_Post_Status_Name;
|
|
const Publish = 'publish' as WP_Post_Status_Name;
|
|
const Pending = 'pending' as WP_Post_Status_Name;
|
|
|
|
switch (status) {
|
|
case Draft:
|
|
return 'draft';
|
|
|
|
case Publish:
|
|
return 'published';
|
|
|
|
case Pending:
|
|
return 'pending';
|
|
|
|
default:
|
|
return 'draft';
|
|
}
|
|
}
|