n8n-nodes-blog-post 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,15 @@
1
- # n8n Blog Post Node
1
+ # n8n Kepha14 Blog Node
2
2
 
3
- A custom n8n node for posting blog content to the kepha14.dev API.
3
+ A custom n8n node for creating and updating blog posts on kepha14.dev API.
4
4
 
5
5
  ## Features
6
6
 
7
- - Post blog content with all required fields
7
+ - **Create** blog posts with all required fields
8
+ - **Update** existing blog posts by ID or slug
8
9
  - Support for optional fields (excerpt, cover image, tags, category, SEO metadata)
9
- - Bearer token authentication
10
+ - Bearer token authentication with configurable base URL
10
11
  - Error handling with continue on fail option
12
+ - Resource-based operations matching the API structure
11
13
 
12
14
  ## Installation
13
15
 
@@ -37,48 +39,93 @@ npm link n8n-nodes-blog-post
37
39
 
38
40
  ### Setting Up Credentials
39
41
 
40
- Before using the Blog Post node, you need to create credentials:
42
+ Before using the Blog node, you need to create credentials:
41
43
 
42
44
  1. In n8n, go to **Credentials** → **Add Credential**
43
- 2. Search for **Blog API** and select it
45
+ 2. Search for **Kepha14 API** and select it
44
46
  3. Fill in the following fields:
45
- - **API URL**: The endpoint URL
46
- - **Secret**: Your Bearer token for API authentication
47
+ - **Base URL**: The base URL of the API (e.g., `https://kepha14.dev`)
48
+ - **API Token**: Your Bearer token for API authentication (from `N8N_WEBHOOK_SECRET`)
47
49
  4. Save the credentials
48
50
 
49
51
  ### Using the Node
50
52
 
51
- The Blog Post node allows you to:
52
- - Set blog post title and slug (required)
53
- - Add full content (required)
54
- - Optionally add excerpt, cover image, tags, category
55
- - Set SEO metadata (meta title, meta description)
56
- - Specify author email
57
- - Configure published status
53
+ The Blog node supports two operations:
54
+
55
+ #### Create Blog Post
56
+
57
+ 1. Add the **Blog** node to your workflow
58
+ 2. Select **Operation**: **Create**
59
+ 3. Fill in required fields:
60
+ - **Title**: The title of the blog post
61
+ - **Slug**: URL-friendly slug (must be unique)
62
+ - **Content**: Full blog post content (HTML/Markdown)
63
+ 4. Optionally configure:
64
+ - **Excerpt**: Short summary
65
+ - **Cover Image URL**: Featured image URL
66
+ - **Published**: Set to `true` to publish immediately (default: `false`)
67
+ - **Tags**: Array of tags
68
+ - **Category**: Category name
69
+ - **Meta Title**: SEO meta title
70
+ - **Meta Description**: SEO meta description
71
+ - **Author Email**: Email of existing user
72
+
73
+ #### Update Blog Post
74
+
75
+ 1. Add the **Blog** node to your workflow
76
+ 2. Select **Operation**: **Update**
77
+ 3. Provide **Blog ID or Slug**: The ID or slug of the blog post to update
78
+ 4. Fill in any fields you want to update (all optional, but at least one required)
79
+ 5. Set fields to empty/null to clear them
58
80
 
59
81
  ## Node Parameters
60
82
 
61
- ### Required Fields
83
+ ### Create Operation
84
+
85
+ **Required Fields:**
62
86
  - **Title**: The title of the blog post
63
- - **Slug**: URL-friendly slug for the blog post
87
+ - **Slug**: URL-friendly slug (must be unique)
64
88
  - **Content**: Full blog post content
65
- - **Credentials**: Blog API credentials (configured separately)
66
89
 
67
- ### Optional Fields
90
+ **Optional Fields:**
68
91
  - **Excerpt**: Short excerpt or summary
69
92
  - **Cover Image URL**: URL of the cover image
70
- - **Published**: Whether the blog post should be published (default: true)
71
- - **Tags**: Array of tags for the blog post
72
- - **Category**: Category of the blog post
73
- - **Meta Title**: SEO meta title
74
- - **Meta Description**: SEO meta description
75
- - **Author Email**: Email of the author
93
+ - **Published**: Whether to publish immediately (default: `false`)
94
+ - **Tags**: Array of tags, e.g., `["Next.js", "Tutorial"]`
95
+ - **Category**: Category name, e.g., "Development"
96
+ - **Meta Title**: SEO meta title (defaults to title if not provided)
97
+ - **Meta Description**: SEO meta description (defaults to excerpt if not provided)
98
+ - **Author Email**: Email of existing user to attribute post to
99
+
100
+ ### Update Operation
101
+
102
+ **Required Fields:**
103
+ - **Blog ID or Slug**: The ID or slug of the blog post to update
104
+
105
+ **Optional Fields (at least one required):**
106
+ - All fields from Create operation are optional for updates
107
+ - Set fields to empty/null to clear them
76
108
 
77
109
  ## Credentials
78
110
 
79
- The node requires **Blog API** credentials with:
80
- - **API URL**: The API endpoint URL (e.g., `)
81
- - **Secret**: Bearer token for authentication
111
+ The node requires **Kepha14 API** credentials with:
112
+ - **Base URL**: The base URL of the API (e.g., `https://kepha14.dev`)
113
+ - **API Token**: Bearer token for authentication (from `N8N_WEBHOOK_SECRET` environment variable)
114
+
115
+ ## API Endpoints
116
+
117
+ The node uses the following endpoints:
118
+ - **Create**: `POST {baseUrl}/api/blogs/n8n`
119
+ - **Update**: `PUT {baseUrl}/api/blogs/n8n/{idOrSlug}`
120
+
121
+ ## Error Handling
122
+
123
+ The node handles common API errors:
124
+ - **401 Unauthorized**: Invalid or missing API token
125
+ - **400 Bad Request**: Validation errors (missing required fields, invalid types)
126
+ - **404 Not Found**: Blog post not found (update operation)
127
+ - **409 Conflict**: Blog slug already exists (create operation)
128
+ - **500 Internal Server Error**: Server-side errors
82
129
 
83
130
  ## Development
84
131
 
@@ -96,7 +143,19 @@ npm run lint
96
143
  npm run format
97
144
  ```
98
145
 
146
+ ## Structure
147
+
148
+ ```
149
+ n8n-nodes-blog-post/
150
+ ├── nodes/
151
+ │ └── Blog/
152
+ │ ├── Blog.node.ts
153
+ │ └── blog.svg
154
+ ├── credentials/
155
+ │ └── Kepha14Api.credentials.ts
156
+ └── package.json
157
+ ```
158
+
99
159
  ## License
100
160
 
101
161
  MIT
102
-
@@ -1,5 +1,5 @@
1
1
  import { ICredentialType, INodeProperties } from 'n8n-workflow';
2
- export declare class BlogApi implements ICredentialType {
2
+ export declare class Kepha14Api implements ICredentialType {
3
3
  name: string;
4
4
  displayName: string;
5
5
  documentationUrl: string;
@@ -1,32 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BlogApi = void 0;
4
- class BlogApi {
3
+ exports.Kepha14Api = void 0;
4
+ class Kepha14Api {
5
5
  constructor() {
6
- this.name = 'blogApi';
7
- this.displayName = 'Blog API';
6
+ this.name = 'kepha14Api';
7
+ this.displayName = 'Kepha14 API';
8
8
  this.documentationUrl = '';
9
9
  this.properties = [
10
10
  {
11
- displayName: 'API URL',
12
- name: 'url',
11
+ displayName: 'Base URL',
12
+ name: 'baseUrl',
13
13
  type: 'string',
14
- default: '',
14
+ default: 'https://kepha14.dev',
15
15
  required: true,
16
- description: 'The API endpoint URL for blog posts',
16
+ description: 'Base URL of the API, e.g., https://kepha14.dev',
17
17
  },
18
18
  {
19
- displayName: 'Secret',
20
- name: 'secret',
19
+ displayName: 'API Token',
20
+ name: 'apiToken',
21
21
  type: 'string',
22
22
  typeOptions: {
23
23
  password: true,
24
24
  },
25
25
  default: '',
26
26
  required: true,
27
- description: 'Bearer token for API authentication',
27
+ description: 'Bearer token for API authentication (from N8N_WEBHOOK_SECRET)',
28
28
  },
29
29
  ];
30
30
  }
31
31
  }
32
- exports.BlogApi = BlogApi;
32
+ exports.Kepha14Api = Kepha14Api;
@@ -1,5 +1,5 @@
1
1
  import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
- export declare class BlogPost implements INodeType {
2
+ export declare class AITool implements INodeType {
3
3
  description: INodeTypeDescription;
4
4
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
5
  }
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AITool = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ function generateSlug(text) {
6
+ return text
7
+ .toLowerCase()
8
+ .trim()
9
+ .replace(/[^\w\s-]/g, '')
10
+ .replace(/[\s_-]+/g, '-')
11
+ .replace(/^-+|-+$/g, '');
12
+ }
13
+ class AITool {
14
+ constructor() {
15
+ this.description = {
16
+ displayName: 'AI Tool',
17
+ name: 'aiTool',
18
+ icon: 'file:ai-tool.svg',
19
+ group: ['transform'],
20
+ version: 1,
21
+ description: 'Create or update AI tools on kepha14.dev',
22
+ defaults: {
23
+ name: 'AI Tool',
24
+ },
25
+ inputs: ['main'],
26
+ outputs: ['main'],
27
+ credentials: [
28
+ {
29
+ name: 'kepha14Api',
30
+ required: true,
31
+ },
32
+ ],
33
+ properties: [
34
+ {
35
+ displayName: 'Resource',
36
+ name: 'resource',
37
+ type: 'options',
38
+ noDataExpression: true,
39
+ options: [
40
+ {
41
+ name: 'AI Tool',
42
+ value: 'aiTool',
43
+ },
44
+ ],
45
+ default: 'aiTool',
46
+ },
47
+ {
48
+ displayName: 'Operation',
49
+ name: 'operation',
50
+ type: 'options',
51
+ noDataExpression: true,
52
+ displayOptions: {
53
+ show: {
54
+ resource: ['aiTool'],
55
+ },
56
+ },
57
+ options: [
58
+ {
59
+ name: 'Create',
60
+ value: 'create',
61
+ description: 'Create a new AI tool',
62
+ action: 'Create an AI tool',
63
+ },
64
+ {
65
+ name: 'Update',
66
+ value: 'update',
67
+ description: 'Update an existing AI tool',
68
+ action: 'Update an AI tool',
69
+ },
70
+ ],
71
+ default: 'create',
72
+ },
73
+ {
74
+ displayName: 'AI Tool ID or Slug',
75
+ name: 'idOrSlug',
76
+ type: 'string',
77
+ displayOptions: {
78
+ show: {
79
+ resource: ['aiTool'],
80
+ operation: ['update'],
81
+ },
82
+ },
83
+ default: '',
84
+ required: true,
85
+ description: 'The ID or slug of the AI tool to update',
86
+ },
87
+ {
88
+ displayName: 'Name',
89
+ name: 'name',
90
+ type: 'string',
91
+ displayOptions: {
92
+ show: {
93
+ resource: ['aiTool'],
94
+ operation: ['create'],
95
+ },
96
+ },
97
+ default: '',
98
+ required: true,
99
+ description: 'Name of the AI tool',
100
+ },
101
+ {
102
+ displayName: 'Website URL',
103
+ name: 'url',
104
+ type: 'string',
105
+ displayOptions: {
106
+ show: {
107
+ resource: ['aiTool'],
108
+ operation: ['create'],
109
+ },
110
+ },
111
+ default: '',
112
+ required: true,
113
+ description: 'Website URL of the tool',
114
+ },
115
+ {
116
+ displayName: 'Slug',
117
+ name: 'slug',
118
+ type: 'string',
119
+ displayOptions: {
120
+ show: {
121
+ resource: ['aiTool'],
122
+ operation: ['create'],
123
+ },
124
+ },
125
+ default: '',
126
+ description: 'URL slug (auto-generated from name if not provided)',
127
+ },
128
+ {
129
+ displayName: 'Name',
130
+ name: 'name',
131
+ type: 'string',
132
+ displayOptions: {
133
+ show: {
134
+ resource: ['aiTool'],
135
+ operation: ['update'],
136
+ },
137
+ },
138
+ default: '',
139
+ description: 'Update the name',
140
+ },
141
+ {
142
+ displayName: 'Website URL',
143
+ name: 'url',
144
+ type: 'string',
145
+ displayOptions: {
146
+ show: {
147
+ resource: ['aiTool'],
148
+ operation: ['update'],
149
+ },
150
+ },
151
+ default: '',
152
+ description: 'Update the website URL',
153
+ },
154
+ {
155
+ displayName: 'Slug',
156
+ name: 'slug',
157
+ type: 'string',
158
+ displayOptions: {
159
+ show: {
160
+ resource: ['aiTool'],
161
+ operation: ['update'],
162
+ },
163
+ },
164
+ default: '',
165
+ description: 'Update the slug',
166
+ },
167
+ {
168
+ displayName: 'Description',
169
+ name: 'description',
170
+ type: 'string',
171
+ typeOptions: {
172
+ rows: 3,
173
+ },
174
+ default: '',
175
+ description: 'Short description of the AI tool',
176
+ },
177
+ {
178
+ displayName: 'Content',
179
+ name: 'content',
180
+ type: 'string',
181
+ typeOptions: {
182
+ rows: 5,
183
+ },
184
+ default: '',
185
+ description: 'Full HTML content/description',
186
+ },
187
+ {
188
+ displayName: 'Category',
189
+ name: 'category',
190
+ type: 'string',
191
+ default: '',
192
+ description: 'Category name',
193
+ },
194
+ {
195
+ displayName: 'Tags',
196
+ name: 'tags',
197
+ type: 'string',
198
+ typeOptions: {
199
+ multipleValues: true,
200
+ },
201
+ default: [],
202
+ description: 'List of tags',
203
+ },
204
+ {
205
+ displayName: 'Logo URL',
206
+ name: 'logo',
207
+ type: 'string',
208
+ default: '',
209
+ description: 'Logo image URL',
210
+ },
211
+ {
212
+ displayName: 'Cover Image URL',
213
+ name: 'coverImage',
214
+ type: 'string',
215
+ default: '',
216
+ },
217
+ {
218
+ displayName: 'YouTube Video URL',
219
+ name: 'youtubeVideoUrl',
220
+ type: 'string',
221
+ default: '',
222
+ description: 'YouTube tutorial video URL',
223
+ },
224
+ {
225
+ displayName: 'Featured',
226
+ name: 'featured',
227
+ type: 'boolean',
228
+ default: false,
229
+ description: 'Feature this tool',
230
+ },
231
+ {
232
+ displayName: 'Published',
233
+ name: 'published',
234
+ type: 'boolean',
235
+ default: false,
236
+ description: 'Publish immediately',
237
+ },
238
+ ],
239
+ };
240
+ }
241
+ async execute() {
242
+ const items = this.getInputData();
243
+ const returnData = [];
244
+ const credentials = await this.getCredentials('kepha14Api');
245
+ const baseUrl = credentials.baseUrl.replace(/\/$/, '');
246
+ const resource = this.getNodeParameter('resource', 0);
247
+ const operation = this.getNodeParameter('operation', 0);
248
+ for (let i = 0; i < items.length; i++) {
249
+ try {
250
+ if (resource === 'aiTool') {
251
+ if (operation === 'create') {
252
+ const name = this.getNodeParameter('name', i);
253
+ const url = this.getNodeParameter('url', i);
254
+ let slug = this.getNodeParameter('slug', i);
255
+ const description = this.getNodeParameter('description', i);
256
+ const content = this.getNodeParameter('content', i);
257
+ const category = this.getNodeParameter('category', i);
258
+ const tags = this.getNodeParameter('tags', i);
259
+ const logo = this.getNodeParameter('logo', i);
260
+ const coverImage = this.getNodeParameter('coverImage', i);
261
+ const youtubeVideoUrl = this.getNodeParameter('youtubeVideoUrl', i);
262
+ const featured = this.getNodeParameter('featured', i);
263
+ const published = this.getNodeParameter('published', i);
264
+ // Auto-generate slug from name if not provided
265
+ if (!slug || slug.trim() === '') {
266
+ slug = generateSlug(name);
267
+ }
268
+ const body = {
269
+ name,
270
+ url,
271
+ };
272
+ if (slug)
273
+ body.slug = slug;
274
+ if (description)
275
+ body.description = description;
276
+ if (content)
277
+ body.content = content;
278
+ if (category)
279
+ body.category = category;
280
+ if (tags && tags.length > 0)
281
+ body.tags = tags;
282
+ if (logo)
283
+ body.logo = logo;
284
+ if (coverImage)
285
+ body.coverImage = coverImage;
286
+ if (youtubeVideoUrl)
287
+ body.youtubeVideoUrl = youtubeVideoUrl;
288
+ if (featured !== undefined)
289
+ body.featured = featured;
290
+ if (published !== undefined)
291
+ body.published = published;
292
+ const response = await this.helpers.httpRequest({
293
+ method: 'POST',
294
+ url: `${baseUrl}/api/ai-tools/n8n`,
295
+ headers: {
296
+ 'Authorization': `Bearer ${credentials.apiToken}`,
297
+ 'Content-Type': 'application/json',
298
+ },
299
+ body,
300
+ json: true,
301
+ });
302
+ returnData.push({
303
+ json: response,
304
+ pairedItem: {
305
+ item: i,
306
+ },
307
+ });
308
+ }
309
+ else if (operation === 'update') {
310
+ const idOrSlug = this.getNodeParameter('idOrSlug', i);
311
+ const name = this.getNodeParameter('name', i);
312
+ const url = this.getNodeParameter('url', i);
313
+ const slug = this.getNodeParameter('slug', i);
314
+ const description = this.getNodeParameter('description', i);
315
+ const content = this.getNodeParameter('content', i);
316
+ const category = this.getNodeParameter('category', i);
317
+ const tags = this.getNodeParameter('tags', i);
318
+ const logo = this.getNodeParameter('logo', i);
319
+ const coverImage = this.getNodeParameter('coverImage', i);
320
+ const youtubeVideoUrl = this.getNodeParameter('youtubeVideoUrl', i);
321
+ const featured = this.getNodeParameter('featured', i);
322
+ const published = this.getNodeParameter('published', i);
323
+ const body = {};
324
+ if (name)
325
+ body.name = name;
326
+ if (url)
327
+ body.url = url;
328
+ if (slug)
329
+ body.slug = slug;
330
+ if (description !== undefined)
331
+ body.description = description || null;
332
+ if (content !== undefined)
333
+ body.content = content || null;
334
+ if (category !== undefined)
335
+ body.category = category || null;
336
+ if (tags !== undefined)
337
+ body.tags = tags;
338
+ if (logo !== undefined)
339
+ body.logo = logo || null;
340
+ if (coverImage !== undefined)
341
+ body.coverImage = coverImage || null;
342
+ if (youtubeVideoUrl !== undefined)
343
+ body.youtubeVideoUrl = youtubeVideoUrl || null;
344
+ if (featured !== undefined)
345
+ body.featured = featured;
346
+ if (published !== undefined)
347
+ body.published = published;
348
+ if (Object.keys(body).length === 0) {
349
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'At least one field must be provided for update', { itemIndex: i });
350
+ }
351
+ const response = await this.helpers.httpRequest({
352
+ method: 'PUT',
353
+ url: `${baseUrl}/api/ai-tools/n8n/${idOrSlug}`,
354
+ headers: {
355
+ 'Authorization': `Bearer ${credentials.apiToken}`,
356
+ 'Content-Type': 'application/json',
357
+ },
358
+ body,
359
+ json: true,
360
+ });
361
+ returnData.push({
362
+ json: response,
363
+ pairedItem: {
364
+ item: i,
365
+ },
366
+ });
367
+ }
368
+ }
369
+ }
370
+ catch (error) {
371
+ if (this.continueOnFail()) {
372
+ returnData.push({
373
+ json: {
374
+ error: error instanceof Error ? error.message : String(error),
375
+ },
376
+ pairedItem: {
377
+ item: i,
378
+ },
379
+ });
380
+ continue;
381
+ }
382
+ throw error;
383
+ }
384
+ }
385
+ return [returnData];
386
+ }
387
+ }
388
+ exports.AITool = AITool;
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
3
+ <path d="M2 17l10 5 10-5"></path>
4
+ <path d="M2 12l10 5 10-5"></path>
5
+ <circle cx="12" cy="12" r="2"></circle>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class Blog implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }