Bladeren bron

feat(session): introduce lightweight SessionSyncIndexItem for incremental sync index

bob.yuxinyang 1 maand geleden
bovenliggende
commit
b78cc0f2fb
3 gewijzigde bestanden met toevoegingen van 83 en 7 verwijderingen
  1. 9 7
      handlers/session_handler.go
  2. 33 0
      models/session.go
  3. 41 0
      models/session_test.go

+ 9 - 7
handlers/session_handler.go

@@ -21,17 +21,15 @@ func sessionPreloads(db *gorm.DB) *gorm.DB {
 	}).Preload("Assets")
 }
 
-// GetSessions supports cursor-like incremental pulls with updatedAfter and can
-// include deletion tombstones for multi-device reconciliation.
+// GetSessions is the lightweight first phase of synchronization. It supports
+// cursor-like incremental pulls and deletion tombstones without preloading
+// concrete event or asset content.
 func (h *SessionHandler) GetSessions(c *gin.Context) {
 	userID := c.GetString("userID")
-	query := sessionPreloads(h.DB).Where("user_id = ?", userID)
+	query := h.DB.Where("user_id = ?", userID)
 	if c.Query("includeDeleted") != "true" {
 		query = query.Where("deleted_at IS NULL")
 	}
-	if search := c.Query("search"); search != "" {
-		query = query.Where("title ILIKE ?", "%"+search+"%")
-	}
 	if value := c.Query("updatedAfter"); value != "" {
 		updatedAfter, err := time.Parse(time.RFC3339Nano, value)
 		if err != nil {
@@ -45,7 +43,11 @@ func (h *SessionHandler) GetSessions(c *gin.Context) {
 		respondError(c, http.StatusInternalServerError, 500, "failed to query field records")
 		return
 	}
-	respondSuccess(c, sessions)
+	index := make([]models.SessionSyncIndexItem, 0, len(sessions))
+	for _, session := range sessions {
+		index = append(index, session.SyncIndexItem())
+	}
+	respondSuccess(c, index)
 }
 
 func (h *SessionHandler) GetSessionDetail(c *gin.Context) {

+ 33 - 0
models/session.go

@@ -25,6 +25,39 @@ type CelestiaSession struct {
 	UpdatedAt      time.Time       `gorm:"autoUpdateTime" json:"updatedAt"`
 }
 
+// SessionSyncIndexItem is the lightweight first phase of synchronization.
+// Events and asset descriptors are intentionally omitted; clients fetch the
+// full session only when UpdatedAt or Revision differs from local state.
+type SessionSyncIndexItem struct {
+	ID         string     `json:"id"`
+	ClientID   *string    `json:"clientId,omitempty"`
+	Title      string     `json:"title"`
+	StartTime  time.Time  `json:"startTime"`
+	EndTime    *time.Time `json:"endTime,omitempty"`
+	DurationMs int64      `json:"durationMs"`
+	PhotoCount int        `json:"photoCount"`
+	NoteCount  int        `json:"noteCount"`
+	Revision   int64      `json:"revision"`
+	DeletedAt  *time.Time `json:"deletedAt,omitempty"`
+	UpdatedAt  time.Time  `json:"updatedAt"`
+}
+
+func (session CelestiaSession) SyncIndexItem() SessionSyncIndexItem {
+	return SessionSyncIndexItem{
+		ID:         session.ID,
+		ClientID:   session.ClientID,
+		Title:      session.Title,
+		StartTime:  session.StartTime,
+		EndTime:    session.EndTime,
+		DurationMs: session.DurationMs,
+		PhotoCount: session.PhotoCount,
+		NoteCount:  session.NoteCount,
+		Revision:   session.Revision,
+		DeletedAt:  session.DeletedAt,
+		UpdatedAt:  session.UpdatedAt,
+	}
+}
+
 // TimelineEvent represents an event (NOTE, PHOTO, MARKER, VOICE) within a session
 type TimelineEvent struct {
 	ID                 string    `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`

+ 41 - 0
models/session_test.go

@@ -0,0 +1,41 @@
+package models
+
+import (
+	"encoding/json"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestSessionSyncIndexItemOmitsConcreteContent(t *testing.T) {
+	clientID := "client-1"
+	updatedAt := time.Date(2026, 7, 26, 12, 30, 0, 123000000, time.UTC)
+	session := CelestiaSession{
+		ID:         "session-1",
+		ClientID:   &clientID,
+		Title:      "现场记录",
+		StartTime:  updatedAt.Add(-time.Hour),
+		DurationMs: 3_600_000,
+		PhotoCount: 2,
+		NoteCount:  3,
+		Revision:   7,
+		UpdatedAt:  updatedAt,
+		Events:     []TimelineEvent{{ID: "event-1"}},
+		Assets:     []MediaAsset{{ID: "asset-1"}},
+	}
+
+	data, err := json.Marshal(session.SyncIndexItem())
+	if err != nil {
+		t.Fatalf("marshal sync index item: %v", err)
+	}
+	payload := string(data)
+	if strings.Contains(payload, `"events"`) || strings.Contains(payload, `"assets"`) {
+		t.Fatalf("sync index leaked concrete content: %s", payload)
+	}
+	if !strings.Contains(payload, `"updatedAt":"2026-07-26T12:30:00.123Z"`) {
+		t.Fatalf("sync index missing precise updatedAt: %s", payload)
+	}
+	if !strings.Contains(payload, `"revision":7`) {
+		t.Fatalf("sync index missing revision guard: %s", payload)
+	}
+}