omitempty for sync response, sync handling for items, most state handling, working

This commit is contained in:
2024-09-24 17:56:13 -05:00
parent d3e5a9fbb1
commit cae3656551

View File

@@ -1,47 +1,53 @@
package api package api
import "encoding/json" import (
"encoding/json"
"fmt"
"io"
"time"
)
type SyncResponse struct { type SyncResponse struct {
// The new token to use for the next incremental sync. // The new token to use for the next incremental sync.
SyncToken string `json:"sync_token"` SyncToken string `json:"sync_token,omitempty"`
// If true, this response is a full sync and the client should not merge but instead replace it's state. // If true, this response is a full sync and the client should not merge but instead replace its state.
FullSync bool `json:"full_sync"` FullSync bool `json:"full_sync,omitempty"`
// Used for commands where local IDs are temporarily chosen by the client and need to be mapped to the server's IDs. // Used for commands where local IDs are temporarily chosen by the client and need to be mapped to the server's IDs.
TempIDMapping map[string]interface{} `json:"temp_id_mapping"` TempIDMapping map[string]interface{} `json:"temp_id_mapping,omitempty"`
Items []interface{} `json:"items"` Items []Item `json:"items,omitempty"`
// CompletedInfo []interface{} `json:"completed_info"` // CompletedInfo []interface{} `json:"completed_info,omitempty"`
// Collaborators []interface{} `json:"collaborators"` // Collaborators []interface{} `json:"collaborators,omitempty"`
// CollaboratorStates []interface{} `json:"collaborator_states"` // CollaboratorStates []interface{} `json:"collaborator_states,omitempty"`
// DayOrders map[string]interface{} `json:"day_orders"` // DayOrders map[string]interface{} `json:"day_orders,omitempty"`
// Filters []interface{} `json:"filters"` // Filters []interface{} `json:"filters,omitempty"`
// Labels []interface{} `json:"labels"` // Labels []interface{} `json:"labels,omitempty"`
// LiveNotifications []interface{} `json:"live_notifications"` // LiveNotifications []interface{} `json:"live_notifications,omitempty"`
// LiveNotificationsLastReadID string `json:"live_notifications_last_read_id"` // LiveNotificationsLastReadID string `json:"live_notifications_last_read_id,omitempty"`
// Locations []interface{} `json:"locations"` // Locations []interface{} `json:"locations,omitempty"`
// Notes []interface{} `json:"notes"` // Notes []interface{} `json:"notes,omitempty"`
// ProjectNotes []interface{} `json:"project_notes"` // ProjectNotes []interface{} `json:"project_notes,omitempty"`
// Projects []interface{} `json:"projects"` // Projects []interface{} `json:"projects,omitempty"`
// Reminders []interface{} `json:"reminders"` // Reminders []interface{} `json:"reminders,omitempty"`
// Sections []interface{} `json:"sections"` // Sections []interface{} `json:"sections,omitempty"`
// Stats map[string]interface{} `json:"stats"` // Stats map[string]interface{} `json:"stats,omitempty"`
// SettingsNotifications map[string]interface{} `json:"settings_notifications"` // SettingsNotifications map[string]interface{} `json:"settings_notifications,omitempty"`
// User map[string]interface{} `json:"user"` // User map[string]interface{} `json:"user,omitempty"`
// UserPlanLimits map[string]interface{} `json:"user_plan_limits"` // UserPlanLimits map[string]interface{} `json:"user_plan_limits,omitempty"`
// UserSettings map[string]interface{} `json:"user_settings"` // UserSettings map[string]interface{} `json:"user_settings,omitempty"`
} }
type State struct { type State struct {
Items map[string]Item Items map[string]Item
} }
type Changes struct { // NewState creates a new blank state with initialized fields.
Added []string func NewState() *State {
Updated []string return &State{
Deleted []string Items: make(map[string]Item),
}
} }
// sync synchronizes the client's state with the server. If the full parameter is set to true, // Synchronize 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. // a full synchronization is performed, otherwise, a partial synchronization is done.
// This strongly mutates the client's state. // This strongly mutates the client's state.
// //
@@ -54,54 +60,72 @@ type Changes struct {
// int - the number of changes synchronized. // int - the number of changes synchronized.
// *Changes - a pointer to a Changes struct containing the details of the changes. // *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. // error - an error object if an error occurred during synchronization, otherwise nil.
func (sc *SyncClient) Synchronize(full bool) (*Changes, error) { func (sc *SyncClient) Synchronize(full bool) (*[]byte, error) {
if sc.RequireFullSync { if sc.requireFullSync || sc.lastFullSync.IsZero() {
fmt.Printf("Performing full sync\n")
sc.syncToken = "*" sc.syncToken = "*"
} }
resourceTypes := make([]string, 4)
for resourceType := range sc.resourceTypes {
resourceTypes = append(resourceTypes, string(resourceType))
}
body := map[string]interface{}{ body := map[string]interface{}{
"sync_token": sc.syncToken, "sync_token": sc.syncToken,
"resource_types": resourceTypes,
} }
jsonBody, err := json.Marshal(body) jsonBody, err := json.Marshal(body)
if err != nil { if err != nil {
return 0, nil, err return nil, fmt.Errorf("failed to marshal request body: %w", err)
} }
res, err := sc.post("/sync", nil, jsonBody) res, err := sc.post("/sync", nil, jsonBody)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to post sync request: %w", err)
} }
now := time.Now()
defer res.Body.Close() defer res.Body.Close()
var syncResponse SyncResponse bodyBytes, err := io.ReadAll(res.Body)
if err := json.NewDecoder(res.Body).Decode(&syncResponse); err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to read response body: %w", err)
} }
var syncResponse SyncResponse
if err := json.Unmarshal(bodyBytes, &syncResponse); err != nil {
return nil, fmt.Errorf("failed to decode sync response: %w", err)
}
// Always set in every request
sc.syncToken = syncResponse.SyncToken sc.syncToken = syncResponse.SyncToken
sc.lastSync = now
// changes := &Changes{ // Simple, replace the state with the new items
// Added: []string{}, if syncResponse.FullSync {
// Updated: []string{}, sc.lastFullSync = now
// Deleted: []string{}, sc.requireFullSync = false
// }
// // Process the items in syncResponse.Items to populate changes if syncResponse.Items != nil {
// for _, item := range syncResponse.Items { sc.State.Items = make(map[string]Item)
// // Assuming item is a map[string]interface{} and has a "status" field for _, item := range syncResponse.Items {
// itemMap := item.(map[string]interface{}) sc.State.Items[item.ID] = item
// if status, ok := itemMap["status"].(string); ok { }
// switch status { }
// case "added": } else {
// changes.Added = append(changes.Added, itemMap["id"].(string)) // Partial sync
// case "updated": if syncResponse.Items != nil {
// changes.Updated = append(changes.Updated, itemMap["id"].(string)) for _, item := range syncResponse.Items {
// case "deleted": sc.State.Items[item.ID] = item
// changes.Deleted = append(changes.Deleted, itemMap["id"].(string))
// }
// }
// }
return changes, nil if item.IsDeleted {
delete(sc.State.Items, item.ID)
}
}
}
}
return nil, nil
// return changes, nil
} }