package handlers import ( "errors" "net/http" "time" "github.com/celestia-trace/backend/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type SyncHandler struct { DB *gorm.DB } func defaultSyncSetting(userID string) models.SyncSetting { return models.SyncSetting{ UserID: userID, AutoSyncWiFi: true, SyncPhotos: true, SyncAudio: true, AudioQuality: "HD", CloudStorageMaxMB: 5120, CloudStorageUsedMB: 0, } } // GetSyncSettings returns the user sync settings and storage status func (h *SyncHandler) GetSyncSettings(c *gin.Context) { userID := c.GetString("userID") var setting models.SyncSetting err := h.DB.First(&setting, "user_id = ?", userID).Error if errors.Is(err, gorm.ErrRecordNotFound) { setting = defaultSyncSetting(userID) h.DB.Create(&setting) } var totalBytes int64 h.DB.Model(&models.MediaAsset{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size_bytes), 0)").Scan(&totalBytes) setting.CloudStorageUsedMB = (totalBytes + 1024*1024 - 1) / (1024 * 1024) respondSuccess(c, setting) } // UpdateSyncSettings updates sync settings func (h *SyncHandler) UpdateSyncSettings(c *gin.Context) { userID := c.GetString("userID") var req models.UpdateSyncSettingRequest if err := c.ShouldBindJSON(&req); err != nil { respondError(c, http.StatusBadRequest, 400, "invalid parameters") return } var setting models.SyncSetting if err := h.DB.First(&setting, "user_id = ?", userID).Error; err != nil { setting = defaultSyncSetting(userID) } if req.AutoSyncWiFi != nil { setting.AutoSyncWiFi = *req.AutoSyncWiFi } if req.SyncPhotos != nil { setting.SyncPhotos = *req.SyncPhotos } if req.SyncAudio != nil { setting.SyncAudio = *req.SyncAudio } if req.AudioQuality != nil { setting.AudioQuality = *req.AudioQuality } now := time.Now() setting.LastSyncedAt = &now if err := h.DB.Save(&setting).Error; err != nil { respondError(c, http.StatusInternalServerError, 500, "failed to update sync settings") return } respondSuccess(c, setting) } // GetSyncSummary returns aggregated stats (devices, sessions, total duration, storage) func (h *SyncHandler) GetSyncSummary(c *gin.Context) { userID := c.GetString("userID") var deviceCount int64 h.DB.Model(&models.BoundDevice{}).Where("user_id = ?", userID).Count(&deviceCount) var sessionCount int64 h.DB.Model(&models.CelestiaSession{}).Where("user_id = ? AND deleted_at IS NULL", userID).Count(&sessionCount) var totalDurationMs int64 h.DB.Model(&models.CelestiaSession{}).Where("user_id = ? AND deleted_at IS NULL", userID).Select("COALESCE(SUM(duration_ms), 0)").Scan(&totalDurationMs) var setting models.SyncSetting if err := h.DB.First(&setting, "user_id = ?", userID).Error; err != nil { setting = defaultSyncSetting(userID) h.DB.Create(&setting) } var totalBytes int64 h.DB.Model(&models.MediaAsset{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size_bytes), 0)").Scan(&totalBytes) setting.CloudStorageUsedMB = (totalBytes + 1024*1024 - 1) / (1024 * 1024) summary := models.SyncSummary{ DeviceCount: deviceCount, SessionCount: sessionCount, TotalDurationMs: totalDurationMs, Storage: setting, } respondSuccess(c, summary) } // TriggerManualSync records a completed client checkpoint. It does not claim // that local files were uploaded; the client only calls this after all uploads. func (h *SyncHandler) TriggerManualSync(c *gin.Context) { userID := c.GetString("userID") now := time.Now() var setting models.SyncSetting if err := h.DB.First(&setting, "user_id = ?", userID).Error; err == nil { setting.LastSyncedAt = &now h.DB.Save(&setting) } respondSuccess(c, gin.H{ "syncedAt": now, "message": "Client sync checkpoint recorded", }) } // GetStorageQuota returns the user's storage quota and current usage in bytes. // The iOS client calls GET /storage/quota before uploading large files. func (h *SyncHandler) GetStorageQuota(c *gin.Context) { userID := c.GetString("userID") var setting models.SyncSetting if err := h.DB.First(&setting, "user_id = ?", userID).Error; err != nil { setting = defaultSyncSetting(userID) h.DB.Create(&setting) } var usedBytes int64 h.DB.Model(&models.MediaAsset{}).Where("user_id = ?", userID). Select("COALESCE(SUM(size_bytes), 0)").Scan(&usedBytes) totalBytes := setting.CloudStorageMaxMB * 1024 * 1024 remainingBytes := totalBytes - usedBytes if remainingBytes < 0 { remainingBytes = 0 } respondSuccess(c, gin.H{ "totalBytes": totalBytes, "usedBytes": usedBytes, "remainingBytes": remainingBytes, }) }