From 5a9995a72441dc7593f995dd4b1af81bb27edebb Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 24 Sep 2024 16:43:53 -0500 Subject: [PATCH] new api organization --- internal/api/activity.go | 54 +++++++++++++++++++++++++++ internal/api/commands.go | 45 ---------------------- internal/api/helpers.go | 8 ++++ internal/api/resource.go | 23 ++++++++++++ internal/api/sync.go | 27 ++++++++++++++ internal/api/types.go | 81 +++++++++++++++++++++++++++++++--------- 6 files changed, 175 insertions(+), 63 deletions(-) create mode 100644 internal/api/activity.go delete mode 100644 internal/api/commands.go create mode 100644 internal/api/helpers.go create mode 100644 internal/api/resource.go diff --git a/internal/api/activity.go b/internal/api/activity.go new file mode 100644 index 0000000..2ea1953 --- /dev/null +++ b/internal/api/activity.go @@ -0,0 +1,54 @@ +package api + +import ( + "encoding/json" + "io" + "net/http" + "net/url" + "time" +) + +type ActivityLog struct { + Count int `json:"count"` + Events []Event `json:"events"` +} + +type Event struct { + EventDate time.Time `json:"event_date"` + EventType string `json:"event_type"` + ExtraData map[string]any `json:"extra_data"` + ExtraDataID int64 `json:"extra_data_id"` + ID int64 `json:"id"` + + InitiatorID *int64 `json:"initiator_id"` + ObjectID string `json:"object_id"` + ObjectType string `json:"object_type"` +} + +func (sc *SyncClient) RecentlyCompleted() (*ActivityLog, error) { + params := url.Values{"event_type": {"completed"}} + req, err := http.NewRequest("GET", API_BASE_URL+"/activity/get?"+params.Encode(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+sc.ApiToken) + + resp, err := sc.Http.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var activityLog *ActivityLog + if err := json.Unmarshal(body, &activityLog); err != nil { + return nil, err + } + + return activityLog, nil +} diff --git a/internal/api/commands.go b/internal/api/commands.go deleted file mode 100644 index d129c27..0000000 --- a/internal/api/commands.go +++ /dev/null @@ -1,45 +0,0 @@ -package api - -import ( - "encoding/json" - "io" - "net/http" - "net/url" -) - -func (sc *SyncClient) RecentlyCompleted() (*ActivityLog, error) { - baseURL := API_BASE_URL + "/activity/get" - params := url.Values{} - params.Add("event_type", "completed") - - fullURL := baseURL + "?" + params.Encode() - - req, err := http.NewRequest("GET", fullURL, nil) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", "Bearer "+sc.ApiToken) - - resp, err := sc.Http.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var activityLog *ActivityLog - if err := json.Unmarshal(body, &activityLog); err != nil { - return nil, err - } - - return activityLog, nil -} - -func (sc *SyncClient) sync() { - // Implementation for synchronize function -} diff --git a/internal/api/helpers.go b/internal/api/helpers.go new file mode 100644 index 0000000..beb5adb --- /dev/null +++ b/internal/api/helpers.go @@ -0,0 +1,8 @@ +package api + +import "net/http" + +// applyAuthorization sets the Authorization header with the API token. +func (sc *SyncClient) applyAuthorization(req *http.Request) { + req.Header.Set("Authorization", "Bearer "+sc.ApiToken) +} diff --git a/internal/api/resource.go b/internal/api/resource.go new file mode 100644 index 0000000..9973ced --- /dev/null +++ b/internal/api/resource.go @@ -0,0 +1,23 @@ +package api + +type ResourceType string + +const ( + Labels ResourceType = "labels" + Projects ResourceType = "projects" + Items ResourceType = "items" + Notes ResourceType = "notes" + Sections ResourceType = "sections" + Filters ResourceType = "filters" + Reminders ResourceType = "reminders" + RemindersLocation ResourceType = "reminders_location" + Locations ResourceType = "locations" + User ResourceType = "user" + LiveNotifications ResourceType = "live_notifications" + Collaborators ResourceType = "collaborators" + UserSettings ResourceType = "user_settings" + NotificationSettings ResourceType = "notification_settings" + UserPlanLimits ResourceType = "user_plan_limits" + CompletedInfo ResourceType = "completed_info" + Stats ResourceType = "stats" +) diff --git a/internal/api/sync.go b/internal/api/sync.go index 778f64e..8edddaf 100644 --- a/internal/api/sync.go +++ b/internal/api/sync.go @@ -1 +1,28 @@ package api + +type ClientState struct { + // Items map[string]Item +} + +type Changes struct { + Added []string + Updated []string + Deleted []string +} + +// sync synchronizes the client's state with the server. If the full parameter is set to true, +// a full synchronization is performed, otherwise, a partial synchronization is done. +// This strongly mutates the client's state. +// +// Parameters: +// +// full - a boolean indicating whether to perform a full synchronization. +// +// Returns: +// +// int - the number of changes synchronized. +// *Changes - a pointer to a Changes struct containing the details of the changes. +// error - an error object if an error occurred during synchronization, otherwise nil. +func (sc *SyncClient) sync(full bool) (int, *Changes, error) { + return 0, nil, nil +} diff --git a/internal/api/types.go b/internal/api/types.go index 23c541b..8b9c909 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -1,16 +1,47 @@ package api import ( + "fmt" "net/http" + "net/url" + "runtime" + "runtime/debug" "time" ) +var ( + userAgent string +) + +func init() { + revision := "unknown" + version := "v0.0.0" + if info, ok := debug.ReadBuildInfo(); ok { + if info.Main.Version != "(devel)" { + version = info.Main.Version + revision = info.Main.Sum + } else { + fmt.Println("WARN : Inaccurate version information") + } + } + userAgent = fmt.Sprintf("todoist-late-reset/%v (%v; revision %v)", version, runtime.GOOS, revision) + fmt.Println(userAgent) +} + +// SyncClient represents a client for synchronizing data with the Todoist API. +// It holds the HTTP client, synchronization tokens, timestamps of the last syncs, +// and the types of resources to be synchronized. type SyncClient struct { - Http *http.Client - SyncToken string - ApiToken string - LastSync time.Time + Http *http.Client + SyncToken string + ApiToken string + // LastSync is the timestamp of the last synchronization, full or incremental. + LastSync time.Time + // LastFullSync is the timestamp of the last full synchronization. LastFullSync time.Time + // RequireFullSync indicates that client state has changed that a full sync is warranted. + RequireFullSync bool + ResourceTypes map[ResourceType]bool } func NewSyncClient(apiToken string) *SyncClient { @@ -21,22 +52,36 @@ func NewSyncClient(apiToken string) *SyncClient { } } -type SyncResponse struct { +// UseResources marks the resource types to be synchronized. +func (sc *SyncClient) UseResources(resourceTypes ...ResourceType) { + for _, resourceType := range resourceTypes { + if resourceType != Items { + // Log a warning or handle the case where the resource type is not implemented + fmt.Printf("WARN : Resource type %v not implemented\n", resourceType) + continue + } + + if sc.ResourceTypes[resourceType] == false { + sc.ResourceTypes[resourceType] = true + + // Incremental sync may not contain all necessary data, so require a full sync. + sc.RequireFullSync = true + } + } } -type ActivityLog struct { - Count int `json:"count"` - Events []Event `json:"events"` -} +// get performs a GET request to the Todoist API, building a request with the given path and parameters. +// It will also apply Authorization, Content-Type, Accept, and User-Agent headers. +func (sc *SyncClient) get(path string, params url.Values) (*http.Response, error) { + req, err := http.NewRequest("GET", API_BASE_URL+path+"?"+params.Encode(), nil) + if err != nil { + return nil, err + } -type Event struct { - EventDate time.Time `json:"event_date"` - EventType string `json:"event_type"` - ExtraData map[string]any `json:"extra_data"` - ExtraDataID int64 `json:"extra_data_id"` - ID int64 `json:"id"` + req.Header.Set("Authorization", "Bearer "+sc.ApiToken) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", userAgent) - InitiatorID *int64 `json:"initiator_id"` - ObjectID string `json:"object_id"` - ObjectType string `json:"object_type"` + return sc.Http.Do(req) }