Skip to content

Commit 37fbeda

Browse files
committed
Add message count and last sync to list-accounts
1 parent feea6a3 commit 37fbeda

File tree

2 files changed

+125
-22
lines changed

2 files changed

+125
-22
lines changed

cmd/msgvault/cmd/list_accounts.go

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import (
55
"fmt"
66
"os"
77
"text/tabwriter"
8+
"time"
89

910
"github.com/spf13/cobra"
10-
"github.com/wesm/msgvault/internal/query"
1111
"github.com/wesm/msgvault/internal/store"
1212
)
1313

@@ -18,6 +18,8 @@ var listAccountsCmd = &cobra.Command{
1818
Short: "List synced email accounts",
1919
Long: `List all email accounts that have been added to msgvault.
2020
21+
Shows account email, message count, and last sync time.
22+
2123
Examples:
2224
msgvault list-accounts
2325
msgvault list-accounts --json`,
@@ -29,59 +31,121 @@ Examples:
2931
}
3032
defer s.Close()
3133

32-
engine := query.NewSQLiteEngine(s.DB())
33-
34-
accounts, err := engine.ListAccounts(cmd.Context())
34+
sources, err := s.ListSources()
3535
if err != nil {
3636
return fmt.Errorf("list accounts: %w", err)
3737
}
3838

39-
if len(accounts) == 0 {
39+
if len(sources) == 0 {
4040
fmt.Println("No accounts found. Use 'msgvault add-account <email>' to add one.")
4141
return nil
4242
}
4343

44+
// Gather stats for each account
45+
stats := make([]accountStats, len(sources))
46+
for i, src := range sources {
47+
count, err := s.CountMessagesForSource(src.ID)
48+
if err != nil {
49+
return fmt.Errorf("count messages for %s: %w", src.Identifier, err)
50+
}
51+
52+
var lastSync *time.Time
53+
if src.LastSyncAt.Valid {
54+
lastSync = &src.LastSyncAt.Time
55+
}
56+
57+
displayName := ""
58+
if src.DisplayName.Valid {
59+
displayName = src.DisplayName.String
60+
}
61+
62+
stats[i] = accountStats{
63+
ID: src.ID,
64+
Email: src.Identifier,
65+
Type: src.SourceType,
66+
DisplayName: displayName,
67+
MessageCount: count,
68+
LastSync: lastSync,
69+
}
70+
}
71+
4472
if listAccountsJSON {
45-
return outputAccountsJSON(accounts)
73+
return outputAccountsJSON(stats)
4674
}
47-
outputAccountsTable(accounts)
75+
outputAccountsTable(stats)
4876
return nil
4977
},
5078
}
5179

52-
func outputAccountsTable(accounts []query.AccountInfo) {
80+
func outputAccountsTable(stats []accountStats) {
5381
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
54-
fmt.Fprintln(w, "ID\tEMAIL\tTYPE\tDISPLAY NAME")
55-
fmt.Fprintln(w, "──\t─────\t────\t────────────")
82+
fmt.Fprintln(w, "ID\tACCOUNT\tTYPE\tDISPLAY NAME\tMESSAGES\tLAST SYNC")
5683

57-
for _, acc := range accounts {
58-
displayName := acc.DisplayName
84+
for _, s := range stats {
85+
displayName := s.DisplayName
5986
if displayName == "" {
6087
displayName = "-"
6188
}
62-
fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", acc.ID, acc.Identifier, acc.SourceType, displayName)
89+
lastSync := "-"
90+
if s.LastSync != nil && !s.LastSync.IsZero() {
91+
lastSync = s.LastSync.Format("2006-01-02 15:04")
92+
}
93+
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%s\n", s.ID, s.Email, s.Type, displayName, formatCount(s.MessageCount), lastSync)
6394
}
6495

6596
w.Flush()
66-
fmt.Printf("\n%d account(s)\n", len(accounts))
6797
}
6898

69-
func outputAccountsJSON(accounts []query.AccountInfo) error {
70-
output := make([]map[string]interface{}, len(accounts))
71-
for i, acc := range accounts {
72-
output[i] = map[string]interface{}{
73-
"id": acc.ID,
74-
"email": acc.Identifier,
75-
"type": acc.SourceType,
76-
"display_name": acc.DisplayName,
99+
func outputAccountsJSON(stats []accountStats) error {
100+
output := make([]map[string]interface{}, len(stats))
101+
for i, s := range stats {
102+
entry := map[string]interface{}{
103+
"id": s.ID,
104+
"email": s.Email,
105+
"type": s.Type,
106+
"display_name": s.DisplayName,
107+
"message_count": s.MessageCount,
108+
}
109+
if s.LastSync != nil && !s.LastSync.IsZero() {
110+
entry["last_sync"] = s.LastSync.Format(time.RFC3339)
111+
} else {
112+
entry["last_sync"] = nil
77113
}
114+
output[i] = entry
78115
}
79116

80117
enc := json.NewEncoder(os.Stdout)
81118
enc.SetIndent("", " ")
82119
return enc.Encode(output)
83120
}
84121

122+
// formatCount formats a number with thousand separators.
123+
func formatCount(n int64) string {
124+
if n < 1000 {
125+
return fmt.Sprintf("%d", n)
126+
}
127+
128+
// Format with commas
129+
s := fmt.Sprintf("%d", n)
130+
result := make([]byte, 0, len(s)+(len(s)-1)/3)
131+
for i, c := range s {
132+
if i > 0 && (len(s)-i)%3 == 0 {
133+
result = append(result, ',')
134+
}
135+
result = append(result, byte(c))
136+
}
137+
return string(result)
138+
}
139+
140+
type accountStats struct {
141+
ID int64
142+
Email string
143+
Type string
144+
DisplayName string
145+
MessageCount int64
146+
LastSync *time.Time
147+
}
148+
85149
func init() {
86150
rootCmd.AddCommand(listAccountsCmd)
87151
listAccountsCmd.Flags().BoolVar(&listAccountsJSON, "json", false, "Output as JSON")

internal/store/sync.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,42 @@ func (s *Store) GetSourceByIdentifier(identifier string) (*Source, error) {
292292

293293
return &source, nil
294294
}
295+
296+
// ListSources returns all sources ordered by identifier.
297+
func (s *Store) ListSources() ([]*Source, error) {
298+
rows, err := s.db.Query(`
299+
SELECT id, source_type, identifier, display_name, google_user_id,
300+
last_sync_at, sync_cursor, created_at, updated_at
301+
FROM sources
302+
ORDER BY identifier
303+
`)
304+
if err != nil {
305+
return nil, err
306+
}
307+
defer rows.Close()
308+
309+
var sources []*Source
310+
for rows.Next() {
311+
var source Source
312+
var createdAt, updatedAt sql.NullTime
313+
314+
err := rows.Scan(
315+
&source.ID, &source.SourceType, &source.Identifier, &source.DisplayName,
316+
&source.GoogleUserID, &source.LastSyncAt, &source.SyncCursor, &createdAt, &updatedAt,
317+
)
318+
if err != nil {
319+
return nil, err
320+
}
321+
322+
if createdAt.Valid {
323+
source.CreatedAt = createdAt.Time
324+
}
325+
if updatedAt.Valid {
326+
source.UpdatedAt = updatedAt.Time
327+
}
328+
329+
sources = append(sources, &source)
330+
}
331+
332+
return sources, rows.Err()
333+
}

0 commit comments

Comments
 (0)