mirror of
https://github.com/hanzoskill/ecto.git
synced 2026-10-30 21:03:34 +00:00
352 lines
8.4 KiB
Go
352 lines
8.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/visionik/ecto/internal/config"
|
|
"github.com/visionik/libecto"
|
|
)
|
|
|
|
var postsCmd = &cobra.Command{
|
|
Use: "posts",
|
|
Short: "List posts",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status, _ := cmd.Flags().GetString("status")
|
|
limit, _ := cmd.Flags().GetInt("limit")
|
|
asJSON, _ := cmd.Flags().GetBool("json")
|
|
|
|
resp, err := client.ListPosts(status, limit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if asJSON {
|
|
return outputJSON(resp)
|
|
}
|
|
|
|
if len(resp.Posts) == 0 {
|
|
println("No posts found")
|
|
return nil
|
|
}
|
|
|
|
for _, p := range resp.Posts {
|
|
printf("[%s] %s - %s (%s)\n", p.Status, p.ID, p.Title, p.Slug)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postCmd = &cobra.Command{
|
|
Use: "post <id|slug>",
|
|
Short: "Get a single post",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
asJSON, _ := cmd.Flags().GetBool("json")
|
|
showBody, _ := cmd.Flags().GetBool("body")
|
|
|
|
post, err := client.GetPost(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if asJSON {
|
|
return outputJSON(map[string]interface{}{"posts": []interface{}{post}})
|
|
}
|
|
|
|
printf("ID: %s\n", post.ID)
|
|
printf("Title: %s\n", post.Title)
|
|
printf("Slug: %s\n", post.Slug)
|
|
printf("Status: %s\n", post.Status)
|
|
printf("Created: %s\n", post.CreatedAt)
|
|
if post.PublishedAt != "" {
|
|
printf("Published: %s\n", post.PublishedAt)
|
|
}
|
|
if len(post.Tags) > 0 {
|
|
var tagNames []string
|
|
for _, t := range post.Tags {
|
|
tagNames = append(tagNames, t.Name)
|
|
}
|
|
printf("Tags: %s\n", strings.Join(tagNames, ", "))
|
|
}
|
|
if post.Excerpt != "" && !showBody {
|
|
printf("\nExcerpt:\n%s\n", post.Excerpt)
|
|
}
|
|
if showBody && post.HTML != "" {
|
|
printf("\nBody (HTML):\n%s\n", post.HTML)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postCreateCmd = &cobra.Command{
|
|
Use: "create",
|
|
Short: "Create a new post",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
title, _ := cmd.Flags().GetString("title")
|
|
status, _ := cmd.Flags().GetString("status")
|
|
mdFile, _ := cmd.Flags().GetString("markdown-file")
|
|
stdinFormat, _ := cmd.Flags().GetString("stdin-format")
|
|
tagsStr, _ := cmd.Flags().GetString("tag")
|
|
|
|
if title == "" {
|
|
return fmt.Errorf("--title is required")
|
|
}
|
|
|
|
post := &libecto.Post{
|
|
Title: title,
|
|
Status: status,
|
|
}
|
|
|
|
// Read content
|
|
var content []byte
|
|
if mdFile != "" {
|
|
content, err = os.ReadFile(mdFile)
|
|
if err != nil {
|
|
return fmt.Errorf("reading markdown file: %w", err)
|
|
}
|
|
} else if stdinFormat == "markdown" {
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
var lines []string
|
|
for scanner.Scan() {
|
|
lines = append(lines, scanner.Text())
|
|
}
|
|
content = []byte(strings.Join(lines, "\n"))
|
|
}
|
|
|
|
if len(content) > 0 {
|
|
post.HTML = libecto.MarkdownToHTML(content)
|
|
}
|
|
|
|
// Handle tags
|
|
if tagsStr != "" {
|
|
tagNames := strings.Split(tagsStr, ",")
|
|
for _, name := range tagNames {
|
|
post.Tags = append(post.Tags, libecto.Tag{Name: strings.TrimSpace(name)})
|
|
}
|
|
}
|
|
|
|
created, err := client.CreatePost(post)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printf("Created post: %s (%s)\n", created.ID, created.Slug)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postEditCmd = &cobra.Command{
|
|
Use: "edit <id|slug>",
|
|
Short: "Edit a post",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// First get the existing post
|
|
existing, err := client.GetPost(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
update := &libecto.Post{
|
|
UpdatedAt: existing.UpdatedAt, // Required for conflict detection
|
|
}
|
|
|
|
if title, _ := cmd.Flags().GetString("title"); title != "" {
|
|
update.Title = title
|
|
}
|
|
if status, _ := cmd.Flags().GetString("status"); status != "" {
|
|
update.Status = status
|
|
}
|
|
if publishAt, _ := cmd.Flags().GetString("publish-at"); publishAt != "" {
|
|
update.PublishedAt = publishAt
|
|
update.Status = "scheduled"
|
|
}
|
|
if mdFile, _ := cmd.Flags().GetString("markdown-file"); mdFile != "" {
|
|
content, err := os.ReadFile(mdFile)
|
|
if err != nil {
|
|
return fmt.Errorf("reading markdown file: %w", err)
|
|
}
|
|
update.HTML = libecto.MarkdownToHTML(content)
|
|
}
|
|
if featureImage, _ := cmd.Flags().GetString("feature-image"); featureImage != "" {
|
|
update.FeatureImage = featureImage
|
|
}
|
|
|
|
updated, err := client.UpdatePost(existing.ID, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printf("Updated post: %s\n", updated.ID)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postDeleteCmd = &cobra.Command{
|
|
Use: "delete <id|slug>",
|
|
Short: "Delete a post",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
force, _ := cmd.Flags().GetBool("force")
|
|
|
|
// Get post first to get ID and confirm
|
|
post, err := client.GetPost(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !force {
|
|
printf("Delete post %q (%s)? [y/N]: ", post.Title, post.ID)
|
|
var answer string
|
|
fmt.Scanln(&answer)
|
|
if strings.ToLower(answer) != "y" {
|
|
println("Cancelled")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if err := client.DeletePost(post.ID); err != nil {
|
|
return err
|
|
}
|
|
|
|
printf("Deleted post: %s\n", post.ID)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postPublishCmd = &cobra.Command{
|
|
Use: "publish <id|slug>",
|
|
Short: "Publish a post",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := client.PublishPost(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printf("Published post: %s\n", updated.ID)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postUnpublishCmd = &cobra.Command{
|
|
Use: "unpublish <id|slug>",
|
|
Short: "Unpublish a post (set to draft)",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := client.UnpublishPost(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printf("Unpublished post: %s\n", updated.ID)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var postScheduleCmd = &cobra.Command{
|
|
Use: "schedule <id|slug>",
|
|
Short: "Schedule a post for publication",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
client, err := config.GetActiveClient(siteName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
at, _ := cmd.Flags().GetString("at")
|
|
|
|
if at == "" {
|
|
return fmt.Errorf("--at is required (ISO8601 timestamp)")
|
|
}
|
|
|
|
updated, err := client.SchedulePost(args[0], at)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printf("Scheduled post %s for %s\n", updated.ID, at)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func outputJSON(v interface{}) error {
|
|
enc := json.NewEncoder(output)
|
|
enc.SetIndent("", " ")
|
|
return enc.Encode(v)
|
|
}
|
|
|
|
func init() {
|
|
postsCmd.Flags().String("status", "", "Filter by status (draft|published|scheduled|all)")
|
|
postsCmd.Flags().Int("limit", 15, "Number of posts to return")
|
|
postsCmd.Flags().Bool("json", false, "Output as JSON")
|
|
|
|
postCmd.Flags().Bool("json", false, "Output as JSON")
|
|
postCmd.Flags().Bool("body", false, "Include full HTML body")
|
|
|
|
postCreateCmd.Flags().String("title", "", "Post title (required)")
|
|
postCreateCmd.Flags().String("status", "draft", "Post status (draft|published)")
|
|
postCreateCmd.Flags().String("markdown-file", "", "Path to markdown file for content")
|
|
postCreateCmd.Flags().String("stdin-format", "", "Read content from stdin (markdown)")
|
|
postCreateCmd.Flags().String("tag", "", "Comma-separated tags")
|
|
|
|
postEditCmd.Flags().String("title", "", "New title")
|
|
postEditCmd.Flags().String("status", "", "New status")
|
|
postEditCmd.Flags().String("markdown-file", "", "Path to markdown file for new content")
|
|
postEditCmd.Flags().String("publish-at", "", "Schedule for publication (ISO8601)")
|
|
postEditCmd.Flags().String("feature-image", "", "URL for the feature image")
|
|
|
|
postDeleteCmd.Flags().Bool("force", false, "Delete without confirmation")
|
|
|
|
postScheduleCmd.Flags().String("at", "", "Publication time (ISO8601)")
|
|
|
|
postCmd.AddCommand(postCreateCmd)
|
|
postCmd.AddCommand(postEditCmd)
|
|
postCmd.AddCommand(postDeleteCmd)
|
|
postCmd.AddCommand(postPublishCmd)
|
|
postCmd.AddCommand(postUnpublishCmd)
|
|
postCmd.AddCommand(postScheduleCmd)
|
|
|
|
rootCmd.AddCommand(postsCmd)
|
|
rootCmd.AddCommand(postCmd)
|
|
}
|