Changeset 0.6.0 (#36)

This commit is contained in:
2026-02-20 22:24:47 +00:00
parent 30d0c11219
commit 925b70f98c
34 changed files with 2575 additions and 637 deletions

View File

@@ -19,13 +19,14 @@ type createMessageRequest struct {
}
type messageResponse struct {
ID string `json:"id"`
ChatID string `json:"chat_id"`
Role string `json:"role"`
Content string `json:"content"`
Model *string `json:"model"`
TokensUsed *int `json:"tokens_used"`
CreatedAt string `json:"created_at"`
ID string `json:"id"`
ChannelID string `json:"channel_id"`
Role string `json:"role"`
Content string `json:"content"`
Model *string `json:"model"`
TokensUsed *int `json:"tokens_used"`
ParentID *string `json:"parent_id,omitempty"`
CreatedAt string `json:"created_at"`
}
// MessageHandler holds dependencies for message endpoints.
@@ -42,18 +43,18 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
page, perPage, offset := parsePagination(c)
userID := getUserID(c)
chatID := c.Param("id")
channelID := c.Param("id")
// Verify ownership
if !userOwnsChat(c, chatID, userID) {
if !userOwnsChannel(c, channelID, userID) {
return
}
// Count total messages
var total int
err := database.DB.QueryRow(
`SELECT COUNT(*) FROM chat_messages WHERE chat_id = $1`,
chatID,
`SELECT COUNT(*) FROM messages WHERE channel_id = $1`,
channelID,
).Scan(&total)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count messages"})
@@ -62,12 +63,12 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
// Fetch messages (oldest first for conversation display)
rows, err := database.DB.Query(`
SELECT id, chat_id, role, content, model, tokens_used, created_at
FROM chat_messages
WHERE chat_id = $1
SELECT id, channel_id, role, content, model, tokens_used, parent_id, created_at
FROM messages
WHERE channel_id = $1
ORDER BY created_at ASC
LIMIT $2 OFFSET $3
`, chatID, perPage, offset)
`, channelID, perPage, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list messages"})
return
@@ -78,8 +79,8 @@ func (h *MessageHandler) ListMessages(c *gin.Context) {
for rows.Next() {
var msg messageResponse
err := rows.Scan(
&msg.ID, &msg.ChatID, &msg.Role, &msg.Content,
&msg.Model, &msg.TokensUsed, &msg.CreatedAt,
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
&msg.Model, &msg.TokensUsed, &msg.ParentID, &msg.CreatedAt,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to scan message"})
@@ -107,29 +108,49 @@ func (h *MessageHandler) CreateMessage(c *gin.Context) {
}
userID := getUserID(c)
chatID := c.Param("id")
channelID := c.Param("id")
// Verify ownership
if !userOwnsChat(c, chatID, userID) {
if !userOwnsChannel(c, channelID, userID) {
return
}
// Find the current leaf message to set as parent
var parentID *string
_ = database.DB.QueryRow(
`SELECT id FROM messages WHERE channel_id = $1 ORDER BY created_at DESC LIMIT 1`,
channelID,
).Scan(&parentID)
var msg messageResponse
err := database.DB.QueryRow(`
INSERT INTO chat_messages (chat_id, role, content, model)
VALUES ($1, $2, $3, $4)
RETURNING id, chat_id, role, content, model, tokens_used, created_at
`, chatID, req.Role, req.Content, req.Model).Scan(
&msg.ID, &msg.ChatID, &msg.Role, &msg.Content,
&msg.Model, &msg.TokensUsed, &msg.CreatedAt,
INSERT INTO messages (channel_id, role, content, model, parent_id, participant_type, participant_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, channel_id, role, content, model, tokens_used, parent_id, created_at
`, channelID, req.Role, req.Content, req.Model, parentID,
func() string {
if req.Role == "assistant" {
return "model"
}
return "user"
}(),
func() string {
if req.Role == "assistant" && req.Model != "" {
return req.Model
}
return userID
}(),
).Scan(
&msg.ID, &msg.ChannelID, &msg.Role, &msg.Content,
&msg.Model, &msg.TokensUsed, &msg.ParentID, &msg.CreatedAt,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create message"})
return
}
// Touch the chat's updated_at so it sorts to top of list
_, _ = database.DB.Exec(`UPDATE chats SET updated_at = NOW() WHERE id = $1`, chatID)
// Touch the channel's updated_at so it sorts to top of list
_, _ = database.DB.Exec(`UPDATE channels SET updated_at = NOW() WHERE id = $1`, channelID)
c.JSON(http.StatusCreated, msg)
}
@@ -152,22 +173,22 @@ func (h *MessageHandler) Stream(c *gin.Context) {
// ── Ownership Check ─────────────────────────
func userOwnsChat(c *gin.Context, chatID, userID string) bool {
func userOwnsChannel(c *gin.Context, channelID, userID string) bool {
var ownerID string
err := database.DB.QueryRow(
`SELECT user_id FROM chats WHERE id = $1`, chatID,
`SELECT user_id FROM channels WHERE id = $1`, channelID,
).Scan(&ownerID)
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "chat not found"})
c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"})
return false
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to verify chat ownership"})
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to verify channel ownership"})
return false
}
if ownerID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "not your chat"})
c.JSON(http.StatusForbidden, gin.H{"error": "not your channel"})
return false
}
return true