- Update package.json name to "vessel" - Update storage keys (vessel-settings, vessel IndexedDB) - Update Go module to vessel-backend with new imports - Update database path to vessel.db - Add new Vessel "V" icon (favicon + app icons) - Update all user-facing branding (titles, sidebar, settings) - Update docker-compose files with vessel naming and network - Change accent color from emerald to violet 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
package api
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"vessel-backend/internal/models"
|
|
)
|
|
|
|
// PushChangesRequest represents the request body for pushing changes
|
|
type PushChangesRequest struct {
|
|
Chats []models.Chat `json:"chats"`
|
|
Messages []models.Message `json:"messages"`
|
|
}
|
|
|
|
// PushChangesHandler returns a handler for pushing changes from client
|
|
func PushChangesHandler(db *sql.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var req PushChangesRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to start transaction"})
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Process chats
|
|
for _, chat := range req.Chats {
|
|
// Check if chat exists
|
|
var existingVersion int64
|
|
err := tx.QueryRow("SELECT sync_version FROM chats WHERE id = ?", chat.ID).Scan(&existingVersion)
|
|
|
|
if err == sql.ErrNoRows {
|
|
// Insert new chat
|
|
_, err = tx.Exec(`
|
|
INSERT INTO chats (id, title, model, pinned, archived, created_at, updated_at, sync_version)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
chat.ID, chat.Title, chat.Model, chat.Pinned, chat.Archived,
|
|
chat.CreatedAt, chat.UpdatedAt, chat.SyncVersion,
|
|
)
|
|
} else if err == nil && chat.SyncVersion > existingVersion {
|
|
// Update existing chat if incoming version is higher
|
|
_, err = tx.Exec(`
|
|
UPDATE chats SET title = ?, model = ?, pinned = ?, archived = ?,
|
|
updated_at = ?, sync_version = ?
|
|
WHERE id = ?`,
|
|
chat.Title, chat.Model, chat.Pinned, chat.Archived,
|
|
chat.UpdatedAt, chat.SyncVersion, chat.ID,
|
|
)
|
|
}
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync chat: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Process messages
|
|
for _, msg := range req.Messages {
|
|
// Check if message exists
|
|
var existingVersion int64
|
|
err := tx.QueryRow("SELECT sync_version FROM messages WHERE id = ?", msg.ID).Scan(&existingVersion)
|
|
|
|
if err == sql.ErrNoRows {
|
|
// Insert new message
|
|
_, err = tx.Exec(`
|
|
INSERT INTO messages (id, chat_id, parent_id, role, content, sibling_index, created_at, sync_version)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
msg.ID, msg.ChatID, msg.ParentID, msg.Role, msg.Content,
|
|
msg.SiblingIndex, msg.CreatedAt, msg.SyncVersion,
|
|
)
|
|
} else if err == nil && msg.SyncVersion > existingVersion {
|
|
// Update existing message if incoming version is higher
|
|
_, err = tx.Exec(`
|
|
UPDATE messages SET content = ?, sibling_index = ?, sync_version = ?
|
|
WHERE id = ?`,
|
|
msg.Content, msg.SiblingIndex, msg.SyncVersion, msg.ID,
|
|
)
|
|
}
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync message: " + err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to commit transaction"})
|
|
return
|
|
}
|
|
|
|
// Get current max sync version
|
|
maxVersion, err := models.GetMaxSyncVersion(db)
|
|
if err != nil {
|
|
maxVersion = 0
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "changes pushed successfully",
|
|
"sync_version": maxVersion,
|
|
})
|
|
}
|
|
}
|
|
|
|
// PullChangesHandler returns a handler for pulling changes from server
|
|
func PullChangesHandler(db *sql.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
sinceVersionStr := c.Query("since_version")
|
|
var sinceVersion int64 = 0
|
|
|
|
if sinceVersionStr != "" {
|
|
var err error
|
|
sinceVersion, err = strconv.ParseInt(sinceVersionStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid since_version parameter"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Get changed chats
|
|
chats, err := models.GetChangedChats(db, sinceVersion)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if chats == nil {
|
|
chats = []models.Chat{}
|
|
}
|
|
|
|
// Get current max sync version
|
|
maxVersion, err := models.GetMaxSyncVersion(db)
|
|
if err != nil {
|
|
maxVersion = 0
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"chats": chats,
|
|
"sync_version": maxVersion,
|
|
})
|
|
}
|
|
}
|