1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package person
6
7import (
8 "encoding/json"
9 "fmt"
10
11 "git.secluded.site/go-lunatask"
12 "git.secluded.site/lune/internal/client"
13 "git.secluded.site/lune/internal/completion"
14 "git.secluded.site/lune/internal/ui"
15 "github.com/charmbracelet/lipgloss"
16 "github.com/charmbracelet/lipgloss/table"
17 "github.com/spf13/cobra"
18)
19
20// ListCmd lists people. Exported for potential use by shortcuts.
21var ListCmd = &cobra.Command{
22 Use: "list",
23 Short: "List people",
24 Long: `List people from Lunatask.
25
26Note: Due to end-to-end encryption, names are not available
27through the API. Only metadata is shown.`,
28 RunE: runList,
29}
30
31func init() {
32 ListCmd.Flags().StringP("relationship", "r", "", "Filter by relationship strength")
33 ListCmd.Flags().String("source", "", "Filter by source")
34 ListCmd.Flags().String("source-id", "", "Filter by source ID")
35 ListCmd.Flags().Bool("json", false, "Output as JSON")
36
37 _ = ListCmd.RegisterFlagCompletionFunc("relationship", completion.Relationships)
38}
39
40func runList(cmd *cobra.Command, _ []string) error {
41 apiClient, err := client.New()
42 if err != nil {
43 return err
44 }
45
46 opts := buildListOptions(cmd)
47
48 people, err := ui.Spin("Fetching people…", func() ([]lunatask.Person, error) {
49 return apiClient.ListPeople(cmd.Context(), opts)
50 })
51 if err != nil {
52 return err
53 }
54
55 relFilter, _ := cmd.Flags().GetString("relationship")
56 if relFilter != "" {
57 people = filterByRelationship(people, relFilter)
58 }
59
60 if len(people) == 0 {
61 fmt.Fprintln(cmd.OutOrStdout(), "No people found")
62
63 return nil
64 }
65
66 if mustGetBoolFlag(cmd, "json") {
67 return outputJSON(cmd, people)
68 }
69
70 return outputTable(cmd, people)
71}
72
73func buildListOptions(cmd *cobra.Command) *lunatask.ListPeopleOptions {
74 source, _ := cmd.Flags().GetString("source")
75 sourceID, _ := cmd.Flags().GetString("source-id")
76
77 if source == "" && sourceID == "" {
78 return nil
79 }
80
81 opts := &lunatask.ListPeopleOptions{}
82 if source != "" {
83 opts.Source = &source
84 }
85
86 if sourceID != "" {
87 opts.SourceID = &sourceID
88 }
89
90 return opts
91}
92
93func filterByRelationship(people []lunatask.Person, rel string) []lunatask.Person {
94 filtered := make([]lunatask.Person, 0, len(people))
95
96 for _, person := range people {
97 if person.RelationshipStrength != nil && string(*person.RelationshipStrength) == rel {
98 filtered = append(filtered, person)
99 }
100 }
101
102 return filtered
103}
104
105func mustGetBoolFlag(cmd *cobra.Command, name string) bool {
106 f := cmd.Flags().Lookup(name)
107 if f == nil {
108 panic("flag not defined: " + name)
109 }
110
111 return f.Value.String() == "true"
112}
113
114func outputJSON(cmd *cobra.Command, people []lunatask.Person) error {
115 enc := json.NewEncoder(cmd.OutOrStdout())
116 enc.SetIndent("", " ")
117
118 if err := enc.Encode(people); err != nil {
119 return fmt.Errorf("encoding JSON: %w", err)
120 }
121
122 return nil
123}
124
125func outputTable(cmd *cobra.Command, people []lunatask.Person) error {
126 rows := make([][]string, 0, len(people))
127
128 for _, person := range people {
129 rel := "-"
130 if person.RelationshipStrength != nil {
131 rel = string(*person.RelationshipStrength)
132 }
133
134 created := ui.FormatDate(person.CreatedAt)
135
136 rows = append(rows, []string{person.ID, rel, created})
137 }
138
139 tbl := table.New().
140 Headers("ID", "RELATIONSHIP", "CREATED").
141 Rows(rows...).
142 StyleFunc(func(row, col int) lipgloss.Style {
143 if row == table.HeaderRow {
144 return ui.TableHeaderStyle()
145 }
146
147 return lipgloss.NewStyle()
148 }).
149 Border(ui.TableBorder())
150
151 fmt.Fprintln(cmd.OutOrStdout(), tbl.Render())
152
153 return nil
154}