424 lines
11 KiB
Go
424 lines
11 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
// HookInfo describes a plugin hook
|
||
|
|
type HookInfo struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Label string `json:"label"`
|
||
|
|
Description string `json:"description"`
|
||
|
|
Pattern string `json:"pattern"` // event, validation, transform
|
||
|
|
TestData map[string]any `json:"test_data"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Available hooks
|
||
|
|
var hooks = []HookInfo{
|
||
|
|
{
|
||
|
|
Name: "post.published",
|
||
|
|
Label: "Post Published",
|
||
|
|
Description: "Triggered when a post is published",
|
||
|
|
Pattern: "event",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"post": map[string]any{
|
||
|
|
"slug": "hello-world",
|
||
|
|
"title": "Hello World",
|
||
|
|
"url": "/hello-world",
|
||
|
|
"excerpt": "This is a test post for plugin development.",
|
||
|
|
"publishedAt": "2024-01-15T10:30:00Z",
|
||
|
|
"tags": []string{"test", "development"},
|
||
|
|
"readingTime": 3,
|
||
|
|
},
|
||
|
|
"author": map[string]any{
|
||
|
|
"name": "Test Author",
|
||
|
|
"email": "author@example.com",
|
||
|
|
},
|
||
|
|
"blog": map[string]any{
|
||
|
|
"name": "Test Blog",
|
||
|
|
"url": "https://test.writekit.dev",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "post.updated",
|
||
|
|
Label: "Post Updated",
|
||
|
|
Description: "Triggered when a post is updated",
|
||
|
|
Pattern: "event",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"post": map[string]any{
|
||
|
|
"slug": "hello-world",
|
||
|
|
"title": "Hello World (Updated)",
|
||
|
|
"url": "/hello-world",
|
||
|
|
"excerpt": "This is a test post that was updated.",
|
||
|
|
"publishedAt": "2024-01-15T10:30:00Z",
|
||
|
|
"updatedAt": "2024-01-16T14:00:00Z",
|
||
|
|
"tags": []string{"test", "development", "updated"},
|
||
|
|
},
|
||
|
|
"author": map[string]any{
|
||
|
|
"name": "Test Author",
|
||
|
|
"email": "author@example.com",
|
||
|
|
},
|
||
|
|
"changes": map[string]any{
|
||
|
|
"title": map[string]any{"old": "Hello World", "new": "Hello World (Updated)"},
|
||
|
|
"content": true,
|
||
|
|
"tags": map[string]any{"added": []string{"updated"}, "removed": []string{}},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "comment.validate",
|
||
|
|
Label: "Comment Validate",
|
||
|
|
Description: "Validate comments before creation",
|
||
|
|
Pattern: "validation",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"content": "This is a test comment for moderation.",
|
||
|
|
"authorName": "Test User",
|
||
|
|
"authorEmail": "user@example.com",
|
||
|
|
"postSlug": "hello-world",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "comment.created",
|
||
|
|
Label: "Comment Created",
|
||
|
|
Description: "Triggered when a comment is created",
|
||
|
|
Pattern: "event",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"comment": map[string]any{
|
||
|
|
"id": "test-comment-123",
|
||
|
|
"content": "Great post! Thanks for sharing.",
|
||
|
|
"authorName": "Test User",
|
||
|
|
"authorEmail": "user@example.com",
|
||
|
|
"postSlug": "hello-world",
|
||
|
|
"createdAt": "2024-01-15T12:00:00Z",
|
||
|
|
},
|
||
|
|
"post": map[string]any{
|
||
|
|
"slug": "hello-world",
|
||
|
|
"title": "Hello World",
|
||
|
|
"url": "/hello-world",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "member.subscribed",
|
||
|
|
Label: "Member Subscribed",
|
||
|
|
Description: "Triggered when a member subscribes",
|
||
|
|
Pattern: "event",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"member": map[string]any{
|
||
|
|
"email": "subscriber@example.com",
|
||
|
|
"name": "New Subscriber",
|
||
|
|
"subscribedAt": "2024-01-15T09:00:00Z",
|
||
|
|
},
|
||
|
|
"tier": map[string]any{
|
||
|
|
"name": "Free",
|
||
|
|
"price": 0,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "content.render",
|
||
|
|
Label: "Content Render",
|
||
|
|
Description: "Transform rendered HTML",
|
||
|
|
Pattern: "transform",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"html": "<h1>Hello World</h1><p>This is test content.</p><pre><code>const x = 1;</code></pre>",
|
||
|
|
"post": map[string]any{
|
||
|
|
"slug": "hello-world",
|
||
|
|
"title": "Hello World",
|
||
|
|
"tags": []string{"test"},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "asset.uploaded",
|
||
|
|
Label: "Asset Uploaded",
|
||
|
|
Description: "Triggered when an asset is uploaded",
|
||
|
|
Pattern: "event",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"id": "asset-123",
|
||
|
|
"url": "https://cdn.example.com/image.webp",
|
||
|
|
"contentType": "image/webp",
|
||
|
|
"size": 102400,
|
||
|
|
"width": 1920,
|
||
|
|
"height": 1080,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: "analytics.sync",
|
||
|
|
Label: "Analytics Sync",
|
||
|
|
Description: "Sync analytics data periodically",
|
||
|
|
Pattern: "event",
|
||
|
|
TestData: map[string]any{
|
||
|
|
"period": map[string]any{
|
||
|
|
"start": "2024-01-08T00:00:00Z",
|
||
|
|
"end": "2024-01-15T00:00:00Z",
|
||
|
|
},
|
||
|
|
"pageviews": 1250,
|
||
|
|
"visitors": 890,
|
||
|
|
"topPages": []map[string]any{
|
||
|
|
{"path": "/hello-world", "views": 450},
|
||
|
|
{"path": "/about", "views": 230},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
// Templates organized by hook and language
|
||
|
|
var templates = map[string]map[string]string{
|
||
|
|
"post.published": {
|
||
|
|
"typescript": `export const onPostPublished = (event: PostPublishedEvent): void => {
|
||
|
|
Runner.log("Post published: " + event.post.title);
|
||
|
|
|
||
|
|
// Example: Send Slack notification
|
||
|
|
Runner.httpRequest({
|
||
|
|
url: Runner.secrets.SLACK_WEBHOOK,
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({
|
||
|
|
text: "New post: " + event.post.title + "\n" + event.post.url,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
func OnPostPublished(event PostPublishedEvent) error {
|
||
|
|
Runner.Log("Post published: " + event.Post.Title)
|
||
|
|
|
||
|
|
// Example: Send Slack notification
|
||
|
|
Runner.HttpRequest(Runner.Secrets.SlackWebhook, "POST", []byte("{\"text\":\"New post published\"}"))
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"post.updated": {
|
||
|
|
"typescript": `export const onPostUpdated = (event: PostUpdatedEvent): void => {
|
||
|
|
Runner.log("Post updated: " + event.post.title);
|
||
|
|
|
||
|
|
// Example: Sync to external CMS
|
||
|
|
if (event.changes.content) {
|
||
|
|
Runner.httpRequest({
|
||
|
|
url: "https://api.example.com/sync",
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({
|
||
|
|
slug: event.post.slug,
|
||
|
|
title: event.post.title,
|
||
|
|
url: event.post.url,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
func OnPostUpdated(event PostUpdatedEvent) error {
|
||
|
|
Runner.Log("Post updated: " + event.Post.Title)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"comment.validate": {
|
||
|
|
"typescript": `export const validateComment = (input: CommentInput): ValidationResult => {
|
||
|
|
// Example: Simple spam check
|
||
|
|
const spamWords = ["buy now", "click here", "free money"];
|
||
|
|
const content = input.content.toLowerCase();
|
||
|
|
|
||
|
|
for (const word of spamWords) {
|
||
|
|
if (content.includes(word)) {
|
||
|
|
return { allowed: false, reason: "Comment flagged as spam" };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Example: Check minimum length
|
||
|
|
if (input.content.length < 10) {
|
||
|
|
return { allowed: false, reason: "Comment too short" };
|
||
|
|
}
|
||
|
|
|
||
|
|
return { allowed: true };
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
import "strings"
|
||
|
|
|
||
|
|
func ValidateComment(input CommentInput) (ValidationResult, error) {
|
||
|
|
// Example: Simple spam check
|
||
|
|
spamWords := []string{"buy now", "click here", "free money"}
|
||
|
|
content := strings.ToLower(input.Content)
|
||
|
|
|
||
|
|
for _, word := range spamWords {
|
||
|
|
if strings.Contains(content, word) {
|
||
|
|
return ValidationResult{Allowed: false, Reason: "Spam detected"}, nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return ValidationResult{Allowed: true}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"comment.created": {
|
||
|
|
"typescript": `export const onCommentCreated = (event: CommentCreatedEvent): void => {
|
||
|
|
Runner.log("New comment on: " + event.post.title);
|
||
|
|
|
||
|
|
// Example: Send notification
|
||
|
|
Runner.httpRequest({
|
||
|
|
url: Runner.secrets.SLACK_WEBHOOK,
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({
|
||
|
|
text: "New comment by " + event.comment.authorName + " on \"" + event.post.title + "\"",
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
func OnCommentCreated(event CommentCreatedEvent) error {
|
||
|
|
Runner.Log("New comment on: " + event.Post.Title)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"member.subscribed": {
|
||
|
|
"typescript": `export const onMemberSubscribed = (event: MemberSubscribedEvent): void => {
|
||
|
|
Runner.log("New subscriber: " + event.member.email);
|
||
|
|
|
||
|
|
// Example: Add to email list
|
||
|
|
Runner.httpRequest({
|
||
|
|
url: "https://api.buttondown.email/v1/subscribers",
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
"Authorization": "Token " + Runner.secrets.BUTTONDOWN_API_KEY,
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
email: event.member.email,
|
||
|
|
notes: "Subscribed from blog",
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
func OnMemberSubscribed(event MemberSubscribedEvent) error {
|
||
|
|
Runner.Log("New subscriber: " + event.Member.Email)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"content.render": {
|
||
|
|
"typescript": `export const renderContent = (input: ContentRenderInput): ContentRenderOutput => {
|
||
|
|
let html = input.html;
|
||
|
|
|
||
|
|
// Example: Add copy button to code blocks
|
||
|
|
html = html.replace(
|
||
|
|
/<pre><code/g,
|
||
|
|
'<pre class="relative group"><button class="copy-btn absolute top-2 right-2 opacity-0 group-hover:opacity-100">Copy</button><code'
|
||
|
|
);
|
||
|
|
|
||
|
|
// Example: Make external links open in new tab
|
||
|
|
html = html.replace(
|
||
|
|
/<a href="(https?:\/\/[^"]+)"/g,
|
||
|
|
'<a href="$1" target="_blank" rel="noopener"'
|
||
|
|
);
|
||
|
|
|
||
|
|
return { html };
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
import "strings"
|
||
|
|
|
||
|
|
func RenderContent(input ContentRenderInput) (ContentRenderOutput, error) {
|
||
|
|
html := input.Html
|
||
|
|
|
||
|
|
// Example: Add copy button to code blocks
|
||
|
|
html = strings.ReplaceAll(html, "<pre><code", "<pre class=\"relative\"><button class=\"copy-btn\">Copy</button><code")
|
||
|
|
|
||
|
|
return ContentRenderOutput{Html: html}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"asset.uploaded": {
|
||
|
|
"typescript": `export const onAssetUploaded = (event: AssetUploadedEvent): void => {
|
||
|
|
Runner.log("Asset uploaded: " + event.url);
|
||
|
|
|
||
|
|
// Example: Backup to external storage
|
||
|
|
if (event.contentType.startsWith("image/")) {
|
||
|
|
Runner.httpRequest({
|
||
|
|
url: "https://api.cloudinary.com/v1_1/demo/image/upload",
|
||
|
|
method: "POST",
|
||
|
|
headers: { "Content-Type": "application/json" },
|
||
|
|
body: JSON.stringify({
|
||
|
|
file: event.url,
|
||
|
|
folder: "blog-backups",
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
func OnAssetUploaded(event AssetUploadedEvent) error {
|
||
|
|
Runner.Log("Asset uploaded: " + event.Url)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
"analytics.sync": {
|
||
|
|
"typescript": `export const onAnalyticsSync = (event: AnalyticsSyncEvent): void => {
|
||
|
|
Runner.log("Analytics sync: " + event.pageviews + " pageviews");
|
||
|
|
|
||
|
|
// Example: Push to external analytics
|
||
|
|
Runner.httpRequest({
|
||
|
|
url: "https://api.example.com/analytics",
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
"Authorization": "Bearer " + Runner.secrets.ANALYTICS_KEY,
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
period: event.period,
|
||
|
|
pageviews: event.pageviews,
|
||
|
|
visitors: event.visitors,
|
||
|
|
topPages: event.topPages,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
};
|
||
|
|
`,
|
||
|
|
"go": `package main
|
||
|
|
|
||
|
|
func OnAnalyticsSync(event AnalyticsSyncEvent) error {
|
||
|
|
Runner.Log("Analytics sync completed")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {}
|
||
|
|
`,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetTemplate returns the template for a specific hook and language
|
||
|
|
func GetTemplate(hook, language string) string {
|
||
|
|
if hookTemplates, ok := templates[hook]; ok {
|
||
|
|
if template, ok := hookTemplates[language]; ok {
|
||
|
|
return template
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Default fallback
|
||
|
|
return templates["post.published"]["typescript"]
|
||
|
|
}
|