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 := apiClient.ListPeople(cmd.Context(), opts)
49 if err != nil {
50 return err
51 }
52
53 relFilter, _ := cmd.Flags().GetString("relationship")
54 if relFilter != "" {
55 people = filterByRelationship(people, relFilter)
56 }
57
58 if len(people) == 0 {
59 fmt.Fprintln(cmd.OutOrStdout(), "No people found")
60
61 return nil
62 }
63
64 if mustGetBoolFlag(cmd, "json") {
65 return outputJSON(cmd, people)
66 }
67
68 return outputTable(cmd, people)
69}
70
71func buildListOptions(cmd *cobra.Command) *lunatask.ListPeopleOptions {
72 source, _ := cmd.Flags().GetString("source")
73 sourceID, _ := cmd.Flags().GetString("source-id")
74
75 if source == "" && sourceID == "" {
76 return nil
77 }
78
79 opts := &lunatask.ListPeopleOptions{}
80 if source != "" {
81 opts.Source = &source
82 }
83
84 if sourceID != "" {
85 opts.SourceID = &sourceID
86 }
87
88 return opts
89}
90
91func filterByRelationship(people []lunatask.Person, rel string) []lunatask.Person {
92 filtered := make([]lunatask.Person, 0, len(people))
93
94 for _, person := range people {
95 if person.RelationshipStrength != nil && string(*person.RelationshipStrength) == rel {
96 filtered = append(filtered, person)
97 }
98 }
99
100 return filtered
101}
102
103func mustGetBoolFlag(cmd *cobra.Command, name string) bool {
104 f := cmd.Flags().Lookup(name)
105 if f == nil {
106 panic("flag not defined: " + name)
107 }
108
109 return f.Value.String() == "true"
110}
111
112func outputJSON(cmd *cobra.Command, people []lunatask.Person) error {
113 enc := json.NewEncoder(cmd.OutOrStdout())
114 enc.SetIndent("", " ")
115
116 if err := enc.Encode(people); err != nil {
117 return fmt.Errorf("encoding JSON: %w", err)
118 }
119
120 return nil
121}
122
123func outputTable(cmd *cobra.Command, people []lunatask.Person) error {
124 rows := make([][]string, 0, len(people))
125
126 for _, person := range people {
127 rel := "-"
128 if person.RelationshipStrength != nil {
129 rel = string(*person.RelationshipStrength)
130 }
131
132 created := ui.FormatDate(person.CreatedAt)
133
134 rows = append(rows, []string{person.ID, rel, created})
135 }
136
137 tbl := table.New().
138 Headers("ID", "RELATIONSHIP", "CREATED").
139 Rows(rows...).
140 StyleFunc(func(row, col int) lipgloss.Style {
141 if row == table.HeaderRow {
142 return ui.Bold
143 }
144
145 return lipgloss.NewStyle()
146 }).
147 Border(lipgloss.HiddenBorder())
148
149 fmt.Fprintln(cmd.OutOrStdout(), tbl.Render())
150
151 return nil
152}