Changeset 0.17.1 (#76)
This commit is contained in:
@@ -89,7 +89,7 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
|
||||
|
||||
var total int
|
||||
err := database.DB.QueryRow(
|
||||
`SELECT COUNT(*) FROM messages WHERE channel_id = $1 AND deleted_at IS NULL`,
|
||||
database.Q(`SELECT COUNT(*) FROM messages WHERE channel_id = $1 AND deleted_at IS NULL`),
|
||||
channelID,
|
||||
).Scan(&total)
|
||||
if err != nil {
|
||||
@@ -97,14 +97,14 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := database.DB.Query(`
|
||||
rows, err := database.DB.Query(database.Q(`
|
||||
SELECT id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
FROM messages
|
||||
WHERE channel_id = $1 AND deleted_at IS NULL
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $2 OFFSET $3
|
||||
`, channelID, perPage, offset)
|
||||
`), channelID, perPage, offset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list messages"})
|
||||
return
|
||||
@@ -123,9 +123,14 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan message"})
|
||||
return
|
||||
}
|
||||
msg.SiblingCount = getSiblingCount(channelID, msg.ParentID)
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
rows.Close() // release connection before sibling queries
|
||||
|
||||
// Enrich with sibling counts (requires DB access, must happen after rows are closed)
|
||||
for i := range messages {
|
||||
messages[i].SiblingCount = getSiblingCount(channelID, messages[i].ParentID)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, paginatedResponse{
|
||||
Data: messages,
|
||||
@@ -191,27 +196,53 @@ func (h *MessageHandler) CreateMessage(c *gin.Context) {
|
||||
}
|
||||
|
||||
var msg messageResponse
|
||||
err := database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, model, parent_id,
|
||||
participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
`, channelID, req.Role, req.Content, req.Model, parentID,
|
||||
participantType, participantID, siblingIdx,
|
||||
).Scan(
|
||||
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
|
||||
&msg.Model, &msg.TokensUsed, &msg.ParentID,
|
||||
&msg.SiblingIndex, &msg.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create message"})
|
||||
return
|
||||
if database.IsSQLite() {
|
||||
newID := store.NewID()
|
||||
_, err := database.DB.Exec(`
|
||||
INSERT INTO messages (id, channel_id, role, content, model, parent_id,
|
||||
participant_type, participant_id, sibling_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, newID, channelID, req.Role, req.Content, req.Model, parentID,
|
||||
participantType, participantID, siblingIdx)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create message"})
|
||||
return
|
||||
}
|
||||
err = database.DB.QueryRow(`
|
||||
SELECT id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
FROM messages WHERE id = ?`, newID).Scan(
|
||||
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
|
||||
&msg.Model, &msg.TokensUsed, &msg.ParentID,
|
||||
&msg.SiblingIndex, &msg.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read created message"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, model, parent_id,
|
||||
participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
`, channelID, req.Role, req.Content, req.Model, parentID,
|
||||
participantType, participantID, siblingIdx,
|
||||
).Scan(
|
||||
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
|
||||
&msg.Model, &msg.TokensUsed, &msg.ParentID,
|
||||
&msg.SiblingIndex, &msg.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create message"})
|
||||
return
|
||||
}
|
||||
}
|
||||
msg.SiblingCount = getSiblingCount(channelID, msg.ParentID)
|
||||
|
||||
_ = updateCursor(channelID, userID, msg.ID)
|
||||
_, _ = database.DB.Exec(`UPDATE channels SET updated_at = NOW() WHERE id = $1`, channelID)
|
||||
_, _ = database.DB.Exec(database.Q(`UPDATE channels SET updated_at = NOW() WHERE id = $1`), channelID)
|
||||
|
||||
c.JSON(http.StatusCreated, msg)
|
||||
}
|
||||
@@ -240,10 +271,10 @@ func (h *MessageHandler) EditMessage(c *gin.Context) {
|
||||
// Load target — must exist, belong to channel, be a user message
|
||||
var targetParentID *string
|
||||
var targetRole string
|
||||
err := database.DB.QueryRow(`
|
||||
err := database.DB.QueryRow(database.Q(`
|
||||
SELECT parent_id, role FROM messages
|
||||
WHERE id = $1 AND channel_id = $2 AND deleted_at IS NULL
|
||||
`, messageID, channelID).Scan(&targetParentID, &targetRole)
|
||||
`), messageID, channelID).Scan(&targetParentID, &targetRole)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "message not found"})
|
||||
@@ -262,18 +293,39 @@ func (h *MessageHandler) EditMessage(c *gin.Context) {
|
||||
siblingIdx := nextSiblingIndex(channelID, targetParentID)
|
||||
|
||||
var msg messageResponse
|
||||
err = database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, parent_id,
|
||||
participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, 'user', $2, $3, 'user', $4, $5)
|
||||
RETURNING id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
`, channelID, req.Content, targetParentID, userID, siblingIdx,
|
||||
).Scan(
|
||||
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
|
||||
&msg.Model, &msg.TokensUsed, &msg.ParentID,
|
||||
&msg.SiblingIndex, &msg.CreatedAt,
|
||||
)
|
||||
if database.IsSQLite() {
|
||||
newID := store.NewID()
|
||||
_, err = database.DB.Exec(`
|
||||
INSERT INTO messages (id, channel_id, role, content, parent_id,
|
||||
participant_type, participant_id, sibling_index)
|
||||
VALUES (?, ?, 'user', ?, ?, 'user', ?, ?)
|
||||
`, newID, channelID, req.Content, targetParentID, userID, siblingIdx)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create edit"})
|
||||
return
|
||||
}
|
||||
err = database.DB.QueryRow(`
|
||||
SELECT id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
FROM messages WHERE id = ?`, newID).Scan(
|
||||
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
|
||||
&msg.Model, &msg.TokensUsed, &msg.ParentID,
|
||||
&msg.SiblingIndex, &msg.CreatedAt,
|
||||
)
|
||||
} else {
|
||||
err = database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, parent_id,
|
||||
participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, 'user', $2, $3, 'user', $4, $5)
|
||||
RETURNING id, channel_id, role, content, model, tokens_used, parent_id,
|
||||
sibling_index, created_at
|
||||
`, channelID, req.Content, targetParentID, userID, siblingIdx,
|
||||
).Scan(
|
||||
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
|
||||
&msg.Model, &msg.TokensUsed, &msg.ParentID,
|
||||
&msg.SiblingIndex, &msg.CreatedAt,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create edit"})
|
||||
return
|
||||
@@ -282,7 +334,7 @@ func (h *MessageHandler) EditMessage(c *gin.Context) {
|
||||
|
||||
// Cursor now points to the new sibling (it's a leaf — no children yet)
|
||||
_ = updateCursor(channelID, userID, msg.ID)
|
||||
_, _ = database.DB.Exec(`UPDATE channels SET updated_at = NOW() WHERE id = $1`, channelID)
|
||||
_, _ = database.DB.Exec(database.Q(`UPDATE channels SET updated_at = NOW() WHERE id = $1`), channelID)
|
||||
|
||||
c.JSON(http.StatusCreated, msg)
|
||||
}
|
||||
@@ -315,10 +367,10 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
|
||||
// Load target message
|
||||
var targetParentID *string
|
||||
var targetRole string
|
||||
err := database.DB.QueryRow(`
|
||||
err := database.DB.QueryRow(database.Q(`
|
||||
SELECT parent_id, role FROM messages
|
||||
WHERE id = $1 AND channel_id = $2 AND deleted_at IS NULL
|
||||
`, messageID, channelID).Scan(&targetParentID, &targetRole)
|
||||
`), messageID, channelID).Scan(&targetParentID, &targetRole)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "message not found"})
|
||||
@@ -399,7 +451,7 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
|
||||
// Fallback: channel's stored model
|
||||
if model == "" {
|
||||
var channelModel *string
|
||||
_ = database.DB.QueryRow(`SELECT model FROM channels WHERE id = $1`, channelID).Scan(&channelModel)
|
||||
_ = database.DB.QueryRow(database.Q(`SELECT model FROM channels WHERE id = $1`), channelID).Scan(&channelModel)
|
||||
if channelModel != nil {
|
||||
model = *channelModel
|
||||
}
|
||||
@@ -427,7 +479,7 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
|
||||
llmMessages = append(llmMessages, providers.Message{Role: "system", Content: presetSystemPrompt})
|
||||
} else {
|
||||
var systemPrompt *string
|
||||
_ = database.DB.QueryRow(`SELECT system_prompt FROM channels WHERE id = $1`, channelID).Scan(&systemPrompt)
|
||||
_ = database.DB.QueryRow(database.Q(`SELECT system_prompt FROM channels WHERE id = $1`), channelID).Scan(&systemPrompt)
|
||||
if systemPrompt != nil && *systemPrompt != "" {
|
||||
llmMessages = append(llmMessages, providers.Message{Role: "system", Content: *systemPrompt})
|
||||
}
|
||||
@@ -480,16 +532,31 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
|
||||
}
|
||||
|
||||
var newID string
|
||||
err := database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, model, tool_calls,
|
||||
parent_id, participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, 'assistant', $2, $3, $4, $5, 'model', $6, $7)
|
||||
RETURNING id
|
||||
`, channelID, result.Content, model, tcVal, newParentID, model, siblingIdx).Scan(&newID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to persist regenerated message: %v", err)
|
||||
if database.IsSQLite() {
|
||||
newID = store.NewID()
|
||||
_, err := database.DB.Exec(`
|
||||
INSERT INTO messages (id, channel_id, role, content, model, tool_calls,
|
||||
parent_id, participant_type, participant_id, sibling_index)
|
||||
VALUES (?, ?, 'assistant', ?, ?, ?, ?, 'model', ?, ?)
|
||||
`, newID, channelID, result.Content, model, tcVal, newParentID, model, siblingIdx)
|
||||
if err != nil {
|
||||
log.Printf("Failed to persist regenerated message: %v", err)
|
||||
newID = ""
|
||||
}
|
||||
} else {
|
||||
err := database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, model, tool_calls,
|
||||
parent_id, participant_type, participant_id, sibling_index)
|
||||
VALUES ($1, 'assistant', $2, $3, $4, $5, 'model', $6, $7)
|
||||
RETURNING id
|
||||
`, channelID, result.Content, model, tcVal, newParentID, model, siblingIdx).Scan(&newID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to persist regenerated message: %v", err)
|
||||
newID = ""
|
||||
}
|
||||
}
|
||||
|
||||
if newID != "" {
|
||||
_ = updateCursor(channelID, userID, newID)
|
||||
|
||||
flusher, _ := c.Writer.(http.Flusher)
|
||||
@@ -505,7 +572,7 @@ func (h *MessageHandler) Regenerate(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = database.DB.Exec(`UPDATE channels SET updated_at = NOW() WHERE id = $1`, channelID)
|
||||
_, _ = database.DB.Exec(database.Q(`UPDATE channels SET updated_at = NOW() WHERE id = $1`), channelID)
|
||||
}
|
||||
|
||||
// Log usage for regeneration
|
||||
@@ -536,10 +603,10 @@ func (h *MessageHandler) UpdateCursor(c *gin.Context) {
|
||||
|
||||
// Verify the target message belongs to this channel
|
||||
var msgChannelID string
|
||||
err := database.DB.QueryRow(`
|
||||
err := database.DB.QueryRow(database.Q(`
|
||||
SELECT channel_id FROM messages
|
||||
WHERE id = $1 AND deleted_at IS NULL
|
||||
`, req.ActiveLeafID).Scan(&msgChannelID)
|
||||
`), req.ActiveLeafID).Scan(&msgChannelID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "message not found"})
|
||||
@@ -609,7 +676,7 @@ func (h *MessageHandler) ListSiblings(c *gin.Context) {
|
||||
func userOwnsChannel(c *gin.Context, channelID, userID string) bool {
|
||||
var ownerID string
|
||||
err := database.DB.QueryRow(
|
||||
`SELECT user_id FROM channels WHERE id = $1`, channelID,
|
||||
database.Q(`SELECT user_id FROM channels WHERE id = $1`), channelID,
|
||||
).Scan(&ownerID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
|
||||
Reference in New Issue
Block a user