Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
1cde47da68 | |||
a687b639d2 | |||
a0ffc7c4d7 | |||
d82c58e608 | |||
5d3cfe2f93 | |||
8de7fc795e |
25
changelog.md
25
changelog.md
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
Updated the project keywords and readme content for better clarity and SEO
|
||||||
|
|
||||||
|
- Improved the project description in `package.json` and `npmextra.json`.
|
||||||
|
- Added comprehensive usage instructions and examples in `readme.md`.
|
||||||
|
|
||||||
## 2024-07-01 - 1.0.2 - fix(core)
|
## 2024-07-01 - 1.0.2 - fix(core)
|
||||||
No changes in the project files
|
No changes in the project files
|
||||||
|
|
||||||
|
@@ -5,10 +5,20 @@
|
|||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "apiclient.xyz",
|
"gitscope": "apiclient.xyz",
|
||||||
"gitrepo": "ghost",
|
"gitrepo": "ghost",
|
||||||
"description": "an unofficial ghost api package",
|
"description": "An unofficial Ghost CMS API package enabling content and admin functionality for managing posts.",
|
||||||
"npmPackagename": "@apiclient.xyz/ghost",
|
"npmPackagename": "@apiclient.xyz/ghost",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"projectDomain": "apiclient.xyz"
|
"projectDomain": "apiclient.xyz",
|
||||||
|
"keywords": [
|
||||||
|
"Ghost CMS",
|
||||||
|
"API client",
|
||||||
|
"content management",
|
||||||
|
"admin API",
|
||||||
|
"content API",
|
||||||
|
"TypeScript",
|
||||||
|
"post management",
|
||||||
|
"blog management"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"npmci": {
|
||||||
|
38
package.json
38
package.json
@@ -1,30 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "@apiclient.xyz/ghost",
|
"name": "@apiclient.xyz/ghost",
|
||||||
"version": "1.0.2",
|
"version": "1.2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "an unofficial ghost api package",
|
"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",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"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",
|
||||||
@@ -48,5 +49,16 @@
|
|||||||
"cli.js",
|
"cli.js",
|
||||||
"npmextra.json",
|
"npmextra.json",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
]
|
],
|
||||||
|
"keywords": [
|
||||||
|
"Ghost CMS",
|
||||||
|
"API client",
|
||||||
|
"content management",
|
||||||
|
"admin API",
|
||||||
|
"content API",
|
||||||
|
"TypeScript",
|
||||||
|
"post management",
|
||||||
|
"blog management"
|
||||||
|
],
|
||||||
|
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
||||||
}
|
}
|
||||||
|
8631
pnpm-lock.yaml
generated
8631
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- mongodb-memory-server
|
||||||
|
- puppeteer
|
475
readme.md
475
readme.md
@@ -1,31 +1,458 @@
|
|||||||
# @apiclient.xyz/ghost
|
# @apiclient.xyz/ghost
|
||||||
an unofficial ghost api package
|
An unofficial Ghost API package
|
||||||
|
|
||||||
## Availabililty and Links
|
## Install
|
||||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@apiclient.xyz/ghost)
|
To install the @apiclient.xyz/ghost package, you can use npm or yarn. Make sure you're using a package manager that supports ESM and TypeScript.
|
||||||
* [gitlab.com (source)](https://code.foss.global/apiclient.xyz/ghost)
|
|
||||||
* [github.com (source mirror)](https://github.com/apiclient.xyz/ghost)
|
|
||||||
* [docs (typedoc)](https://apiclient.xyz.gitlab.io/ghost/)
|
|
||||||
|
|
||||||
## Status for master
|
NPM:
|
||||||
|
```bash
|
||||||
|
npm install @apiclient.xyz/ghost
|
||||||
|
```
|
||||||
|
|
||||||
Status Category | Status Badge
|
Yarn:
|
||||||
-- | --
|
```bash
|
||||||
GitLab Pipelines | [](https://lossless.cloud)
|
yarn add @apiclient.xyz/ghost
|
||||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
```
|
||||||
npm | [](https://lossless.cloud)
|
|
||||||
Snyk | [](https://lossless.cloud)
|
|
||||||
TypeScript Support | [](https://lossless.cloud)
|
|
||||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
|
||||||
Code Style | [](https://lossless.cloud)
|
|
||||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
|
||||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
|
||||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Use TypeScript for best in class intellisense
|
|
||||||
For further information read the linked docs at the top of this readme.
|
|
||||||
|
|
||||||
## Legal
|
Below is a detailed guide on how to use the `@apiclient.xyz/ghost` package in your TypeScript projects. We will cover everything from initialization to advanced usage scenarios.
|
||||||
> MIT licensed | **©** [Task Venture Capital GmbH](https://task.vc)
|
|
||||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
### Initialization
|
||||||
|
|
||||||
|
First, you need to import the necessary classes and initialize the Ghost instance with the required API keys.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Ghost } from '@apiclient.xyz/ghost';
|
||||||
|
|
||||||
|
// Initialize the Ghost instance
|
||||||
|
const ghostInstance = new Ghost({
|
||||||
|
baseUrl: 'https://your-ghost-url.com',
|
||||||
|
contentApiKey: 'your-content-api-key',
|
||||||
|
adminApiKey: 'your-admin-api-key'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
#### Fetching Posts
|
||||||
|
|
||||||
|
You can fetch posts with the following method. This will give you an array of `Post` instances.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Fetch all posts
|
||||||
|
const posts = await ghostInstance.getPosts();
|
||||||
|
|
||||||
|
// Print titles of all posts
|
||||||
|
posts.forEach(post => {
|
||||||
|
console.log(post.getTitle());
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fetching a Single Post by ID
|
||||||
|
|
||||||
|
To fetch a single post by its ID:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const postId = 'your-post-id';
|
||||||
|
const post = await ghostInstance.getPostById(postId);
|
||||||
|
|
||||||
|
console.log(post.getTitle());
|
||||||
|
console.log(post.getHtml());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Post Class Methods
|
||||||
|
|
||||||
|
The `Post` class has several methods that can be useful for different scenarios.
|
||||||
|
|
||||||
|
#### Getting Post Data
|
||||||
|
|
||||||
|
These methods allow you to retrieve various parts of the post data.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const postId = post.getId();
|
||||||
|
const postTitle = post.getTitle();
|
||||||
|
const postHtml = post.getHtml();
|
||||||
|
const postExcerpt = post.getExcerpt();
|
||||||
|
const postFeatureImage = post.getFeatureImage();
|
||||||
|
|
||||||
|
console.log(`ID: ${postId}`);
|
||||||
|
console.log(`Title: ${postTitle}`);
|
||||||
|
console.log(`HTML: ${postHtml}`);
|
||||||
|
console.log(`Excerpt: ${postExcerpt}`);
|
||||||
|
console.log(`Feature Image: ${postFeatureImage}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Updating a Post
|
||||||
|
|
||||||
|
To update a post, you can use the `update` method. Make sure you have the necessary permissions and fields.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const updatedPostData = {
|
||||||
|
...post.toJson(),
|
||||||
|
title: 'Updated Title',
|
||||||
|
html: '<p>Updated HTML content</p>'
|
||||||
|
};
|
||||||
|
|
||||||
|
await post.update(updatedPostData);
|
||||||
|
console.log('Post updated successfully');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Deleting a Post
|
||||||
|
|
||||||
|
To delete a post:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await post.delete();
|
||||||
|
console.log('Post deleted successfully');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Scenarios
|
||||||
|
|
||||||
|
#### Creating a New Post
|
||||||
|
|
||||||
|
You can create a new post using the `createPost` method of the `Ghost` class.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const newPostData = {
|
||||||
|
id: 'new-post-id',
|
||||||
|
title: 'New Post Title',
|
||||||
|
html: '<p>This is the content of the new post.</p>',
|
||||||
|
excerpt: 'New post excerpt',
|
||||||
|
feature_image: 'https://your-image-url.com/image.jpg'
|
||||||
|
};
|
||||||
|
|
||||||
|
const newPost = await ghostInstance.createPost(newPostData);
|
||||||
|
|
||||||
|
console.log('New post created successfully');
|
||||||
|
console.log(`ID: ${newPost.getId()}`);
|
||||||
|
console.log(`Title: ${newPost.getTitle()}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Error Handling
|
||||||
|
|
||||||
|
Both the `Ghost` and `Post` classes throw errors that you can catch and handle.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
const posts = await ghostInstance.getPosts();
|
||||||
|
console.log('Posts fetched successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching posts:', error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, for updating or deleting a post:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
await post.update({ title: 'Updated Title' });
|
||||||
|
console.log('Post updated successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating post:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await post.delete();
|
||||||
|
console.log('Post deleted successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting post:', error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fetching Posts with Filters and Options
|
||||||
|
|
||||||
|
The `getPosts` method can accept various filters and options.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const filteredPosts = await ghostInstance.getPosts({ limit: 10, include: 'tags,authors' });
|
||||||
|
|
||||||
|
filteredPosts.forEach(post => {
|
||||||
|
console.log(post.getTitle());
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
To give you a comprehensive understanding, let's look at a couple of example projects.
|
||||||
|
|
||||||
|
#### Example 1: A Basic Blog
|
||||||
|
|
||||||
|
In this scenario, we will create a simple script to fetch all posts and display their titles.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Ghost } from '@apiclient.xyz/ghost';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const ghostInstance = new Ghost({
|
||||||
|
baseUrl: 'https://your-ghost-url.com',
|
||||||
|
contentApiKey: 'your-content-api-key',
|
||||||
|
adminApiKey: 'your-admin-api-key'
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const posts = await ghostInstance.getPosts();
|
||||||
|
posts.forEach(post => console.log(post.getTitle()));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching posts:', error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 2: Post Management Tool
|
||||||
|
|
||||||
|
In this example, let's create a tool that can fetch, create, update, and delete posts.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Ghost, Post, IPostOptions } from '@apiclient.xyz/ghost';
|
||||||
|
|
||||||
|
const ghostInstance = new Ghost({
|
||||||
|
baseUrl: 'https://your-ghost-url.com',
|
||||||
|
contentApiKey: 'your-content-api-key',
|
||||||
|
adminApiKey: 'your-admin-api-key'
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Fetch posts
|
||||||
|
const posts = await ghostInstance.getPosts();
|
||||||
|
console.log('Fetched posts:');
|
||||||
|
posts.forEach(post => console.log(post.getTitle()));
|
||||||
|
|
||||||
|
// Create a new post
|
||||||
|
const newPostData: IPostOptions = {
|
||||||
|
id: 'new-post-id',
|
||||||
|
title: 'New Post Title',
|
||||||
|
html: '<p>This is the content of the new post.</p>',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newPost = await ghostInstance.createPost(newPostData);
|
||||||
|
console.log('New post created:', newPost.getTitle());
|
||||||
|
|
||||||
|
// Update the new post
|
||||||
|
const updatedPost = await newPost.update({ title: 'Updated Post Title' });
|
||||||
|
console.log('Post updated:', updatedPost.getTitle());
|
||||||
|
|
||||||
|
// Delete the new post
|
||||||
|
await updatedPost.delete();
|
||||||
|
console.log('Post deleted');
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
This package includes unit tests written using the `@push.rocks/tapbundle` and `@push.rocks/qenv` libraries. Here is how you can run them.
|
||||||
|
|
||||||
|
1. Install the development dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conclusion
|
||||||
|
|
||||||
|
The `@apiclient.xyz/ghost` package provides a comprehensive and type-safe way to interact with the Ghost CMS API using TypeScript. The features provided by the `Ghost` and `Post` classes allow for a wide range of interactions, from basic CRUD operations to advanced filtering and error handling.
|
||||||
|
|
||||||
|
For more information, please refer to the [documentation](https://apiclient.xyz.gitlab.io/ghost/).
|
||||||
|
undefined
|
124
test/test.ts
124
test/test.ts
@@ -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()
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@apiclient.xyz/ghost',
|
name: '@apiclient.xyz/ghost',
|
||||||
version: '1.0.2',
|
version: '1.2.0',
|
||||||
description: 'an unofficial ghost api package'
|
description: 'An unofficial Ghost CMS API package enabling content and admin functionality for managing posts.'
|
||||||
}
|
}
|
||||||
|
50
ts/classes.author.ts
Normal file
50
ts/classes.author.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
65
ts/classes.page.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
55
ts/classes.tag.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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';
|
Reference in New Issue
Block a user