Sync bug fixes across the board!
All checks were successful
Ascently - Sync Deploy / build-and-push (push) Successful in 2m15s

This commit is contained in:
2026-01-09 22:48:20 -07:00
parent d002c703d5
commit f4f4968431
18 changed files with 703 additions and 1269 deletions

View File

@@ -11,16 +11,11 @@ import (
"path/filepath"
"strings"
"time"
"github.com/joho/godotenv"
)
const VERSION = "2.4.0"
func min(a, b int) int {
if a < b {
return a
}
return b
}
const VERSION = "2.5.0"
type ClimbDataBackup struct {
ExportedAt string `json:"exportedAt"`
@@ -41,11 +36,12 @@ type DeltaSyncRequest struct {
}
type DeltaSyncResponse struct {
ServerTime string `json:"serverTime"`
Gyms []BackupGym `json:"gyms"`
Problems []BackupProblem `json:"problems"`
Sessions []BackupClimbSession `json:"sessions"`
Attempts []BackupAttempt `json:"attempts"`
ServerTime string `json:"serverTime"`
RequestFullSync bool `json:"requestFullSync,omitempty"`
Gyms []BackupGym `json:"gyms"`
Problems []BackupProblem `json:"problems"`
Sessions []BackupClimbSession `json:"sessions"`
Attempts []BackupAttempt `json:"attempts"`
}
type BackupGym struct {
@@ -282,6 +278,81 @@ func (s *SyncServer) mergeAttempts(existing []BackupAttempt, updates []BackupAtt
return result
}
func (s *SyncServer) cleanupTombstones(backup *ClimbDataBackup) {
cutoffTime := time.Now().UTC().Add(-90 * 24 * time.Hour)
log.Printf("Cleaning up tombstones older than %s", cutoffTime.Format(time.RFC3339))
// Gyms
activeGyms := make([]BackupGym, 0, len(backup.Gyms))
for _, item := range backup.Gyms {
if !item.IsDeleted {
activeGyms = append(activeGyms, item)
continue
}
updatedAt, err := time.Parse(time.RFC3339, item.UpdatedAt)
if err == nil && updatedAt.After(cutoffTime) {
activeGyms = append(activeGyms, item)
} else {
log.Printf("Pruning deleted gym: %s", item.ID)
}
}
backup.Gyms = activeGyms
// Problems
activeProblems := make([]BackupProblem, 0, len(backup.Problems))
for _, item := range backup.Problems {
if !item.IsDeleted {
activeProblems = append(activeProblems, item)
continue
}
updatedAt, err := time.Parse(time.RFC3339, item.UpdatedAt)
if err == nil && updatedAt.After(cutoffTime) {
activeProblems = append(activeProblems, item)
} else {
log.Printf("Pruning deleted problem: %s", item.ID)
}
}
backup.Problems = activeProblems
// Sessions
activeSessions := make([]BackupClimbSession, 0, len(backup.Sessions))
for _, item := range backup.Sessions {
if !item.IsDeleted {
activeSessions = append(activeSessions, item)
continue
}
updatedAt, err := time.Parse(time.RFC3339, item.UpdatedAt)
if err == nil && updatedAt.After(cutoffTime) {
activeSessions = append(activeSessions, item)
} else {
log.Printf("Pruning deleted session: %s", item.ID)
}
}
backup.Sessions = activeSessions
// Attempts
activeAttempts := make([]BackupAttempt, 0, len(backup.Attempts))
for _, item := range backup.Attempts {
if !item.IsDeleted {
activeAttempts = append(activeAttempts, item)
continue
}
timeStr := item.CreatedAt
if item.UpdatedAt != nil {
timeStr = *item.UpdatedAt
}
updatedAt, err := time.Parse(time.RFC3339, timeStr)
if err == nil && updatedAt.After(cutoffTime) {
activeAttempts = append(activeAttempts, item)
} else {
log.Printf("Pruning deleted attempt: %s", item.ID)
}
}
backup.Attempts = activeAttempts
}
func (s *SyncServer) saveData(backup *ClimbDataBackup) error {
backup.ExportedAt = time.Now().UTC().Format(time.RFC3339)
@@ -339,6 +410,8 @@ func (s *SyncServer) handlePut(w http.ResponseWriter, r *http.Request) {
return
}
s.cleanupTombstones(&backup)
if err := s.saveData(&backup); err != nil {
log.Printf("Failed to save data: %v", err)
http.Error(w, "Failed to save data", http.StatusInternalServerError)
@@ -476,14 +549,33 @@ func (s *SyncServer) handleDeltaSync(w http.ResponseWriter, r *http.Request) {
return
}
clientLastSyncCheck, err := time.Parse(time.RFC3339, deltaRequest.LastSyncTime)
isServerEmpty := len(serverBackup.Gyms) == 0 && len(serverBackup.Problems) == 0 &&
len(serverBackup.Sessions) == 0 && len(serverBackup.Attempts) == 0
if err == nil && !clientLastSyncCheck.IsZero() && isServerEmpty {
log.Printf("Server is empty but client has sync history. Requesting full sync.")
response := DeltaSyncResponse{
ServerTime: time.Now().UTC().Format(time.RFC3339),
RequestFullSync: true,
Gyms: []BackupGym{},
Problems: []BackupProblem{},
Sessions: []BackupClimbSession{},
Attempts: []BackupAttempt{},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
return
}
// Merge client changes into server data
// Note: We no longer need separate deletion handling as IsDeleted is part of the struct
// and handled by standard merge logic (latest timestamp wins)
serverBackup.Gyms = s.mergeGyms(serverBackup.Gyms, deltaRequest.Gyms)
serverBackup.Problems = s.mergeProblems(serverBackup.Problems, deltaRequest.Problems)
serverBackup.Sessions = s.mergeSessions(serverBackup.Sessions, deltaRequest.Sessions)
serverBackup.Attempts = s.mergeAttempts(serverBackup.Attempts, deltaRequest.Attempts)
s.cleanupTombstones(serverBackup)
// Save merged data
if err := s.saveData(serverBackup); err != nil {
log.Printf("Failed to save data: %v", err)
@@ -565,7 +657,9 @@ func (s *SyncServer) handleSync(w http.ResponseWriter, r *http.Request) {
}
func main() {
godotenv.Load()
authToken := os.Getenv("AUTH_TOKEN")
print(authToken)
if authToken == "" {
log.Fatal("AUTH_TOKEN environment variable is required")
}