package treepath import ( "database/sql" "github.com/google/uuid" "git.gobha.me/xcaliber/chat-switchboard/database" ) // UpdateCursor upserts the channel_cursors row for a user. func UpdateCursor(channelID, userID, messageID string) error { if database.IsSQLite() { // SQLite: id TEXT PRIMARY KEY has no default generator; supply one. // Use excluded.active_leaf_id so ON CONFLICT works cleanly. _, err := database.DB.Exec(` INSERT INTO channel_cursors (id, channel_id, user_id, active_leaf_id) VALUES (?, ?, ?, ?) ON CONFLICT (channel_id, user_id) DO UPDATE SET active_leaf_id = excluded.active_leaf_id, updated_at = datetime('now') `, uuid.New().String(), channelID, userID, messageID) return err } // Postgres: id has DEFAULT gen_random_uuid(); $3 is referenced twice. _, err := database.DB.Exec(` INSERT INTO channel_cursors (channel_id, user_id, active_leaf_id) VALUES ($1, $2, $3) ON CONFLICT (channel_id, user_id) DO UPDATE SET active_leaf_id = $3, updated_at = NOW() `, channelID, userID, messageID) return err } // NextSiblingIndex returns the next sibling_index for a new child of parentID. // For root messages (parentID is nil), counts existing roots in the channel. func NextSiblingIndex(channelID string, parentID *string) int { var maxIdx sql.NullInt64 if parentID == nil { database.DB.QueryRow(database.Q(` SELECT MAX(sibling_index) FROM messages WHERE channel_id = $1 AND parent_id IS NULL AND deleted_at IS NULL `), channelID).Scan(&maxIdx) } else { database.DB.QueryRow(database.Q(` SELECT MAX(sibling_index) FROM messages WHERE parent_id = $1 AND deleted_at IS NULL `), *parentID).Scan(&maxIdx) } if !maxIdx.Valid { return 0 } return int(maxIdx.Int64) + 1 }