Files
WPSidekick/cmd/cli/main.go
T
2026-01-25 08:57:55 +01:00

249 lines
5.7 KiB
Go

package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type client struct {
baseURL string
http *http.Client
}
func newClient(baseURL string) *client {
return &client{
baseURL: strings.TrimRight(baseURL, "/"),
http: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func (c *client) do(method, path string, payload interface{}) ([]byte, int, error) {
var body io.Reader
if payload != nil {
data, err := json.Marshal(payload)
if err != nil {
return nil, 0, err
}
body = bytes.NewReader(data)
}
req, err := http.NewRequest(method, c.baseURL+path, body)
if err != nil {
return nil, 0, err
}
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.http.Do(req)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, resp.StatusCode, err
}
return respBody, resp.StatusCode, nil
}
func main() {
defaultAPI := os.Getenv("WPSK_API_URL")
if defaultAPI == "" {
defaultAPI = "http://localhost:8080"
}
apiURL := flag.String("api", defaultAPI, "API base URL")
usage := func() {
fmt.Fprintf(os.Stderr, "Usage: %s [--api URL] <command> [args]\n\n", os.Args[0])
fmt.Fprintln(os.Stderr, "Commands:")
fmt.Fprintln(os.Stderr, " health")
fmt.Fprintln(os.Stderr, " pending [summary]")
fmt.Fprintln(os.Stderr, " optimize <post_id> [language]")
fmt.Fprintln(os.Stderr, " status <job_id>")
fmt.Fprintln(os.Stderr, " changes <post_id>")
fmt.Fprintln(os.Stderr, " approve <draft_post_id>")
fmt.Fprintln(os.Stderr, " reject <draft_post_id>")
fmt.Fprintln(os.Stderr, " help")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Examples:")
fmt.Fprintln(os.Stderr, " wp-sk-cli pending")
fmt.Fprintln(os.Stderr, " wp-sk-cli changes 123")
fmt.Fprintln(os.Stderr, " WPSK_API_URL=http://server:8080 wp-sk-cli pending")
fmt.Fprintln(os.Stderr, " wp-sk-cli approve 456 --api http://server:8080")
}
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
usage()
os.Exit(1)
}
cmd := args[0]
args = args[1:]
if cmd == "help" || cmd == "-h" || cmd == "--help" {
usage()
return
}
c := newClient(*apiURL)
switch cmd {
case "health":
handleSimpleGet(c, "/api/v1/health")
case "pending":
if len(args) > 0 && args[0] == "summary" {
handlePendingSummary(c)
return
}
handleSimpleGet(c, "/api/v1/pending")
case "status":
if len(args) < 1 {
die("status requires <job_id>")
}
handleSimpleGet(c, "/api/v1/status/"+args[0])
case "changes":
if len(args) < 1 {
die("changes requires <post_id>")
}
handleSimpleGet(c, "/api/v1/optimization/"+args[0])
case "optimize":
if len(args) < 1 {
die("optimize requires <post_id>")
}
postID, err := parseIntArg("post_id", args[0])
if err != nil {
die(err.Error())
}
language := "en"
if len(args) > 1 {
language = args[1]
}
handleSimplePost(c, "/api/v1/optimize", map[string]interface{}{
"post_id": postID,
"language": language,
})
case "approve":
if len(args) < 1 {
die("approve requires <draft_post_id>")
}
draftID, err := parseIntArg("draft_post_id", args[0])
if err != nil {
die(err.Error())
}
handleSimplePost(c, "/api/v1/apply-draft", map[string]interface{}{
"draft_post_id": draftID,
})
case "reject":
if len(args) < 1 {
die("reject requires <draft_post_id>")
}
draftID, err := parseIntArg("draft_post_id", args[0])
if err != nil {
die(err.Error())
}
handleSimplePost(c, "/api/v1/reject-draft", map[string]interface{}{
"draft_post_id": draftID,
})
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", cmd)
usage()
os.Exit(1)
}
}
func handleSimpleGet(c *client, path string) {
body, status, err := c.do(http.MethodGet, path, nil)
handleResponse(body, status, err)
}
func handleSimplePost(c *client, path string, payload interface{}) {
body, status, err := c.do(http.MethodPost, path, payload)
handleResponse(body, status, err)
}
func handleResponse(body []byte, status int, err error) {
if err != nil {
die(err.Error())
}
if status < 200 || status >= 300 {
fmt.Fprintf(os.Stderr, "Request failed: status %d\n", status)
fmt.Fprintln(os.Stderr, string(body))
os.Exit(1)
}
var pretty bytes.Buffer
if json.Indent(&pretty, body, "", " ") == nil {
fmt.Println(pretty.String())
return
}
fmt.Println(string(body))
}
type pendingResponse struct {
Count int `json:"count"`
Records []pendingRecord `json:"records"`
}
type pendingRecord struct {
OriginalPostID int `json:"original_post_id"`
DraftPostID int `json:"draft_post_id"`
PostTitle string `json:"post_title"`
}
func handlePendingSummary(c *client) {
body, status, err := c.do(http.MethodGet, "/api/v1/pending", nil)
if err != nil {
die(err.Error())
}
if status < 200 || status >= 300 {
fmt.Fprintf(os.Stderr, "Request failed: status %d\n", status)
fmt.Fprintln(os.Stderr, string(body))
os.Exit(1)
}
var resp pendingResponse
if err := json.Unmarshal(body, &resp); err != nil {
die("failed to parse response")
}
if resp.Count == 0 || len(resp.Records) == 0 {
fmt.Println("No pending records.")
return
}
for _, record := range resp.Records {
if record.OriginalPostID > 0 {
fmt.Printf("%d\t%s (original %d)\n", record.DraftPostID, record.PostTitle, record.OriginalPostID)
} else {
fmt.Printf("%d\t%s\n", record.DraftPostID, record.PostTitle)
}
}
}
func parseIntArg(name, value string) (int, error) {
parsed, err := strconv.Atoi(value)
if err != nil {
return 0, fmt.Errorf("%s must be an integer", name)
}
return parsed, nil
}
func die(msg string) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}