package store import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" ext "git.warky.dev/wdevs/amcs/internal/types" ) func (db *DB) SaveChatHistory(ctx context.Context, h ext.ChatHistory) (ext.ChatHistory, error) { messages, err := json.Marshal(h.Messages) if err != nil { return ext.ChatHistory{}, fmt.Errorf("marshal messages: %w", err) } meta, err := json.Marshal(h.Metadata) if err != nil { return ext.ChatHistory{}, fmt.Errorf("marshal metadata: %w", err) } row := db.pool.QueryRow(ctx, ` insert into chat_histories (session_id, title, channel, agent_id, project_id, messages, summary, metadata) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id, created_at, updated_at `, h.SessionID, nullStr(h.Title), nullStr(h.Channel), nullStr(h.AgentID), h.ProjectID, messages, nullStr(h.Summary), meta, ) created := h if err := row.Scan(&created.ID, &created.CreatedAt, &created.UpdatedAt); err != nil { return ext.ChatHistory{}, fmt.Errorf("insert chat history: %w", err) } return created, nil } func (db *DB) GetChatHistory(ctx context.Context, id uuid.UUID) (ext.ChatHistory, bool, error) { row := db.pool.QueryRow(ctx, ` select id, session_id, title, channel, agent_id, project_id, messages, summary, metadata, created_at, updated_at from chat_histories where id = $1 `, id) h, err := scanChatHistory(row) if err == pgx.ErrNoRows { return ext.ChatHistory{}, false, nil } if err != nil { return ext.ChatHistory{}, false, fmt.Errorf("get chat history: %w", err) } return h, true, nil } func (db *DB) GetChatHistoryBySessionID(ctx context.Context, sessionID string) (ext.ChatHistory, bool, error) { row := db.pool.QueryRow(ctx, ` select id, session_id, title, channel, agent_id, project_id, messages, summary, metadata, created_at, updated_at from chat_histories where session_id = $1 order by created_at desc limit 1 `, sessionID) h, err := scanChatHistory(row) if err == pgx.ErrNoRows { return ext.ChatHistory{}, false, nil } if err != nil { return ext.ChatHistory{}, false, fmt.Errorf("get chat history by session: %w", err) } return h, true, nil } type ListChatHistoriesFilter struct { ProjectID *uuid.UUID Channel string AgentID string SessionID string Days int Limit int } func (db *DB) ListChatHistories(ctx context.Context, f ListChatHistoriesFilter) ([]ext.ChatHistory, error) { limit := f.Limit if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } conditions := []string{} args := []any{} if f.ProjectID != nil { args = append(args, *f.ProjectID) conditions = append(conditions, fmt.Sprintf("project_id = $%d", len(args))) } if f.Channel != "" { args = append(args, f.Channel) conditions = append(conditions, fmt.Sprintf("channel = $%d", len(args))) } if f.AgentID != "" { args = append(args, f.AgentID) conditions = append(conditions, fmt.Sprintf("agent_id = $%d", len(args))) } if f.SessionID != "" { args = append(args, f.SessionID) conditions = append(conditions, fmt.Sprintf("session_id = $%d", len(args))) } if f.Days > 0 { args = append(args, time.Now().UTC().AddDate(0, 0, -f.Days)) conditions = append(conditions, fmt.Sprintf("created_at >= $%d", len(args))) } q := ` select id, session_id, title, channel, agent_id, project_id, messages, summary, metadata, created_at, updated_at from chat_histories` if len(conditions) > 0 { q += " where " + strings.Join(conditions, " and ") } args = append(args, limit) q += fmt.Sprintf(" order by created_at desc limit $%d", len(args)) rows, err := db.pool.Query(ctx, q, args...) if err != nil { return nil, fmt.Errorf("list chat histories: %w", err) } defer rows.Close() var result []ext.ChatHistory for rows.Next() { h, err := scanChatHistory(rows) if err != nil { return nil, fmt.Errorf("scan chat history: %w", err) } result = append(result, h) } return result, rows.Err() } func (db *DB) DeleteChatHistory(ctx context.Context, id uuid.UUID) (bool, error) { tag, err := db.pool.Exec(ctx, `delete from chat_histories where id = $1`, id) if err != nil { return false, fmt.Errorf("delete chat history: %w", err) } return tag.RowsAffected() > 0, nil } type rowScanner interface { Scan(dest ...any) error } func scanChatHistory(row rowScanner) (ext.ChatHistory, error) { var h ext.ChatHistory var title, channel, agentID, summary *string var messagesJSON, metaJSON []byte if err := row.Scan( &h.ID, &h.SessionID, &title, &channel, &agentID, &h.ProjectID, &messagesJSON, &summary, &metaJSON, &h.CreatedAt, &h.UpdatedAt, ); err != nil { return ext.ChatHistory{}, err } h.Title = strVal(title) h.Channel = strVal(channel) h.AgentID = strVal(agentID) h.Summary = strVal(summary) if err := json.Unmarshal(messagesJSON, &h.Messages); err != nil { return ext.ChatHistory{}, fmt.Errorf("unmarshal messages: %w", err) } if err := json.Unmarshal(metaJSON, &h.Metadata); err != nil { return ext.ChatHistory{}, fmt.Errorf("unmarshal metadata: %w", err) } if h.Messages == nil { h.Messages = []ext.ChatMessage{} } if h.Metadata == nil { h.Metadata = map[string]any{} } return h, nil }