2024-07-01 16:32:22 +02:00
|
|
|
import * as plugins from './ghost.plugins.js';
|
2025-10-07 13:53:58 +00:00
|
|
|
import { Post, type IPost, type ITag, type IAuthor } from './classes.post.js';
|
|
|
|
import { Author } from './classes.author.js';
|
|
|
|
import { Tag } from './classes.tag.js';
|
|
|
|
import { Page, type IPage } from './classes.page.js';
|
2024-07-01 16:32:22 +02:00
|
|
|
|
|
|
|
export interface IGhostConstructorOptions {
|
|
|
|
baseUrl: string;
|
|
|
|
contentApiKey: string;
|
|
|
|
adminApiKey: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Ghost {
|
|
|
|
public options: IGhostConstructorOptions;
|
|
|
|
public adminApi: any;
|
|
|
|
public contentApi: any;
|
|
|
|
|
|
|
|
constructor(optionsArg: IGhostConstructorOptions) {
|
|
|
|
this.options = optionsArg;
|
|
|
|
|
|
|
|
this.adminApi = new plugins.GhostAdminAPI({
|
|
|
|
url: this.options.baseUrl,
|
|
|
|
key: this.options.adminApiKey,
|
2024-07-06 17:47:06 +02:00
|
|
|
version: 'v3',
|
2024-07-01 16:32:22 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
this.contentApi = new plugins.GhostContentAPI({
|
|
|
|
url: this.options.baseUrl,
|
|
|
|
key: this.options.contentApiKey,
|
2024-07-06 17:47:06 +02:00
|
|
|
version: 'v3',
|
2024-07-01 16:32:22 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-10-07 13:53:58 +00:00
|
|
|
public async getPosts(optionsArg?: {
|
|
|
|
limit?: number;
|
|
|
|
tag?: string;
|
|
|
|
author?: string;
|
|
|
|
featured?: boolean;
|
|
|
|
filter?: string;
|
|
|
|
}): Promise<Post[]> {
|
2024-07-01 16:32:22 +02:00
|
|
|
try {
|
2025-10-07 13:53:58 +00:00
|
|
|
const limit = optionsArg?.limit || 1000;
|
|
|
|
const filters: string[] = [];
|
|
|
|
|
|
|
|
if (optionsArg?.tag) {
|
|
|
|
filters.push(`tag:${optionsArg.tag}`);
|
|
|
|
}
|
|
|
|
if (optionsArg?.author) {
|
|
|
|
filters.push(`author:${optionsArg.author}`);
|
|
|
|
}
|
|
|
|
if (optionsArg?.featured !== undefined) {
|
|
|
|
filters.push(`featured:${optionsArg.featured}`);
|
|
|
|
}
|
|
|
|
if (optionsArg?.filter) {
|
|
|
|
filters.push(optionsArg.filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
const filterString = filters.length > 0 ? filters.join('+') : undefined;
|
|
|
|
|
|
|
|
const postsData = await this.contentApi.posts.browse({
|
|
|
|
limit,
|
|
|
|
include: 'tags,authors',
|
|
|
|
...(filterString && { filter: filterString })
|
|
|
|
});
|
|
|
|
return postsData.map((postData: IPost) => new Post(this, postData));
|
2024-07-01 16:32:22 +02:00
|
|
|
} catch (error) {
|
|
|
|
console.error('Error fetching posts:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPostById(id: string): Promise<Post> {
|
|
|
|
try {
|
|
|
|
const postData = await this.contentApi.posts.read({ id });
|
|
|
|
return new Post(postData, this.adminApi);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching post with id ${id}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-10-07 13:53:58 +00:00
|
|
|
public async createPost(postData: IPost): Promise<Post> {
|
2024-07-01 16:32:22 +02:00
|
|
|
try {
|
|
|
|
const createdPostData = await this.adminApi.posts.add(postData);
|
|
|
|
return new Post(createdPostData, this.adminApi);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error creating post:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
2024-07-06 17:47:06 +02:00
|
|
|
|
|
|
|
public async createPostFromHtml(optionsArg: { title: string; html: string }) {
|
|
|
|
const postData = await this.adminApi.posts.add(
|
|
|
|
{ title: optionsArg.title, html: optionsArg.html },
|
|
|
|
{ source: 'html' } // Tell the API to use HTML as the content source, instead of Lexical
|
|
|
|
);
|
|
|
|
return new Post(this, postData);
|
|
|
|
}
|
2025-10-07 13:53:58 +00:00
|
|
|
|
|
|
|
public async getTags(optionsArg?: { filter?: string; limit?: number }): Promise<ITag[]> {
|
|
|
|
try {
|
|
|
|
const limit = optionsArg?.limit || 1000;
|
|
|
|
const tagsData = await this.contentApi.tags.browse({ limit });
|
|
|
|
|
|
|
|
if (optionsArg?.filter) {
|
|
|
|
const matcher = new plugins.smartmatch.SmartMatch(optionsArg.filter);
|
|
|
|
return tagsData.filter((tag: ITag) => matcher.match(tag.slug));
|
|
|
|
}
|
|
|
|
|
|
|
|
return tagsData;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error fetching tags:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getTagById(id: string): Promise<Tag> {
|
|
|
|
try {
|
|
|
|
const tagData = await this.contentApi.tags.read({ id });
|
|
|
|
return new Tag(this, tagData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching tag with id ${id}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getTagBySlug(slug: string): Promise<Tag> {
|
|
|
|
try {
|
|
|
|
const tagData = await this.contentApi.tags.read({ slug });
|
|
|
|
return new Tag(this, tagData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching tag with slug ${slug}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createTag(tagData: Partial<ITag>): Promise<Tag> {
|
|
|
|
try {
|
|
|
|
const createdTagData = await this.adminApi.tags.add(tagData);
|
|
|
|
return new Tag(this, createdTagData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error creating tag:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAuthors(optionsArg?: { filter?: string; limit?: number }): Promise<Author[]> {
|
|
|
|
try {
|
|
|
|
const limit = optionsArg?.limit || 1000;
|
|
|
|
const authorsData = await this.contentApi.authors.browse({ limit });
|
|
|
|
|
|
|
|
if (optionsArg?.filter) {
|
|
|
|
const matcher = new plugins.smartmatch.SmartMatch(optionsArg.filter);
|
|
|
|
return authorsData
|
|
|
|
.filter((author: IAuthor) => matcher.match(author.slug))
|
|
|
|
.map((author: IAuthor) => new Author(this, author));
|
|
|
|
}
|
|
|
|
|
|
|
|
return authorsData.map((author: IAuthor) => new Author(this, author));
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error fetching authors:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAuthorById(id: string): Promise<Author> {
|
|
|
|
try {
|
|
|
|
const authorData = await this.contentApi.authors.read({ id });
|
|
|
|
return new Author(this, authorData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching author with id ${id}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAuthorBySlug(slug: string): Promise<Author> {
|
|
|
|
try {
|
|
|
|
const authorData = await this.contentApi.authors.read({ slug });
|
|
|
|
return new Author(this, authorData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching author with slug ${slug}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPages(optionsArg?: { limit?: number; filter?: string }): Promise<Page[]> {
|
|
|
|
try {
|
|
|
|
const limit = optionsArg?.limit || 1000;
|
|
|
|
const pagesData = await this.contentApi.pages.browse({ limit, include: 'tags,authors' });
|
|
|
|
|
|
|
|
if (optionsArg?.filter) {
|
|
|
|
const matcher = new plugins.smartmatch.SmartMatch(optionsArg.filter);
|
|
|
|
return pagesData
|
|
|
|
.filter((page: IPage) => matcher.match(page.slug))
|
|
|
|
.map((page: IPage) => new Page(this, page));
|
|
|
|
}
|
|
|
|
|
|
|
|
return pagesData.map((pageData: IPage) => new Page(this, pageData));
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error fetching pages:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPageById(id: string): Promise<Page> {
|
|
|
|
try {
|
|
|
|
const pageData = await this.contentApi.pages.read({ id });
|
|
|
|
return new Page(this, pageData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching page with id ${id}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPageBySlug(slug: string): Promise<Page> {
|
|
|
|
try {
|
|
|
|
const pageData = await this.contentApi.pages.read({ slug });
|
|
|
|
return new Page(this, pageData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(`Error fetching page with slug ${slug}:`, error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createPage(pageData: Partial<IPage>): Promise<Page> {
|
|
|
|
try {
|
|
|
|
const createdPageData = await this.adminApi.pages.add(pageData);
|
|
|
|
return new Page(this, createdPageData);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error creating page:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async searchPosts(query: string, optionsArg?: { limit?: number }): Promise<Post[]> {
|
|
|
|
try {
|
|
|
|
const limit = optionsArg?.limit || 100;
|
|
|
|
const postsData = await this.contentApi.posts.browse({
|
|
|
|
limit,
|
|
|
|
filter: `title:~'${query}'`,
|
|
|
|
include: 'tags,authors'
|
|
|
|
});
|
|
|
|
return postsData.map((postData: IPost) => new Post(this, postData));
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error searching posts:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async uploadImage(filePath: string): Promise<string> {
|
|
|
|
try {
|
|
|
|
const result = await this.adminApi.images.upload({ file: filePath });
|
|
|
|
return result.url;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error uploading image:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async bulkUpdatePosts(postIds: string[], updates: Partial<IPost>): Promise<Post[]> {
|
|
|
|
try {
|
|
|
|
const updatePromises = postIds.map(async (id) => {
|
|
|
|
const post = await this.getPostById(id);
|
|
|
|
return post.update({ ...post.postData, ...updates, id });
|
|
|
|
});
|
|
|
|
return await Promise.all(updatePromises);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error bulk updating posts:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async bulkDeletePosts(postIds: string[]): Promise<void> {
|
|
|
|
try {
|
|
|
|
const deletePromises = postIds.map(async (id) => {
|
|
|
|
const post = await this.getPostById(id);
|
|
|
|
return post.delete();
|
|
|
|
});
|
|
|
|
await Promise.all(deletePromises);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error bulk deleting posts:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getRelatedPosts(postId: string, limit: number = 5): Promise<Post[]> {
|
|
|
|
try {
|
|
|
|
const post = await this.getPostById(postId);
|
|
|
|
const tags = post.postData.tags;
|
|
|
|
|
|
|
|
if (!tags || !Array.isArray(tags) || tags.length === 0) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const tagSlugs = tags.map(tag => tag.slug).join(',');
|
|
|
|
const postsData = await this.contentApi.posts.browse({
|
|
|
|
limit,
|
|
|
|
filter: `tag:[${tagSlugs}]+id:-${postId}`,
|
|
|
|
include: 'tags,authors'
|
|
|
|
});
|
|
|
|
|
|
|
|
return postsData.map((postData: IPost) => new Post(this, postData));
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error fetching related posts:', error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
2024-07-01 16:32:22 +02:00
|
|
|
}
|