Skip to content

Commit 42044fd

Browse files
committed
feat(wip): push to leaflet!
1 parent 15437db commit 42044fd

File tree

10 files changed

+675
-149
lines changed

10 files changed

+675
-149
lines changed

cmd/commands.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ func (c *MovieCommand) Create() *cobra.Command {
3030
Short: "Manage movie watch queue",
3131
Long: `Track movies you want to watch.
3232
33-
Search TMDB for movies and add them to your queue. Mark movies as watched when
34-
completed. Maintains a history of your movie watching activity.`,
33+
Search for movies and add them to your queue. Mark movies as watched
34+
when completed. Maintains a history of your movie watching activity.`,
3535
}
3636

37+
// TODO: add colors
38+
// TODO: fix critic score parsing
3739
addCmd := &cobra.Command{
3840
Use: "add [search query...]",
3941
Short: "Search and add movie to watch queue",
@@ -54,6 +56,7 @@ Use the -i flag for an interactive interface with navigation keys.`,
5456
addCmd.Flags().BoolP("interactive", "i", false, "Use interactive interface for movie selection")
5557
root.AddCommand(addCmd)
5658

59+
// TODO: add interactive list view
5760
root.AddCommand(&cobra.Command{
5861
Use: "list [--all|--watched|--queued]",
5962
Short: "List movies in queue with status filtering",
@@ -122,9 +125,9 @@ func (c *TVCommand) Create() *cobra.Command {
122125
Short: "Manage TV show watch queue",
123126
Long: `Track TV shows and episodes.
124127
125-
Search TMDB for TV shows and add them to your queue. Track which shows you're
126-
currently watching, mark episodes as watched, and maintain a complete history
127-
of your viewing activity.`,
128+
Search for TV shows and add them to your queue. Track which shows you're currently
129+
watching, mark episodes as watched, and maintain a complete history of your viewing
130+
activity.`,
128131
}
129132

130133
addCmd := &cobra.Command{

cmd/publication_commands.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ If credentials are not provided via flags, use the interactive input.`,
5959
handle = args[0]
6060
}
6161

62+
// Auto-fill with last authenticated handle if available
63+
if handle == "" {
64+
lastHandle := c.handler.GetLastAuthenticatedHandle()
65+
if lastHandle != "" {
66+
handle = lastHandle
67+
}
68+
}
69+
6270
password, _ := cmd.Flags().GetString("password")
6371

6472
if handle != "" && password != "" {

internal/handlers/publication.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/charmbracelet/log"
1314
"github.com/stormlightlabs/noteleaf/internal/models"
1415
"github.com/stormlightlabs/noteleaf/internal/public"
1516
"github.com/stormlightlabs/noteleaf/internal/repo"
1617
"github.com/stormlightlabs/noteleaf/internal/services"
18+
"github.com/stormlightlabs/noteleaf/internal/shared"
1719
"github.com/stormlightlabs/noteleaf/internal/store"
1820
"github.com/stormlightlabs/noteleaf/internal/ui"
1921
)
@@ -24,6 +26,7 @@ type PublicationHandler struct {
2426
config *store.Config
2527
repos *repo.Repositories
2628
atproto services.ATProtoClient
29+
debug *log.Logger
2730
}
2831

2932
// NewPublicationHandler creates a new publication handler
@@ -41,10 +44,21 @@ func NewPublicationHandler() (*PublicationHandler, error) {
4144
repos := repo.NewRepositories(db.DB)
4245
atproto := services.NewATProtoService()
4346

47+
d, _ := store.GetConfigDir()
48+
debug := shared.NewDebugLoggerWithFile(d)
49+
4450
if config.ATProtoDID != "" && config.ATProtoAccessJWT != "" && config.ATProtoRefreshJWT != "" {
4551
session, err := sessionFromConfig(config)
4652
if err == nil {
47-
_ = atproto.RestoreSession(session)
53+
if err := atproto.RestoreSession(session); err == nil {
54+
updatedSession, _ := atproto.GetSession()
55+
if updatedSession != nil {
56+
config.ATProtoAccessJWT = updatedSession.AccessJWT
57+
config.ATProtoRefreshJWT = updatedSession.RefreshJWT
58+
config.ATProtoExpiresAt = updatedSession.ExpiresAt.Format("2006-01-02T15:04:05Z07:00")
59+
_ = store.SaveConfig(config)
60+
}
61+
}
4862
}
4963
}
5064

@@ -53,6 +67,7 @@ func NewPublicationHandler() (*PublicationHandler, error) {
5367
config: config,
5468
repos: repos,
5569
atproto: atproto,
70+
debug: debug,
5671
}, nil
5772
}
5873

@@ -272,7 +287,6 @@ func (h *PublicationHandler) Post(ctx context.Context, noteID int64, isDraft boo
272287
if err != nil {
273288
return err
274289
}
275-
276290
ui.Infoln("Creating document '%s' on leaflet...", note.Title)
277291

278292
result, err := h.atproto.PostDocument(ctx, *doc, isDraft)
@@ -665,10 +679,22 @@ func (h *PublicationHandler) prepareDocumentForPublish(ctx context.Context, note
665679
return nil, nil, fmt.Errorf("failed to convert markdown to leaflet format: %w", err)
666680
}
667681

682+
publicationURI, err := h.atproto.GetDefaultPublication(ctx)
683+
if err != nil {
684+
return nil, nil, fmt.Errorf("failed to get publication: %w", err)
685+
}
686+
687+
docType := public.TypeDocument
688+
if isDraft {
689+
docType = public.TypeDocumentDraft
690+
}
691+
668692
doc := &public.Document{
693+
Type: docType,
669694
Author: session.DID,
670695
Title: note.Title,
671696
Description: "",
697+
Publication: publicationURI,
672698
Pages: []public.LinearDocument{
673699
{
674700
Type: public.TypeLinearDocument,
@@ -903,6 +929,14 @@ func (h *PublicationHandler) GetAuthStatus() string {
903929
return "Not authenticated"
904930
}
905931

932+
// GetLastAuthenticatedHandle returns the last authenticated handle from config
933+
func (h *PublicationHandler) GetLastAuthenticatedHandle() string {
934+
if h.config != nil && h.config.ATProtoHandle != "" {
935+
return h.config.ATProtoHandle
936+
}
937+
return ""
938+
}
939+
906940
// extractNoteDirectory extracts the directory path from a note's FilePath
907941
func extractNoteDirectory(note *models.Note) string {
908942
if note.FilePath == "" {

internal/handlers/publication_test.go

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,63 @@ func TestPublicationHandler(t *testing.T) {
189189
})
190190
})
191191

192+
t.Run("GetLastAuthenticatedHandle", func(t *testing.T) {
193+
t.Run("returns empty string when no config", func(t *testing.T) {
194+
handler := &PublicationHandler{
195+
config: nil,
196+
}
197+
198+
handle := handler.GetLastAuthenticatedHandle()
199+
if handle != "" {
200+
t.Errorf("Expected empty string, got '%s'", handle)
201+
}
202+
})
203+
204+
t.Run("returns empty string when handle not set", func(t *testing.T) {
205+
handler := &PublicationHandler{
206+
config: &store.Config{},
207+
}
208+
209+
handle := handler.GetLastAuthenticatedHandle()
210+
if handle != "" {
211+
t.Errorf("Expected empty string, got '%s'", handle)
212+
}
213+
})
214+
215+
t.Run("returns handle from config", func(t *testing.T) {
216+
expectedHandle := "test.bsky.social"
217+
handler := &PublicationHandler{
218+
config: &store.Config{
219+
ATProtoHandle: expectedHandle,
220+
},
221+
}
222+
223+
handle := handler.GetLastAuthenticatedHandle()
224+
if handle != expectedHandle {
225+
t.Errorf("Expected '%s', got '%s'", expectedHandle, handle)
226+
}
227+
})
228+
229+
t.Run("returns handle after successful authentication", func(t *testing.T) {
230+
suite := NewHandlerTestSuite(t)
231+
defer suite.Cleanup()
232+
233+
handler := CreateHandler(t, NewPublicationHandler)
234+
ctx := context.Background()
235+
236+
mock := services.SetupSuccessfulAuthMocks()
237+
handler.atproto = mock
238+
239+
err := handler.Auth(ctx, "user.bsky.social", "password123")
240+
suite.AssertNoError(err, "authentication should succeed")
241+
242+
handle := handler.GetLastAuthenticatedHandle()
243+
if handle != "user.bsky.social" {
244+
t.Errorf("Expected 'user.bsky.social', got '%s'", handle)
245+
}
246+
})
247+
})
248+
192249
t.Run("NewPublicationHandler", func(t *testing.T) {
193250
t.Run("creates handler successfully", func(t *testing.T) {
194251
suite := NewHandlerTestSuite(t)
@@ -1120,7 +1177,9 @@ func TestPublicationHandler(t *testing.T) {
11201177
id, err := handler.repos.Notes.Create(ctx, note)
11211178
suite.AssertNoError(err, "create note")
11221179

1123-
session := &services.Session{
1180+
mock := services.NewMockATProtoService()
1181+
mock.IsAuthenticatedVal = true
1182+
mock.Session = &services.Session{
11241183
DID: "did:plc:test123",
11251184
Handle: "test.bsky.social",
11261185
AccessJWT: "access_token",
@@ -1129,11 +1188,7 @@ func TestPublicationHandler(t *testing.T) {
11291188
ExpiresAt: time.Now().Add(2 * time.Hour),
11301189
Authenticated: true,
11311190
}
1132-
1133-
err = handler.atproto.RestoreSession(session)
1134-
if err != nil {
1135-
t.Fatalf("Failed to restore session: %v", err)
1136-
}
1191+
handler.atproto = mock
11371192

11381193
err = handler.PostPreview(ctx, id, false, "", false)
11391194
suite.AssertNoError(err, "preview should succeed")
@@ -1154,7 +1209,9 @@ func TestPublicationHandler(t *testing.T) {
11541209
id, err := handler.repos.Notes.Create(ctx, note)
11551210
suite.AssertNoError(err, "create note")
11561211

1157-
session := &services.Session{
1212+
mock := services.NewMockATProtoService()
1213+
mock.IsAuthenticatedVal = true
1214+
mock.Session = &services.Session{
11581215
DID: "did:plc:test123",
11591216
Handle: "test.bsky.social",
11601217
AccessJWT: "access_token",
@@ -1163,11 +1220,7 @@ func TestPublicationHandler(t *testing.T) {
11631220
ExpiresAt: time.Now().Add(2 * time.Hour),
11641221
Authenticated: true,
11651222
}
1166-
1167-
err = handler.atproto.RestoreSession(session)
1168-
if err != nil {
1169-
t.Fatalf("Failed to restore session: %v", err)
1170-
}
1223+
handler.atproto = mock
11711224

11721225
err = handler.PostPreview(ctx, id, true, "", false)
11731226
suite.AssertNoError(err, "preview draft should succeed")
@@ -1207,7 +1260,9 @@ func TestPublicationHandler(t *testing.T) {
12071260
id, err := handler.repos.Notes.Create(ctx, note)
12081261
suite.AssertNoError(err, "create note")
12091262

1210-
session := &services.Session{
1263+
mock := services.NewMockATProtoService()
1264+
mock.IsAuthenticatedVal = true
1265+
mock.Session = &services.Session{
12111266
DID: "did:plc:test123",
12121267
Handle: "test.bsky.social",
12131268
AccessJWT: "access_token",
@@ -1216,11 +1271,7 @@ func TestPublicationHandler(t *testing.T) {
12161271
ExpiresAt: time.Now().Add(2 * time.Hour),
12171272
Authenticated: true,
12181273
}
1219-
1220-
err = handler.atproto.RestoreSession(session)
1221-
if err != nil {
1222-
t.Fatalf("Failed to restore session: %v", err)
1223-
}
1274+
handler.atproto = mock
12241275

12251276
err = handler.PostValidate(ctx, id, false, "", false)
12261277
suite.AssertNoError(err, "validation should succeed")
@@ -1339,7 +1390,9 @@ func TestPublicationHandler(t *testing.T) {
13391390
id, err := handler.repos.Notes.Create(ctx, note)
13401391
suite.AssertNoError(err, "create note")
13411392

1342-
session := &services.Session{
1393+
mock := services.NewMockATProtoService()
1394+
mock.IsAuthenticatedVal = true
1395+
mock.Session = &services.Session{
13431396
DID: "did:plc:test123",
13441397
Handle: "test.bsky.social",
13451398
AccessJWT: "access_token",
@@ -1348,11 +1401,7 @@ func TestPublicationHandler(t *testing.T) {
13481401
ExpiresAt: time.Now().Add(2 * time.Hour),
13491402
Authenticated: true,
13501403
}
1351-
1352-
err = handler.atproto.RestoreSession(session)
1353-
if err != nil {
1354-
t.Fatalf("Failed to restore session: %v", err)
1355-
}
1404+
handler.atproto = mock
13561405

13571406
err = handler.PatchPreview(ctx, id, "", false)
13581407
suite.AssertNoError(err, "preview should succeed")
@@ -1397,7 +1446,9 @@ func TestPublicationHandler(t *testing.T) {
13971446
id, err := handler.repos.Notes.Create(ctx, note)
13981447
suite.AssertNoError(err, "create note")
13991448

1400-
session := &services.Session{
1449+
mock := services.NewMockATProtoService()
1450+
mock.IsAuthenticatedVal = true
1451+
mock.Session = &services.Session{
14011452
DID: "did:plc:test123",
14021453
Handle: "test.bsky.social",
14031454
AccessJWT: "access_token",
@@ -1406,11 +1457,7 @@ func TestPublicationHandler(t *testing.T) {
14061457
ExpiresAt: time.Now().Add(2 * time.Hour),
14071458
Authenticated: true,
14081459
}
1409-
1410-
err = handler.atproto.RestoreSession(session)
1411-
if err != nil {
1412-
t.Fatalf("Failed to restore session: %v", err)
1413-
}
1460+
handler.atproto = mock
14141461

14151462
err = handler.PatchValidate(ctx, id, "", false)
14161463
suite.AssertNoError(err, "validation should succeed")

0 commit comments

Comments
 (0)