sync_handler.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package handlers
  2. import (
  3. "errors"
  4. "net/http"
  5. "time"
  6. "github.com/celestia-trace/backend/models"
  7. "github.com/gin-gonic/gin"
  8. "gorm.io/gorm"
  9. )
  10. type SyncHandler struct {
  11. DB *gorm.DB
  12. }
  13. func defaultSyncSetting(userID string) models.SyncSetting {
  14. return models.SyncSetting{
  15. UserID: userID,
  16. AutoSyncWiFi: true,
  17. SyncPhotos: true,
  18. SyncAudio: true,
  19. AudioQuality: "HD",
  20. CloudStorageMaxMB: 5120,
  21. CloudStorageUsedMB: 0,
  22. }
  23. }
  24. // GetSyncSettings returns the user sync settings and storage status
  25. func (h *SyncHandler) GetSyncSettings(c *gin.Context) {
  26. userID := c.GetString("userID")
  27. var setting models.SyncSetting
  28. err := h.DB.First(&setting, "user_id = ?", userID).Error
  29. if errors.Is(err, gorm.ErrRecordNotFound) {
  30. setting = defaultSyncSetting(userID)
  31. h.DB.Create(&setting)
  32. }
  33. var totalBytes int64
  34. h.DB.Model(&models.MediaAsset{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size_bytes), 0)").Scan(&totalBytes)
  35. setting.CloudStorageUsedMB = (totalBytes + 1024*1024 - 1) / (1024 * 1024)
  36. respondSuccess(c, setting)
  37. }
  38. // UpdateSyncSettings updates sync settings
  39. func (h *SyncHandler) UpdateSyncSettings(c *gin.Context) {
  40. userID := c.GetString("userID")
  41. var req models.UpdateSyncSettingRequest
  42. if err := c.ShouldBindJSON(&req); err != nil {
  43. respondError(c, http.StatusBadRequest, 400, "invalid parameters")
  44. return
  45. }
  46. var setting models.SyncSetting
  47. if err := h.DB.First(&setting, "user_id = ?", userID).Error; err != nil {
  48. setting = defaultSyncSetting(userID)
  49. }
  50. if req.AutoSyncWiFi != nil {
  51. setting.AutoSyncWiFi = *req.AutoSyncWiFi
  52. }
  53. if req.SyncPhotos != nil {
  54. setting.SyncPhotos = *req.SyncPhotos
  55. }
  56. if req.SyncAudio != nil {
  57. setting.SyncAudio = *req.SyncAudio
  58. }
  59. if req.AudioQuality != nil {
  60. setting.AudioQuality = *req.AudioQuality
  61. }
  62. now := time.Now()
  63. setting.LastSyncedAt = &now
  64. if err := h.DB.Save(&setting).Error; err != nil {
  65. respondError(c, http.StatusInternalServerError, 500, "failed to update sync settings")
  66. return
  67. }
  68. respondSuccess(c, setting)
  69. }
  70. // GetSyncSummary returns aggregated stats (devices, sessions, total duration, storage)
  71. func (h *SyncHandler) GetSyncSummary(c *gin.Context) {
  72. userID := c.GetString("userID")
  73. var deviceCount int64
  74. h.DB.Model(&models.BoundDevice{}).Where("user_id = ?", userID).Count(&deviceCount)
  75. var sessionCount int64
  76. h.DB.Model(&models.CelestiaSession{}).Where("user_id = ? AND deleted_at IS NULL", userID).Count(&sessionCount)
  77. var totalDurationMs int64
  78. h.DB.Model(&models.CelestiaSession{}).Where("user_id = ? AND deleted_at IS NULL", userID).Select("COALESCE(SUM(duration_ms), 0)").Scan(&totalDurationMs)
  79. var setting models.SyncSetting
  80. if err := h.DB.First(&setting, "user_id = ?", userID).Error; err != nil {
  81. setting = defaultSyncSetting(userID)
  82. h.DB.Create(&setting)
  83. }
  84. var totalBytes int64
  85. h.DB.Model(&models.MediaAsset{}).Where("user_id = ?", userID).Select("COALESCE(SUM(size_bytes), 0)").Scan(&totalBytes)
  86. setting.CloudStorageUsedMB = (totalBytes + 1024*1024 - 1) / (1024 * 1024)
  87. summary := models.SyncSummary{
  88. DeviceCount: deviceCount,
  89. SessionCount: sessionCount,
  90. TotalDurationMs: totalDurationMs,
  91. Storage: setting,
  92. }
  93. respondSuccess(c, summary)
  94. }
  95. // TriggerManualSync records a completed client checkpoint. It does not claim
  96. // that local files were uploaded; the client only calls this after all uploads.
  97. func (h *SyncHandler) TriggerManualSync(c *gin.Context) {
  98. userID := c.GetString("userID")
  99. now := time.Now()
  100. var setting models.SyncSetting
  101. if err := h.DB.First(&setting, "user_id = ?", userID).Error; err == nil {
  102. setting.LastSyncedAt = &now
  103. h.DB.Save(&setting)
  104. }
  105. respondSuccess(c, gin.H{
  106. "syncedAt": now,
  107. "message": "Client sync checkpoint recorded",
  108. })
  109. }
  110. // GetStorageQuota returns the user's storage quota and current usage in bytes.
  111. // The iOS client calls GET /storage/quota before uploading large files.
  112. func (h *SyncHandler) GetStorageQuota(c *gin.Context) {
  113. userID := c.GetString("userID")
  114. var setting models.SyncSetting
  115. if err := h.DB.First(&setting, "user_id = ?", userID).Error; err != nil {
  116. setting = defaultSyncSetting(userID)
  117. h.DB.Create(&setting)
  118. }
  119. var usedBytes int64
  120. h.DB.Model(&models.MediaAsset{}).Where("user_id = ?", userID).
  121. Select("COALESCE(SUM(size_bytes), 0)").Scan(&usedBytes)
  122. totalBytes := setting.CloudStorageMaxMB * 1024 * 1024
  123. remainingBytes := totalBytes - usedBytes
  124. if remainingBytes < 0 {
  125. remainingBytes = 0
  126. }
  127. respondSuccess(c, gin.H{
  128. "totalBytes": totalBytes,
  129. "usedBytes": usedBytes,
  130. "remainingBytes": remainingBytes,
  131. })
  132. }