gogcli/internal/googleauth/service.go
salmonumbrella d2be673d10
feat: add retry logic, concurrent fetching, and extended commands
Resilience:
- RetryTransport with circuit breaker for 429/5xx resilience
- Exponential backoff with jitter, respects Retry-After headers
- Circuit breaker auto-resets after 30s of successful requests

Performance:
- Concurrent gmail thread fetching (fixes N+1 query pattern)
- Bounded concurrency with semaphore (max 10 parallel)

New calendar commands:
- colors: list available event/calendar colors
- conflicts: check availability across calendars
- search: find events by text query
- time: show current time in multiple timezones

New gmail commands:
- autoforward: get/enable/disable auto-forwarding
- delegates: list/add/remove mail delegation
- filters: list/create/delete inbox filters
- forwarding: manage forwarding addresses
- sendas: manage send-as aliases
- vacation: get/enable/disable vacation responder
- batch: bulk operations (mark-read, archive, label, delete)
- watch: Pub/Sub push with webhook forwarding

New services:
- Sheets: read/write/append spreadsheet data
- Tasks: manage tasklists and tasks

Developer experience:
- Shell completion (bash, zsh, fish, powershell)
- version command with build info
- --debug flag for verbose logging
- lefthook for pre-commit hooks

Documentation:
- Expanded README with examples
- Gmail watch/Pub/Sub guide (docs/watch.md)
- Architecture spec (docs/spec.md)
- Release process (docs/RELEASING.md)
2025-12-24 17:51:21 -08:00

80 lines
2.2 KiB
Go

package googleauth
import (
"errors"
"fmt"
"sort"
"strings"
)
type Service string
const (
ServiceGmail Service = "gmail"
ServiceCalendar Service = "calendar"
ServiceDrive Service = "drive"
ServiceContacts Service = "contacts"
ServiceTasks Service = "tasks"
ServicePeople Service = "people"
ServiceSheets Service = "sheets"
)
func ParseService(s string) (Service, error) {
switch Service(strings.ToLower(strings.TrimSpace(s))) {
case ServiceGmail, ServiceCalendar, ServiceDrive, ServiceContacts, ServiceTasks, ServicePeople, ServiceSheets:
return Service(strings.ToLower(strings.TrimSpace(s))), nil
default:
return "", fmt.Errorf("unknown service %q (expected gmail|calendar|drive|contacts|tasks|people|sheets)", s)
}
}
func AllServices() []Service {
return []Service{ServiceGmail, ServiceCalendar, ServiceDrive, ServiceContacts, ServiceTasks, ServicePeople, ServiceSheets}
}
func Scopes(service Service) ([]string, error) {
switch service {
case ServiceGmail:
return []string{"https://mail.google.com/"}, nil
case ServiceCalendar:
return []string{"https://www.googleapis.com/auth/calendar"}, nil
case ServiceDrive:
return []string{"https://www.googleapis.com/auth/drive"}, nil
case ServiceContacts:
return []string{
"https://www.googleapis.com/auth/contacts",
"https://www.googleapis.com/auth/contacts.other.readonly",
"https://www.googleapis.com/auth/directory.readonly",
}, nil
case ServiceTasks:
return []string{"https://www.googleapis.com/auth/tasks"}, nil
case ServicePeople:
// Needed for "people/me" requests.
return []string{"profile"}, nil
case ServiceSheets:
return []string{"https://www.googleapis.com/auth/spreadsheets"}, nil
default:
return nil, errors.New("unknown service")
}
}
func ScopesForServices(services []Service) ([]string, error) {
set := make(map[string]struct{})
for _, svc := range services {
scopes, err := Scopes(svc)
if err != nil {
return nil, err
}
for _, s := range scopes {
set[s] = struct{}{}
}
}
out := make([]string, 0, len(set))
for s := range set {
out = append(out, s)
}
// stable ordering (useful for tests + auth URL diffs)
sort.Strings(out)
return out, nil
}