6 Commits

Author SHA1 Message Date
cf10f51089 1.3.0
Some checks failed
Default (tags) / security (push) Failing after 13s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-07 13:55:01 +00:00
2f05d0edc4 feat(core): Add Ghost CMS API client classes and README documentation 2025-10-07 13:55:00 +00:00
1cde47da68 1.2.0
Some checks failed
Default (tags) / security (push) Failing after 29s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-07 13:53:58 +00:00
a687b639d2 feat(ghost): Implement Tag, Author and Page models; add advanced filtering, search, bulk operations, image upload, related-posts, update tests and bump dependencies 2025-10-07 13:53:58 +00:00
a0ffc7c4d7 1.1.0
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 16s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2024-07-06 17:47:07 +02:00
d82c58e608 feat(core): Enhanced post fetching and creation with additional metadata and support for HTML source 2024-07-06 17:47:06 +02:00
14 changed files with 7169 additions and 2374 deletions

View File

@@ -1,5 +1,33 @@
# Changelog # Changelog
## 2025-10-07 - 1.3.0 - feat(core)
Add Ghost CMS API client classes and README documentation
- Implemented core TypeScript classes: Ghost, Post, Tag, Author and Page with CRUD methods and helpers
- Added TypeScript interfaces for posts, tags, authors and pages to provide typings
- Included comprehensive README with installation, usage examples and API method documentation
- Added unit test scaffold under test/test.ts
- Updated package metadata and commitinfo (version 1.2.0)
## 2025-10-07 - 1.2.0 - feat(ghost)
Implement Tag, Author and Page models; add advanced filtering, search, bulk operations, image upload, related-posts, update tests and bump dependencies
- Add fully implemented Author, Tag and Page classes with CRUD methods
- Enhance Ghost class with: improved getPosts filtering (tag/author/featured/custom filters), getTags/getAuthors/getPages with minimatch filtering, getTag/getAuthor/getPage by id/slug, createTag/createPage, searchPosts, uploadImage, bulkUpdatePosts, bulkDeletePosts and getRelatedPosts
- Refactor Post types (IPost) and update Post class to use the new type and consistent constructors/serialization
- Export new modules (author, tag, page) from index.ts
- Integrate @push.rocks/smartmatch for pattern filtering and expose it via ghost.plugins
- Update tests to exercise tags, authors, pages, filtering, search and related posts; change test baseUrl to localhost and adjust imports
- Bump devDependencies and dependencies versions, adjust test script, add packageManager metadata and add pnpm-workspace.yaml
## 2024-07-06 - 1.1.0 - feat(core)
Enhanced post fetching and creation with additional metadata and support for HTML source
- Added support for fetching posts with included tags and authors.
- Implemented helper method to create posts directly from HTML content.
- Enhanced Post class to include additional metadata such as visibility, created_at, updated_at, published_at, etc.
- Modified getPosts method in Ghost class to include tags and authors in the response.
## 2024-07-01 - 1.0.3 - fix(docs) ## 2024-07-01 - 1.0.3 - fix(docs)
Updated the project keywords and readme content for better clarity and SEO Updated the project keywords and readme content for better clarity and SEO

View File

@@ -1,6 +1,6 @@
{ {
"name": "@apiclient.xyz/ghost", "name": "@apiclient.xyz/ghost",
"version": "1.0.3", "version": "1.3.0",
"private": false, "private": false,
"description": "An unofficial Ghost CMS API package enabling content and admin functionality for managing posts.", "description": "An unofficial Ghost CMS API package enabling content and admin functionality for managing posts.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@@ -9,22 +9,23 @@
"author": "Task Venture Capital GmbH", "author": "Task Venture Capital GmbH",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "(tstest test/ --web)", "test": "(tstest test/ --verbose)",
"build": "(tsbuild --web --allowimplicitany)", "build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "(tsdoc)" "buildDocs": "(tsdoc)"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.82", "@git.zone/tsbuild": "^2.6.8",
"@git.zone/tsbundle": "^2.0.5", "@git.zone/tsbundle": "^2.5.1",
"@git.zone/tsrun": "^1.2.49", "@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.44", "@git.zone/tstest": "^2.3.8",
"@push.rocks/qenv": "^6.0.5", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/tapbundle": "^5.0.15", "@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^20.14.9" "@types/node": "^22.12.0"
}, },
"dependencies": { "dependencies": {
"@tryghost/admin-api": "^1.13.12", "@push.rocks/smartmatch": "^2.0.0",
"@tryghost/content-api": "^1.11.21" "@tryghost/admin-api": "^1.14.0",
"@tryghost/content-api": "^1.12.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -58,5 +59,6 @@
"TypeScript", "TypeScript",
"post management", "post management",
"blog management" "blog management"
] ],
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
} }

8631
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

196
readme.md
View File

@@ -172,6 +172,202 @@ filteredPosts.forEach(post => {
}); });
``` ```
#### Fetching Tags
You can fetch all tags from your Ghost site using the `getTags` method.
```typescript
const tags = await ghostInstance.getTags();
tags.forEach(tag => {
console.log(`${tag.name} (${tag.slug})`);
});
```
#### Fetching Tags with Minimatch Filtering
The `getTags` method supports filtering tags using minimatch patterns on tag slugs.
```typescript
// Fetch all tags starting with 'tech-'
const techTags = await ghostInstance.getTags({ filter: 'tech-*' });
// Fetch all tags containing 'blog'
const blogTags = await ghostInstance.getTags({ filter: '*blog*' });
// Fetch with limit
const limitedTags = await ghostInstance.getTags({ limit: 10, filter: 'a*' });
limitedTags.forEach(tag => {
console.log(tag.name);
});
```
#### Working with Tag Class
Fetch individual tags and manage them with the `Tag` class.
```typescript
// Get tag by slug
const tag = await ghostInstance.getTagBySlug('tech');
console.log(tag.getName());
console.log(tag.getDescription());
// Create a new tag
const newTag = await ghostInstance.createTag({
name: 'JavaScript',
slug: 'javascript',
description: 'Posts about JavaScript'
});
// Update a tag
await newTag.update({
description: 'All things JavaScript'
});
// Delete a tag
await newTag.delete();
```
#### Fetching Authors
Manage and filter authors with minimatch patterns.
```typescript
// Get all authors
const authors = await ghostInstance.getAuthors();
authors.forEach(author => {
console.log(`${author.getName()} (${author.getSlug()})`);
});
// Filter authors with minimatch
const filteredAuthors = await ghostInstance.getAuthors({ filter: 'j*' });
// Get author by slug
const author = await ghostInstance.getAuthorBySlug('john-doe');
console.log(author.getBio());
// Update author
await author.update({
bio: 'Updated bio information'
});
```
#### Working with Pages
Ghost pages are similar to posts but for static content.
```typescript
// Get all pages
const pages = await ghostInstance.getPages();
pages.forEach(page => {
console.log(page.getTitle());
});
// Filter pages with minimatch
const filteredPages = await ghostInstance.getPages({ filter: 'about*' });
// Get page by slug
const page = await ghostInstance.getPageBySlug('about');
console.log(page.getHtml());
// Create a new page
const newPage = await ghostInstance.createPage({
title: 'Contact Us',
html: '<p>Contact information...</p>'
});
// Update a page
await newPage.update({
html: '<p>Updated contact information...</p>'
});
// Delete a page
await newPage.delete();
```
#### Enhanced Post Filtering
The `getPosts` method now supports multiple filtering options.
```typescript
// Filter by tag
const techPosts = await ghostInstance.getPosts({ tag: 'tech', limit: 10 });
// Filter by author
const authorPosts = await ghostInstance.getPosts({ author: 'john-doe' });
// Get only featured posts
const featuredPosts = await ghostInstance.getPosts({ featured: true });
// Combine multiple filters
const filteredPosts = await ghostInstance.getPosts({
tag: 'javascript',
featured: true,
limit: 5
});
```
#### Searching Posts
Full-text search across post titles and excerpts.
```typescript
// Search for posts containing a keyword
const searchResults = await ghostInstance.searchPosts('ghost api', { limit: 10 });
searchResults.forEach(post => {
console.log(post.getTitle());
console.log(post.getExcerpt());
});
```
#### Finding Related Posts
Get posts with similar tags.
```typescript
const post = await ghostInstance.getPostById('some-post-id');
// Get related posts based on tags
const relatedPosts = await ghostInstance.getRelatedPosts(post.getId(), 5);
relatedPosts.forEach(relatedPost => {
console.log(`Related: ${relatedPost.getTitle()}`);
});
```
#### Image Upload
Upload images to your Ghost site.
```typescript
// Upload an image file
const imageUrl = await ghostInstance.uploadImage('/path/to/image.jpg');
// Use the uploaded image URL in a post
await ghostInstance.createPost({
title: 'Post with Image',
html: '<p>Content here</p>',
feature_image: imageUrl
});
```
#### Bulk Operations
Perform operations on multiple posts at once.
```typescript
// Bulk update posts
const postIds = ['id1', 'id2', 'id3'];
await ghostInstance.bulkUpdatePosts(postIds, {
featured: true
});
// Bulk delete posts
await ghostInstance.bulkDeletePosts(['id4', 'id5']);
```
### Example Projects ### Example Projects
To give you a comprehensive understanding, let's look at a couple of example projects. To give you a comprehensive understanding, let's look at a couple of example projects.

View File

@@ -1,14 +1,17 @@
import { expect, expectAsync, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@push.rocks/tapbundle';
import * as qenv from '@push.rocks/qenv'; import * as qenv from '@push.rocks/qenv';
const testQenv = new qenv.Qenv('./', './.nogit/'); const testQenv = new qenv.Qenv('./', './.nogit/');
import * as ghost from '../ts/index.js' import * as ghost from '../ts/index.js';
// make sure we can import the IPost type
import {type IPost} from '../ts/index.js';
let testGhostInstance: ghost.Ghost; let testGhostInstance: ghost.Ghost;
tap.test('should create a valid instance of Ghost', async () => { tap.test('should create a valid instance of Ghost', async () => {
testGhostInstance = new ghost.Ghost({ testGhostInstance = new ghost.Ghost({
baseUrl: 'https://coffee.link', baseUrl: 'http://localhost:2368',
adminApiKey: await testQenv.getEnvVarOnDemand('ADMIN_APIKEY'), adminApiKey: await testQenv.getEnvVarOnDemand('ADMIN_APIKEY'),
contentApiKey: await testQenv.getEnvVarOnDemand('CONTENT_APIKEY'), contentApiKey: await testQenv.getEnvVarOnDemand('CONTENT_APIKEY'),
}); });
@@ -19,7 +22,120 @@ tap.test('should get posts', async () => {
const posts = await testGhostInstance.getPosts(); const posts = await testGhostInstance.getPosts();
expect(posts).toBeArray(); expect(posts).toBeArray();
expect(posts[0]).toBeInstanceOf(ghost.Post); expect(posts[0]).toBeInstanceOf(ghost.Post);
console.log(posts.map((post) => post.getTitle())); console.log(JSON.stringify(posts[0].postData, null, 2));
posts.map((post) => {
// console.log(JSON.stringify(post.postData, null, 2));
console.log(`-> ${post.getTitle()}`);
console.log(`by ${post.getAuthor().name}`)
console.log(post.getExcerpt());
console.log(`===============`)
}) })
})
tap.test('should get all tags', async () => {
const tags = await testGhostInstance.getTags();
expect(tags).toBeArray();
console.log(`Found ${tags.length} tags:`);
tags.forEach((tag) => {
console.log(`-> ${tag.name} (${tag.slug})`);
});
});
tap.test('should filter tags with minimatch pattern', async () => {
const allTags = await testGhostInstance.getTags();
if (allTags.length > 0) {
const firstTagSlug = allTags[0].slug;
const pattern = `${firstTagSlug.charAt(0)}*`;
const filteredTags = await testGhostInstance.getTags({ filter: pattern });
expect(filteredTags).toBeArray();
console.log(`Filtered tags with pattern '${pattern}':`);
filteredTags.forEach((tag) => {
console.log(`-> ${tag.name} (${tag.slug})`);
expect(tag.slug).toMatch(new RegExp(`^${firstTagSlug.charAt(0)}`));
});
} else {
console.log('No tags available to test filtering');
}
});
tap.test('should get tag by slug', async () => {
const tags = await testGhostInstance.getTags({ limit: 1 });
if (tags.length > 0) {
const tag = await testGhostInstance.getTagBySlug(tags[0].slug);
expect(tag).toBeInstanceOf(ghost.Tag);
console.log(`Got tag: ${tag.getName()} (${tag.getSlug()})`);
}
});
tap.test('should get all authors', async () => {
const authors = await testGhostInstance.getAuthors();
expect(authors).toBeArray();
console.log(`Found ${authors.length} authors:`);
authors.forEach((author) => {
console.log(`-> ${author.getName()} (${author.getSlug()})`);
});
});
tap.test('should filter authors with minimatch pattern', async () => {
const authors = await testGhostInstance.getAuthors();
if (authors.length > 0) {
const firstAuthorSlug = authors[0].getSlug();
const pattern = `${firstAuthorSlug.charAt(0)}*`;
const filteredAuthors = await testGhostInstance.getAuthors({ filter: pattern });
expect(filteredAuthors).toBeArray();
console.log(`Filtered authors with pattern '${pattern}':`);
filteredAuthors.forEach((author) => {
console.log(`-> ${author.getName()} (${author.getSlug()})`);
});
}
});
tap.test('should get all pages', async () => {
const pages = await testGhostInstance.getPages();
expect(pages).toBeArray();
console.log(`Found ${pages.length} pages:`);
pages.forEach((page) => {
console.log(`-> ${page.getTitle()} (${page.getSlug()})`);
});
});
tap.test('should filter posts by tag', async () => {
const tags = await testGhostInstance.getTags({ limit: 1 });
if (tags.length > 0) {
const posts = await testGhostInstance.getPosts({ tag: tags[0].slug, limit: 5 });
expect(posts).toBeArray();
console.log(`Found ${posts.length} posts with tag '${tags[0].name}'`);
}
});
tap.test('should filter posts by featured status', async () => {
const featuredPosts = await testGhostInstance.getPosts({ featured: true, limit: 5 });
expect(featuredPosts).toBeArray();
console.log(`Found ${featuredPosts.length} featured posts`);
});
tap.test('should search posts', async () => {
const searchResults = await testGhostInstance.searchPosts('the', { limit: 5 });
expect(searchResults).toBeArray();
console.log(`Found ${searchResults.length} posts matching 'the':`);
searchResults.forEach((post) => {
console.log(`-> ${post.getTitle()}`);
});
});
tap.test('should get related posts', async () => {
const posts = await testGhostInstance.getPosts({ limit: 1 });
if (posts.length > 0) {
const relatedPosts = await testGhostInstance.getRelatedPosts(posts[0].getId(), 3);
expect(relatedPosts).toBeArray();
console.log(`Found ${relatedPosts.length} related posts for '${posts[0].getTitle()}'`);
relatedPosts.forEach((post) => {
console.log(`-> ${post.getTitle()}`);
});
}
});
tap.start() tap.start()

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@apiclient.xyz/ghost', name: '@apiclient.xyz/ghost',
version: '1.0.3', version: '1.3.0',
description: 'An unofficial Ghost CMS API package enabling content and admin functionality for managing posts.' description: 'An unofficial Ghost CMS API package enabling content and admin functionality for managing posts.'
} }

50
ts/classes.author.ts Normal file
View File

@@ -0,0 +1,50 @@
import type { Ghost } from './classes.ghost.js';
import type { IAuthor } from './classes.post.js';
export class Author {
public ghostInstanceRef: Ghost;
public authorData: IAuthor;
constructor(ghostInstanceRefArg: Ghost, authorData: IAuthor) {
this.ghostInstanceRef = ghostInstanceRefArg;
this.authorData = authorData;
}
public getId(): string {
return this.authorData.id;
}
public getName(): string {
return this.authorData.name;
}
public getSlug(): string {
return this.authorData.slug;
}
public getProfileImage(): string | undefined {
return this.authorData.profile_image;
}
public getBio(): string | undefined {
return this.authorData.bio;
}
public toJson(): IAuthor {
return this.authorData;
}
public async update(authorData: Partial<IAuthor>): Promise<Author> {
try {
const updatedAuthorData = await this.ghostInstanceRef.adminApi.users.edit({
...authorData,
id: this.getId()
});
this.authorData = updatedAuthorData;
return this;
} catch (error) {
console.error('Error updating author:', error);
throw error;
}
}
}

View File

@@ -1,5 +1,8 @@
import * as plugins from './ghost.plugins.js'; import * as plugins from './ghost.plugins.js';
import { Post, type IPostOptions } from './classes.post.js'; 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';
export interface IGhostConstructorOptions { export interface IGhostConstructorOptions {
baseUrl: string; baseUrl: string;
@@ -18,20 +21,48 @@ export class Ghost {
this.adminApi = new plugins.GhostAdminAPI({ this.adminApi = new plugins.GhostAdminAPI({
url: this.options.baseUrl, url: this.options.baseUrl,
key: this.options.adminApiKey, key: this.options.adminApiKey,
version: "v3" version: 'v3',
}); });
this.contentApi = new plugins.GhostContentAPI({ this.contentApi = new plugins.GhostContentAPI({
url: this.options.baseUrl, url: this.options.baseUrl,
key: this.options.contentApiKey, key: this.options.contentApiKey,
version: "v3" version: 'v3',
}); });
} }
public async getPosts(limit: number = 1000): Promise<Post[]> { public async getPosts(optionsArg?: {
limit?: number;
tag?: string;
author?: string;
featured?: boolean;
filter?: string;
}): Promise<Post[]> {
try { try {
const postsData = await this.contentApi.posts.browse({ limit }); const limit = optionsArg?.limit || 1000;
return postsData.map((postData: IPostOptions) => new Post(this, postData)); 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));
} catch (error) { } catch (error) {
console.error('Error fetching posts:', error); console.error('Error fetching posts:', error);
throw error; throw error;
@@ -48,7 +79,7 @@ export class Ghost {
} }
} }
public async createPost(postData: IPostOptions): Promise<Post> { public async createPost(postData: IPost): Promise<Post> {
try { try {
const createdPostData = await this.adminApi.posts.add(postData); const createdPostData = await this.adminApi.posts.add(postData);
return new Post(createdPostData, this.adminApi); return new Post(createdPostData, this.adminApi);
@@ -57,4 +88,221 @@ export class Ghost {
throw error; throw error;
} }
} }
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);
}
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;
}
}
} }

65
ts/classes.page.ts Normal file
View File

@@ -0,0 +1,65 @@
import type { Ghost } from './classes.ghost.js';
import type { IPost, IAuthor } from './classes.post.js';
export interface IPage extends IPost {}
export class Page {
public ghostInstanceRef: Ghost;
public pageData: IPage;
constructor(ghostInstanceRefArg: Ghost, pageData: IPage) {
this.ghostInstanceRef = ghostInstanceRefArg;
this.pageData = pageData;
}
public getId(): string {
return this.pageData.id;
}
public getTitle(): string {
return this.pageData.title;
}
public getHtml(): string {
return this.pageData.html;
}
public getSlug(): string {
return this.pageData.slug;
}
public getFeatureImage(): string | undefined {
return this.pageData.feature_image;
}
public getAuthor(): IAuthor {
return this.pageData.primary_author;
}
public toJson(): IPage {
return this.pageData;
}
public async update(pageData: Partial<IPage>): Promise<Page> {
try {
const updatedPageData = await this.ghostInstanceRef.adminApi.pages.edit({
...pageData,
id: this.getId()
});
this.pageData = updatedPageData;
return this;
} catch (error) {
console.error('Error updating page:', error);
throw error;
}
}
public async delete(): Promise<void> {
try {
await this.ghostInstanceRef.adminApi.pages.delete({ id: this.getId() });
} catch (error) {
console.error(`Error deleting page with id ${this.getId()}:`, error);
throw error;
}
}
}

View File

@@ -1,20 +1,91 @@
import type { Ghost } from './classes.ghost.js'; import type { Ghost } from './classes.ghost.js';
import * as plugins from './ghost.plugins.js'; import * as plugins from './ghost.plugins.js';
export interface IPostOptions { export interface IAuthor {
id: string; id: string;
title: string; name: string;
html: string; slug: string;
excerpt?: string; profile_image?: string;
cover_image?: string;
bio?: string;
website?: string;
location?: string;
facebook?: string;
twitter?: string;
meta_title?: string;
meta_description?: string;
url: string;
}
export interface ITag {
id: string;
name: string;
slug: string;
description?: string;
feature_image?: string; feature_image?: string;
visibility: string;
og_image?: string;
og_title?: string;
og_description?: string;
twitter_image?: string;
twitter_title?: string;
twitter_description?: string;
meta_title?: string;
meta_description?: string;
codeinjection_head?: string;
codeinjection_foot?: string;
canonical_url?: string;
accent_color?: string;
url: string;
}
export interface IPost {
id: string;
uuid: string;
title: string;
slug: string;
html: string;
comment_id: string;
feature_image?: string;
featured: boolean;
visibility: string;
created_at: string;
updated_at: string;
published_at: string;
custom_excerpt?: string;
codeinjection_head?: string | null;
codeinjection_foot?: string | null;
custom_template?: string | null;
canonical_url?: string | null;
url: string;
excerpt?: string;
reading_time: number;
access: boolean;
comments: boolean;
og_image?: string | null;
og_title?: string | null;
og_description?: string | null;
twitter_image?: string | null;
twitter_title?: string | null;
twitter_description?: string | null;
meta_title?: string | null;
meta_description?: string | null;
email_subject?: string | null;
frontmatter?: string | null;
feature_image_alt?: string | null;
feature_image_caption?: string | null;
authors: IAuthor[];
tags: ITag[];
primary_author: IAuthor;
primary_tag: ITag;
[key: string]: any; // To allow for additional properties [key: string]: any; // To allow for additional properties
} }
export class Post { export class Post {
public ghostInstanceRef: Ghost; public ghostInstanceRef: Ghost;
private postData: IPostOptions; public postData: IPost;
constructor(ghostInstanceRefArg: Ghost, postData: IPostOptions) { constructor(ghostInstanceRefArg: Ghost, postData: IPost) {
this.ghostInstanceRef = ghostInstanceRefArg; this.ghostInstanceRef = ghostInstanceRefArg;
this.postData = postData; this.postData = postData;
} }
@@ -39,11 +110,15 @@ export class Post {
return this.postData.feature_image; return this.postData.feature_image;
} }
public toJson(): IPostOptions { public getAuthor(): IAuthor {
return this.postData.primary_author;
}
public toJson(): IPost {
return this.postData; return this.postData;
} }
public async update(postData: IPostOptions): Promise<Post> { public async update(postData: IPost): Promise<Post> {
try { try {
const updatedPostData = await this.ghostInstanceRef.adminApi.posts.edit(postData); const updatedPostData = await this.ghostInstanceRef.adminApi.posts.edit(postData);
this.postData = updatedPostData; this.postData = updatedPostData;

55
ts/classes.tag.ts Normal file
View File

@@ -0,0 +1,55 @@
import type { Ghost } from './classes.ghost.js';
import type { ITag } from './classes.post.js';
export class Tag {
public ghostInstanceRef: Ghost;
public tagData: ITag;
constructor(ghostInstanceRefArg: Ghost, tagData: ITag) {
this.ghostInstanceRef = ghostInstanceRefArg;
this.tagData = tagData;
}
public getId(): string {
return this.tagData.id;
}
public getName(): string {
return this.tagData.name;
}
public getSlug(): string {
return this.tagData.slug;
}
public getDescription(): string | undefined {
return this.tagData.description;
}
public toJson(): ITag {
return this.tagData;
}
public async update(tagData: Partial<ITag>): Promise<Tag> {
try {
const updatedTagData = await this.ghostInstanceRef.adminApi.tags.edit({
...tagData,
id: this.getId()
});
this.tagData = updatedTagData;
return this;
} catch (error) {
console.error('Error updating tag:', error);
throw error;
}
}
public async delete(): Promise<void> {
try {
await this.ghostInstanceRef.adminApi.tags.delete({ id: this.getId() });
} catch (error) {
console.error(`Error deleting tag with id ${this.getId()}:`, error);
throw error;
}
}
}

View File

@@ -1,7 +1,9 @@
import GhostContentAPI from '@tryghost/content-api'; import GhostContentAPI from '@tryghost/content-api';
import GhostAdminAPI from '@tryghost/admin-api'; import GhostAdminAPI from '@tryghost/admin-api';
import * as smartmatch from '@push.rocks/smartmatch';
export { export {
GhostContentAPI, GhostContentAPI,
GhostAdminAPI GhostAdminAPI,
smartmatch
} }

View File

@@ -1,2 +1,5 @@
export * from './classes.ghost.js'; export * from './classes.ghost.js';
export * from './classes.post.js'; export * from './classes.post.js';
export * from './classes.author.js';
export * from './classes.tag.js';
export * from './classes.page.js';