Skip to content

Commit 2a28159

Browse files
committed
feat(wip): create or update post handler & command
1 parent f5d0c10 commit 2a28159

File tree

4 files changed

+872
-68
lines changed

4 files changed

+872
-68
lines changed

cmd/publication_commands.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"fmt"
5+
"strconv"
56

67
"github.com/spf13/cobra"
78
"github.com/stormlightlabs/noteleaf/internal/handlers"
@@ -154,5 +155,98 @@ Use filters to show specific subsets:
154155
}
155156
root.AddCommand(statusCmd)
156157

158+
postCmd := &cobra.Command{
159+
Use: "post [note-id]",
160+
Short: "Create a new document on leaflet",
161+
Long: `Publish a local note to leaflet.pub as a new document.
162+
163+
This command converts your markdown note to leaflet's block format and creates
164+
a new document on the platform. The note will be linked to the leaflet document
165+
for future updates via the patch command.
166+
167+
Examples:
168+
noteleaf pub post 123 # Publish note 123
169+
noteleaf pub post 123 --draft # Create as draft
170+
noteleaf pub post 123 --preview # Preview without posting
171+
noteleaf pub post 123 --validate # Validate conversion only`,
172+
Args: cobra.ExactArgs(1),
173+
RunE: func(cmd *cobra.Command, args []string) error {
174+
noteID, err := parseNoteID(args[0])
175+
if err != nil {
176+
return err
177+
}
178+
179+
isDraft, _ := cmd.Flags().GetBool("draft")
180+
preview, _ := cmd.Flags().GetBool("preview")
181+
validate, _ := cmd.Flags().GetBool("validate")
182+
183+
defer c.handler.Close()
184+
185+
if preview {
186+
return c.handler.PostPreview(cmd.Context(), noteID, isDraft)
187+
}
188+
189+
if validate {
190+
return c.handler.PostValidate(cmd.Context(), noteID, isDraft)
191+
}
192+
193+
return c.handler.Post(cmd.Context(), noteID, isDraft)
194+
},
195+
}
196+
postCmd.Flags().Bool("draft", false, "Create as draft instead of publishing")
197+
postCmd.Flags().Bool("preview", false, "Show what would be posted without actually posting")
198+
postCmd.Flags().Bool("validate", false, "Validate markdown conversion without posting")
199+
root.AddCommand(postCmd)
200+
201+
patchCmd := &cobra.Command{
202+
Use: "patch [note-id]",
203+
Short: "Update an existing document on leaflet",
204+
Long: `Update an existing leaflet document from a local note.
205+
206+
This command converts your markdown note to leaflet's block format and updates
207+
the existing document on the platform. The note must have been previously posted
208+
or pulled from leaflet (it needs a leaflet record key).
209+
210+
The document's draft/published status is preserved from the note's current state.
211+
212+
Examples:
213+
noteleaf pub patch 123 # Update existing document
214+
noteleaf pub patch 123 --preview # Preview without updating
215+
noteleaf pub patch 123 --validate # Validate conversion only`,
216+
Args: cobra.ExactArgs(1),
217+
RunE: func(cmd *cobra.Command, args []string) error {
218+
noteID, err := parseNoteID(args[0])
219+
if err != nil {
220+
return err
221+
}
222+
223+
preview, _ := cmd.Flags().GetBool("preview")
224+
validate, _ := cmd.Flags().GetBool("validate")
225+
226+
defer c.handler.Close()
227+
228+
if preview {
229+
return c.handler.PatchPreview(cmd.Context(), noteID)
230+
}
231+
232+
if validate {
233+
return c.handler.PatchValidate(cmd.Context(), noteID)
234+
}
235+
236+
return c.handler.Patch(cmd.Context(), noteID)
237+
},
238+
}
239+
patchCmd.Flags().Bool("preview", false, "Show what would be updated without actually patching")
240+
patchCmd.Flags().Bool("validate", false, "Validate markdown conversion without patching")
241+
root.AddCommand(patchCmd)
242+
157243
return root
158244
}
245+
246+
func parseNoteID(arg string) (int64, error) {
247+
noteID, err := strconv.ParseInt(arg, 10, 64)
248+
if err != nil {
249+
return 0, fmt.Errorf("invalid note ID '%s': must be a number", arg)
250+
}
251+
return noteID, nil
252+
}

cmd/publication_commands_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ func TestPublicationCommand(t *testing.T) {
6666
"pull",
6767
"list [--published|--draft|--all]",
6868
"status",
69+
"post [note-id]",
70+
"patch [note-id]",
6971
}
7072

7173
for _, expected := range expectedSubcommands {
@@ -176,6 +178,196 @@ func TestPublicationCommand(t *testing.T) {
176178
})
177179
})
178180

181+
t.Run("Post Command", func(t *testing.T) {
182+
t.Run("requires note ID argument", func(t *testing.T) {
183+
handler, cleanup := createTestPublicationHandler(t)
184+
defer cleanup()
185+
186+
cmd := NewPublicationCommand(handler).Create()
187+
cmd.SetArgs([]string{"post"})
188+
err := cmd.Execute()
189+
190+
if err == nil {
191+
t.Error("Expected error for missing note ID")
192+
}
193+
})
194+
195+
t.Run("rejects invalid note ID", func(t *testing.T) {
196+
handler, cleanup := createTestPublicationHandler(t)
197+
defer cleanup()
198+
199+
cmd := NewPublicationCommand(handler).Create()
200+
cmd.SetArgs([]string{"post", "not-a-number"})
201+
err := cmd.Execute()
202+
203+
if err == nil {
204+
t.Error("Expected error for invalid note ID")
205+
}
206+
if !strings.Contains(err.Error(), "invalid note ID") {
207+
t.Errorf("Expected 'invalid note ID' error, got: %v", err)
208+
}
209+
})
210+
211+
t.Run("fails when not authenticated", func(t *testing.T) {
212+
handler, cleanup := createTestPublicationHandler(t)
213+
defer cleanup()
214+
215+
cmd := NewPublicationCommand(handler).Create()
216+
cmd.SetArgs([]string{"post", "123"})
217+
err := cmd.Execute()
218+
219+
if err == nil {
220+
t.Error("Expected post to fail when not authenticated")
221+
}
222+
if !strings.Contains(err.Error(), "not authenticated") {
223+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
224+
}
225+
})
226+
227+
t.Run("preview mode fails when not authenticated", func(t *testing.T) {
228+
handler, cleanup := createTestPublicationHandler(t)
229+
defer cleanup()
230+
231+
cmd := NewPublicationCommand(handler).Create()
232+
cmd.SetArgs([]string{"post", "123", "--preview"})
233+
err := cmd.Execute()
234+
235+
if err == nil {
236+
t.Error("Expected post --preview to fail when not authenticated")
237+
}
238+
if !strings.Contains(err.Error(), "not authenticated") {
239+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
240+
}
241+
})
242+
243+
t.Run("validate mode fails when not authenticated", func(t *testing.T) {
244+
handler, cleanup := createTestPublicationHandler(t)
245+
defer cleanup()
246+
247+
cmd := NewPublicationCommand(handler).Create()
248+
cmd.SetArgs([]string{"post", "123", "--validate"})
249+
err := cmd.Execute()
250+
251+
if err == nil {
252+
t.Error("Expected post --validate to fail when not authenticated")
253+
}
254+
if !strings.Contains(err.Error(), "not authenticated") {
255+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
256+
}
257+
})
258+
259+
t.Run("accepts draft flag", func(t *testing.T) {
260+
handler, cleanup := createTestPublicationHandler(t)
261+
defer cleanup()
262+
263+
cmd := NewPublicationCommand(handler).Create()
264+
cmd.SetArgs([]string{"post", "123", "--draft"})
265+
err := cmd.Execute()
266+
267+
if err == nil {
268+
t.Error("Expected post --draft to fail when not authenticated")
269+
}
270+
if !strings.Contains(err.Error(), "not authenticated") {
271+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
272+
}
273+
})
274+
275+
t.Run("accepts preview and draft flags together", func(t *testing.T) {
276+
handler, cleanup := createTestPublicationHandler(t)
277+
defer cleanup()
278+
279+
cmd := NewPublicationCommand(handler).Create()
280+
cmd.SetArgs([]string{"post", "123", "--preview", "--draft"})
281+
err := cmd.Execute()
282+
283+
if err == nil {
284+
t.Error("Expected post --preview --draft to fail when not authenticated")
285+
}
286+
if !strings.Contains(err.Error(), "not authenticated") {
287+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
288+
}
289+
})
290+
})
291+
292+
t.Run("Patch Command", func(t *testing.T) {
293+
t.Run("requires note ID argument", func(t *testing.T) {
294+
handler, cleanup := createTestPublicationHandler(t)
295+
defer cleanup()
296+
297+
cmd := NewPublicationCommand(handler).Create()
298+
cmd.SetArgs([]string{"patch"})
299+
err := cmd.Execute()
300+
301+
if err == nil {
302+
t.Error("Expected error for missing note ID")
303+
}
304+
})
305+
306+
t.Run("rejects invalid note ID", func(t *testing.T) {
307+
handler, cleanup := createTestPublicationHandler(t)
308+
defer cleanup()
309+
310+
cmd := NewPublicationCommand(handler).Create()
311+
cmd.SetArgs([]string{"patch", "not-a-number"})
312+
err := cmd.Execute()
313+
314+
if err == nil {
315+
t.Error("Expected error for invalid note ID")
316+
}
317+
if !strings.Contains(err.Error(), "invalid note ID") {
318+
t.Errorf("Expected 'invalid note ID' error, got: %v", err)
319+
}
320+
})
321+
322+
t.Run("fails when not authenticated", func(t *testing.T) {
323+
handler, cleanup := createTestPublicationHandler(t)
324+
defer cleanup()
325+
326+
cmd := NewPublicationCommand(handler).Create()
327+
cmd.SetArgs([]string{"patch", "123"})
328+
err := cmd.Execute()
329+
330+
if err == nil {
331+
t.Error("Expected patch to fail when not authenticated")
332+
}
333+
if !strings.Contains(err.Error(), "not authenticated") {
334+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
335+
}
336+
})
337+
338+
t.Run("preview mode fails when not authenticated", func(t *testing.T) {
339+
handler, cleanup := createTestPublicationHandler(t)
340+
defer cleanup()
341+
342+
cmd := NewPublicationCommand(handler).Create()
343+
cmd.SetArgs([]string{"patch", "123", "--preview"})
344+
err := cmd.Execute()
345+
346+
if err == nil {
347+
t.Error("Expected patch --preview to fail when not authenticated")
348+
}
349+
if !strings.Contains(err.Error(), "not authenticated") {
350+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
351+
}
352+
})
353+
354+
t.Run("validate mode fails when not authenticated", func(t *testing.T) {
355+
handler, cleanup := createTestPublicationHandler(t)
356+
defer cleanup()
357+
358+
cmd := NewPublicationCommand(handler).Create()
359+
cmd.SetArgs([]string{"patch", "123", "--validate"})
360+
err := cmd.Execute()
361+
362+
if err == nil {
363+
t.Error("Expected patch --validate to fail when not authenticated")
364+
}
365+
if !strings.Contains(err.Error(), "not authenticated") {
366+
t.Errorf("Expected 'not authenticated' error, got: %v", err)
367+
}
368+
})
369+
})
370+
179371
t.Run("Command Help", func(t *testing.T) {
180372
t.Run("root help", func(t *testing.T) {
181373
handler, cleanup := createTestPublicationHandler(t)

0 commit comments

Comments
 (0)