chore: refactor db

This commit is contained in:
adamdottv 2025-05-13 13:08:43 -05:00
parent d8f3b60625
commit 01b6bf5bb7
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
19 changed files with 193 additions and 156 deletions

View file

@ -15,13 +15,11 @@ INSERT INTO files (
session_id, session_id,
path, path,
content, content,
version, version
created_at,
updated_at
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') ?, ?, ?, ?, ?
) )
RETURNING id, session_id, path, content, version, created_at, updated_at RETURNING id, session_id, path, content, version, is_new, created_at, updated_at
` `
type CreateFileParams struct { type CreateFileParams struct {
@ -47,6 +45,7 @@ func (q *Queries) CreateFile(ctx context.Context, arg CreateFileParams) (File, e
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -74,7 +73,7 @@ func (q *Queries) DeleteSessionFiles(ctx context.Context, sessionID string) erro
} }
const getFile = `-- name: GetFile :one const getFile = `-- name: GetFile :one
SELECT id, session_id, path, content, version, created_at, updated_at SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files FROM files
WHERE id = ? LIMIT 1 WHERE id = ? LIMIT 1
` `
@ -88,6 +87,7 @@ func (q *Queries) GetFile(ctx context.Context, id string) (File, error) {
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -95,7 +95,7 @@ func (q *Queries) GetFile(ctx context.Context, id string) (File, error) {
} }
const getFileByPathAndSession = `-- name: GetFileByPathAndSession :one const getFileByPathAndSession = `-- name: GetFileByPathAndSession :one
SELECT id, session_id, path, content, version, created_at, updated_at SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files FROM files
WHERE path = ? AND session_id = ? WHERE path = ? AND session_id = ?
ORDER BY created_at DESC ORDER BY created_at DESC
@ -116,6 +116,7 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )
@ -123,7 +124,7 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
} }
const listFilesByPath = `-- name: ListFilesByPath :many const listFilesByPath = `-- name: ListFilesByPath :many
SELECT id, session_id, path, content, version, created_at, updated_at SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files FROM files
WHERE path = ? WHERE path = ?
ORDER BY created_at DESC ORDER BY created_at DESC
@ -144,6 +145,7 @@ func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, err
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -161,7 +163,7 @@ func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, err
} }
const listFilesBySession = `-- name: ListFilesBySession :many const listFilesBySession = `-- name: ListFilesBySession :many
SELECT id, session_id, path, content, version, created_at, updated_at SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files FROM files
WHERE session_id = ? WHERE session_id = ?
ORDER BY created_at ASC ORDER BY created_at ASC
@ -182,6 +184,7 @@ func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]F
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -199,7 +202,7 @@ func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]F
} }
const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many
SELECT f.id, f.session_id, f.path, f.content, f.version, f.created_at, f.updated_at SELECT f.id, f.session_id, f.path, f.content, f.version, f.is_new, f.created_at, f.updated_at
FROM files f FROM files f
INNER JOIN ( INNER JOIN (
SELECT path, MAX(created_at) as max_created_at SELECT path, MAX(created_at) as max_created_at
@ -225,6 +228,7 @@ func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string)
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -242,7 +246,7 @@ func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string)
} }
const listNewFiles = `-- name: ListNewFiles :many const listNewFiles = `-- name: ListNewFiles :many
SELECT id, session_id, path, content, version, created_at, updated_at SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files FROM files
WHERE is_new = 1 WHERE is_new = 1
ORDER BY created_at DESC ORDER BY created_at DESC
@ -263,6 +267,7 @@ func (q *Queries) ListNewFiles(ctx context.Context) ([]File, error) {
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -284,9 +289,9 @@ UPDATE files
SET SET
content = ?, content = ?,
version = ?, version = ?,
updated_at = strftime('%s', 'now') updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ? WHERE id = ?
RETURNING id, session_id, path, content, version, created_at, updated_at RETURNING id, session_id, path, content, version, is_new, created_at, updated_at
` `
type UpdateFileParams struct { type UpdateFileParams struct {
@ -304,6 +309,7 @@ func (q *Queries) UpdateFile(ctx context.Context, arg UpdateFileParams) (File, e
&i.Path, &i.Path,
&i.Content, &i.Content,
&i.Version, &i.Version,
&i.IsNew,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
) )

View file

@ -17,27 +17,24 @@ INSERT INTO logs (
timestamp, timestamp,
level, level,
message, message,
attributes, attributes
created_at
) VALUES ( ) VALUES (
?, ?,
?, ?,
?, ?,
?, ?,
?, ?,
?,
? ?
) RETURNING id, session_id, timestamp, level, message, attributes, created_at ) RETURNING id, session_id, timestamp, level, message, attributes, created_at, updated_at
` `
type CreateLogParams struct { type CreateLogParams struct {
ID string `json:"id"` ID string `json:"id"`
SessionID sql.NullString `json:"session_id"` SessionID sql.NullString `json:"session_id"`
Timestamp int64 `json:"timestamp"` Timestamp string `json:"timestamp"`
Level string `json:"level"` Level string `json:"level"`
Message string `json:"message"` Message string `json:"message"`
Attributes sql.NullString `json:"attributes"` Attributes sql.NullString `json:"attributes"`
CreatedAt int64 `json:"created_at"`
} }
func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, error) { func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, error) {
@ -48,7 +45,6 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro
arg.Level, arg.Level,
arg.Message, arg.Message,
arg.Attributes, arg.Attributes,
arg.CreatedAt,
) )
var i Log var i Log
err := row.Scan( err := row.Scan(
@ -59,12 +55,13 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro
&i.Message, &i.Message,
&i.Attributes, &i.Attributes,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt,
) )
return i, err return i, err
} }
const listAllLogs = `-- name: ListAllLogs :many const listAllLogs = `-- name: ListAllLogs :many
SELECT id, session_id, timestamp, level, message, attributes, created_at FROM logs SELECT id, session_id, timestamp, level, message, attributes, created_at, updated_at FROM logs
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT ? LIMIT ?
` `
@ -86,6 +83,7 @@ func (q *Queries) ListAllLogs(ctx context.Context, limit int64) ([]Log, error) {
&i.Message, &i.Message,
&i.Attributes, &i.Attributes,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -101,7 +99,7 @@ func (q *Queries) ListAllLogs(ctx context.Context, limit int64) ([]Log, error) {
} }
const listLogsBySession = `-- name: ListLogsBySession :many const listLogsBySession = `-- name: ListLogsBySession :many
SELECT id, session_id, timestamp, level, message, attributes, created_at FROM logs SELECT id, session_id, timestamp, level, message, attributes, created_at, updated_at FROM logs
WHERE session_id = ? WHERE session_id = ?
ORDER BY timestamp ASC ORDER BY timestamp ASC
` `
@ -123,6 +121,7 @@ func (q *Queries) ListLogsBySession(ctx context.Context, sessionID sql.NullStrin
&i.Message, &i.Message,
&i.Attributes, &i.Attributes,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt,
); err != nil { ); err != nil {
return nil, err return nil, err
} }

View file

@ -16,11 +16,9 @@ INSERT INTO messages (
session_id, session_id,
role, role,
parts, parts,
model, model
created_at,
updated_at
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') ?, ?, ?, ?, ?
) )
RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at
` `
@ -145,7 +143,7 @@ ORDER BY created_at ASC
type ListMessagesBySessionAfterParams struct { type ListMessagesBySessionAfterParams struct {
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
CreatedAt int64 `json:"created_at"` CreatedAt string `json:"created_at"`
} }
func (q *Queries) ListMessagesBySessionAfter(ctx context.Context, arg ListMessagesBySessionAfterParams) ([]Message, error) { func (q *Queries) ListMessagesBySessionAfter(ctx context.Context, arg ListMessagesBySessionAfterParams) ([]Message, error) {
@ -185,14 +183,14 @@ UPDATE messages
SET SET
parts = ?, parts = ?,
finished_at = ?, finished_at = ?,
updated_at = strftime('%s', 'now') updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ? WHERE id = ?
` `
type UpdateMessageParams struct { type UpdateMessageParams struct {
Parts string `json:"parts"` Parts string `json:"parts"`
FinishedAt sql.NullInt64 `json:"finished_at"` FinishedAt sql.NullString `json:"finished_at"`
ID string `json:"id"` ID string `json:"id"`
} }
func (q *Queries) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error { func (q *Queries) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error {

View file

@ -1,11 +0,0 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE sessions ADD COLUMN summary TEXT;
ALTER TABLE sessions ADD COLUMN summarized_at INTEGER;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE sessions DROP COLUMN summarized_at;
ALTER TABLE sessions DROP COLUMN summary;
-- +goose StatementEnd

View file

@ -1,16 +0,0 @@
-- +goose Up
CREATE TABLE logs (
id TEXT PRIMARY KEY,
session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
timestamp INTEGER NOT NULL,
level TEXT NOT NULL,
message TEXT NOT NULL,
attributes TEXT,
created_at INTEGER NOT NULL
);
CREATE INDEX logs_session_id_idx ON logs(session_id);
CREATE INDEX logs_timestamp_idx ON logs(timestamp);
-- +goose Down
DROP TABLE logs;

View file

@ -6,17 +6,19 @@ CREATE TABLE IF NOT EXISTS sessions (
parent_session_id TEXT, parent_session_id TEXT,
title TEXT NOT NULL, title TEXT NOT NULL,
message_count INTEGER NOT NULL DEFAULT 0 CHECK (message_count >= 0), message_count INTEGER NOT NULL DEFAULT 0 CHECK (message_count >= 0),
prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0), prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0),
completion_tokens INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens>= 0), completion_tokens INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens >= 0),
cost REAL NOT NULL DEFAULT 0.0 CHECK (cost >= 0.0), cost REAL NOT NULL DEFAULT 0.0 CHECK (cost >= 0.0),
updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds summary TEXT,
created_at INTEGER NOT NULL -- Unix timestamp in milliseconds summarized_at TEXT,
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now'))
); );
CREATE TRIGGER IF NOT EXISTS update_sessions_updated_at CREATE TRIGGER IF NOT EXISTS update_sessions_updated_at
AFTER UPDATE ON sessions AFTER UPDATE ON sessions
BEGIN BEGIN
UPDATE sessions SET updated_at = strftime('%s', 'now') UPDATE sessions SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id; WHERE id = new.id;
END; END;
@ -27,8 +29,9 @@ CREATE TABLE IF NOT EXISTS files (
path TEXT NOT NULL, path TEXT NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds is_new INTEGER DEFAULT 0,
updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE, FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE,
UNIQUE(path, session_id, version) UNIQUE(path, session_id, version)
); );
@ -39,7 +42,7 @@ CREATE INDEX IF NOT EXISTS idx_files_path ON files (path);
CREATE TRIGGER IF NOT EXISTS update_files_updated_at CREATE TRIGGER IF NOT EXISTS update_files_updated_at
AFTER UPDATE ON files AFTER UPDATE ON files
BEGIN BEGIN
UPDATE files SET updated_at = strftime('%s', 'now') UPDATE files SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id; WHERE id = new.id;
END; END;
@ -50,9 +53,9 @@ CREATE TABLE IF NOT EXISTS messages (
role TEXT NOT NULL, role TEXT NOT NULL,
parts TEXT NOT NULL default '[]', parts TEXT NOT NULL default '[]',
model TEXT, model TEXT,
created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
finished_at INTEGER, -- Unix timestamp in milliseconds finished_at TEXT,
FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE
); );
@ -61,7 +64,7 @@ CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages (session_id);
CREATE TRIGGER IF NOT EXISTS update_messages_updated_at CREATE TRIGGER IF NOT EXISTS update_messages_updated_at
AFTER UPDATE ON messages AFTER UPDATE ON messages
BEGIN BEGIN
UPDATE messages SET updated_at = strftime('%s', 'now') UPDATE messages SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id; WHERE id = new.id;
END; END;
@ -81,6 +84,28 @@ UPDATE sessions SET
WHERE id = old.session_id; WHERE id = old.session_id;
END; END;
-- Logs
CREATE TABLE IF NOT EXISTS logs (
id TEXT PRIMARY KEY,
session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
timestamp TEXT NOT NULL,
level TEXT NOT NULL,
message TEXT NOT NULL,
attributes TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now'))
);
CREATE INDEX logs_session_id_idx ON logs(session_id);
CREATE INDEX logs_timestamp_idx ON logs(timestamp);
CREATE TRIGGER IF NOT EXISTS update_logs_updated_at
AFTER UPDATE ON logs
BEGIN
UPDATE logs SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id;
END;
-- +goose StatementEnd -- +goose StatementEnd
-- +goose Down -- +goose Down
@ -88,11 +113,13 @@ END;
DROP TRIGGER IF EXISTS update_sessions_updated_at; DROP TRIGGER IF EXISTS update_sessions_updated_at;
DROP TRIGGER IF EXISTS update_messages_updated_at; DROP TRIGGER IF EXISTS update_messages_updated_at;
DROP TRIGGER IF EXISTS update_files_updated_at; DROP TRIGGER IF EXISTS update_files_updated_at;
DROP TRIGGER IF EXISTS update_logs_updated_at;
DROP TRIGGER IF EXISTS update_session_message_count_on_delete; DROP TRIGGER IF EXISTS update_session_message_count_on_delete;
DROP TRIGGER IF EXISTS update_session_message_count_on_insert; DROP TRIGGER IF EXISTS update_session_message_count_on_insert;
DROP TABLE IF EXISTS sessions; DROP TABLE IF EXISTS logs;
DROP TABLE IF EXISTS messages; DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS files; DROP TABLE IF EXISTS files;
DROP TABLE IF EXISTS sessions;
-- +goose StatementEnd -- +goose StatementEnd

View file

@ -9,23 +9,25 @@ import (
) )
type File struct { type File struct {
ID string `json:"id"` ID string `json:"id"`
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
Path string `json:"path"` Path string `json:"path"`
Content string `json:"content"` Content string `json:"content"`
Version string `json:"version"` Version string `json:"version"`
CreatedAt int64 `json:"created_at"` IsNew sql.NullInt64 `json:"is_new"`
UpdatedAt int64 `json:"updated_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
} }
type Log struct { type Log struct {
ID string `json:"id"` ID string `json:"id"`
SessionID sql.NullString `json:"session_id"` SessionID sql.NullString `json:"session_id"`
Timestamp int64 `json:"timestamp"` Timestamp string `json:"timestamp"`
Level string `json:"level"` Level string `json:"level"`
Message string `json:"message"` Message string `json:"message"`
Attributes sql.NullString `json:"attributes"` Attributes sql.NullString `json:"attributes"`
CreatedAt int64 `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
} }
type Message struct { type Message struct {
@ -34,9 +36,9 @@ type Message struct {
Role string `json:"role"` Role string `json:"role"`
Parts string `json:"parts"` Parts string `json:"parts"`
Model sql.NullString `json:"model"` Model sql.NullString `json:"model"`
CreatedAt int64 `json:"created_at"` CreatedAt string `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt string `json:"updated_at"`
FinishedAt sql.NullInt64 `json:"finished_at"` FinishedAt sql.NullString `json:"finished_at"`
} }
type Session struct { type Session struct {
@ -47,8 +49,8 @@ type Session struct {
PromptTokens int64 `json:"prompt_tokens"` PromptTokens int64 `json:"prompt_tokens"`
CompletionTokens int64 `json:"completion_tokens"` CompletionTokens int64 `json:"completion_tokens"`
Cost float64 `json:"cost"` Cost float64 `json:"cost"`
UpdatedAt int64 `json:"updated_at"`
CreatedAt int64 `json:"created_at"`
Summary sql.NullString `json:"summary"` Summary sql.NullString `json:"summary"`
SummarizedAt sql.NullInt64 `json:"summarized_at"` SummarizedAt sql.NullString `json:"summarized_at"`
UpdatedAt string `json:"updated_at"`
CreatedAt string `json:"created_at"`
} }

View file

@ -20,9 +20,7 @@ INSERT INTO sessions (
completion_tokens, completion_tokens,
cost, cost,
summary, summary,
summarized_at, summarized_at
updated_at,
created_at
) VALUES ( ) VALUES (
?, ?,
?, ?,
@ -32,10 +30,8 @@ INSERT INTO sessions (
?, ?,
?, ?,
?, ?,
?, ?
strftime('%s', 'now'), ) RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
strftime('%s', 'now')
) RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
` `
type CreateSessionParams struct { type CreateSessionParams struct {
@ -47,7 +43,7 @@ type CreateSessionParams struct {
CompletionTokens int64 `json:"completion_tokens"` CompletionTokens int64 `json:"completion_tokens"`
Cost float64 `json:"cost"` Cost float64 `json:"cost"`
Summary sql.NullString `json:"summary"` Summary sql.NullString `json:"summary"`
SummarizedAt sql.NullInt64 `json:"summarized_at"` SummarizedAt sql.NullString `json:"summarized_at"`
} }
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) { func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
@ -71,10 +67,10 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
&i.PromptTokens, &i.PromptTokens,
&i.CompletionTokens, &i.CompletionTokens,
&i.Cost, &i.Cost,
&i.UpdatedAt,
&i.CreatedAt,
&i.Summary, &i.Summary,
&i.SummarizedAt, &i.SummarizedAt,
&i.UpdatedAt,
&i.CreatedAt,
) )
return i, err return i, err
} }
@ -90,7 +86,7 @@ func (q *Queries) DeleteSession(ctx context.Context, id string) error {
} }
const getSessionByID = `-- name: GetSessionByID :one const getSessionByID = `-- name: GetSessionByID :one
SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
FROM sessions FROM sessions
WHERE id = ? LIMIT 1 WHERE id = ? LIMIT 1
` `
@ -106,16 +102,16 @@ func (q *Queries) GetSessionByID(ctx context.Context, id string) (Session, error
&i.PromptTokens, &i.PromptTokens,
&i.CompletionTokens, &i.CompletionTokens,
&i.Cost, &i.Cost,
&i.UpdatedAt,
&i.CreatedAt,
&i.Summary, &i.Summary,
&i.SummarizedAt, &i.SummarizedAt,
&i.UpdatedAt,
&i.CreatedAt,
) )
return i, err return i, err
} }
const listSessions = `-- name: ListSessions :many const listSessions = `-- name: ListSessions :many
SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
FROM sessions FROM sessions
WHERE parent_session_id is NULL WHERE parent_session_id is NULL
ORDER BY created_at DESC ORDER BY created_at DESC
@ -138,10 +134,10 @@ func (q *Queries) ListSessions(ctx context.Context) ([]Session, error) {
&i.PromptTokens, &i.PromptTokens,
&i.CompletionTokens, &i.CompletionTokens,
&i.Cost, &i.Cost,
&i.UpdatedAt,
&i.CreatedAt,
&i.Summary, &i.Summary,
&i.SummarizedAt, &i.SummarizedAt,
&i.UpdatedAt,
&i.CreatedAt,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -166,7 +162,7 @@ SET
summary = ?, summary = ?,
summarized_at = ? summarized_at = ?
WHERE id = ? WHERE id = ?
RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
` `
type UpdateSessionParams struct { type UpdateSessionParams struct {
@ -175,7 +171,7 @@ type UpdateSessionParams struct {
CompletionTokens int64 `json:"completion_tokens"` CompletionTokens int64 `json:"completion_tokens"`
Cost float64 `json:"cost"` Cost float64 `json:"cost"`
Summary sql.NullString `json:"summary"` Summary sql.NullString `json:"summary"`
SummarizedAt sql.NullInt64 `json:"summarized_at"` SummarizedAt sql.NullString `json:"summarized_at"`
ID string `json:"id"` ID string `json:"id"`
} }
@ -198,10 +194,10 @@ func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) (S
&i.PromptTokens, &i.PromptTokens,
&i.CompletionTokens, &i.CompletionTokens,
&i.Cost, &i.Cost,
&i.UpdatedAt,
&i.CreatedAt,
&i.Summary, &i.Summary,
&i.SummarizedAt, &i.SummarizedAt,
&i.UpdatedAt,
&i.CreatedAt,
) )
return i, err return i, err
} }

View file

@ -28,11 +28,9 @@ INSERT INTO files (
session_id, session_id,
path, path,
content, content,
version, version
created_at,
updated_at
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') ?, ?, ?, ?, ?
) )
RETURNING *; RETURNING *;
@ -41,7 +39,7 @@ UPDATE files
SET SET
content = ?, content = ?,
version = ?, version = ?,
updated_at = strftime('%s', 'now') updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ? WHERE id = ?
RETURNING *; RETURNING *;

View file

@ -5,15 +5,13 @@ INSERT INTO logs (
timestamp, timestamp,
level, level,
message, message,
attributes, attributes
created_at
) VALUES ( ) VALUES (
?, ?,
?, ?,
?, ?,
?, ?,
?, ?,
?,
? ?
) RETURNING *; ) RETURNING *;

View file

@ -21,11 +21,9 @@ INSERT INTO messages (
session_id, session_id,
role, role,
parts, parts,
model, model
created_at,
updated_at
) VALUES ( ) VALUES (
?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now') ?, ?, ?, ?, ?
) )
RETURNING *; RETURNING *;
@ -34,7 +32,7 @@ UPDATE messages
SET SET
parts = ?, parts = ?,
finished_at = ?, finished_at = ?,
updated_at = strftime('%s', 'now') updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ?; WHERE id = ?;

View file

@ -8,9 +8,7 @@ INSERT INTO sessions (
completion_tokens, completion_tokens,
cost, cost,
summary, summary,
summarized_at, summarized_at
updated_at,
created_at
) VALUES ( ) VALUES (
?, ?,
?, ?,
@ -20,9 +18,7 @@ INSERT INTO sessions (
?, ?,
?, ?,
?, ?,
?, ?
strftime('%s', 'now'),
strftime('%s', 'now')
) RETURNING *; ) RETURNING *;
-- name: GetSessionByID :one -- name: GetSessionByID :one

View file

@ -113,7 +113,13 @@ func (s *service) CreateVersion(ctx context.Context, sessionID, path, content st
if b.Version == InitialVersion && a.Version != InitialVersion { if b.Version == InitialVersion && a.Version != InitialVersion {
return -1 return -1
} }
return int(b.CreatedAt - a.CreatedAt) // Fallback to timestamp // Compare timestamps as strings (ISO format sorts correctly)
if b.CreatedAt > a.CreatedAt {
return 1
} else if a.CreatedAt > b.CreatedAt {
return -1
}
return 0 // Equal timestamps
}) })
latestFile := files[0] latestFile := files[0]
@ -362,14 +368,27 @@ func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[File] {
} }
func (s *service) fromDBItem(item db.File) File { func (s *service) fromDBItem(item db.File) File {
// Parse timestamps from ISO strings
createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
if err != nil {
slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
createdAt = time.Now() // Fallback
}
updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt)
if err != nil {
slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
updatedAt = time.Now() // Fallback
}
return File{ return File{
ID: item.ID, ID: item.ID,
SessionID: item.SessionID, SessionID: item.SessionID,
Path: item.Path, Path: item.Path,
Content: item.Content, Content: item.Content,
Version: item.Version, Version: item.Version,
CreatedAt: time.UnixMilli(item.CreatedAt * 1000), CreatedAt: createdAt,
UpdatedAt: time.UnixMilli(item.UpdatedAt * 1000), UpdatedAt: updatedAt,
} }
} }

View file

@ -209,7 +209,7 @@ func (a *agent) prepareMessageHistory(ctx context.Context, sessionID string) (se
var sessionMessages []message.Message var sessionMessages []message.Message
if currentSession.Summary != "" && !currentSession.SummarizedAt.IsZero() { if currentSession.Summary != "" && !currentSession.SummarizedAt.IsZero() {
// If summary exists, only fetch messages after the summarization timestamp // If summary exists, only fetch messages after the summarization timestamp
sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt.UnixMilli()) sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt)
if err != nil { if err != nil {
return currentSession, nil, fmt.Errorf("failed to list messages after summary: %w", err) return currentSession, nil, fmt.Errorf("failed to list messages after summary: %w", err)
} }

View file

@ -86,11 +86,10 @@ func (s *service) Create(ctx context.Context, timestamp time.Time, level, messag
dbLog, err := s.db.CreateLog(ctx, db.CreateLogParams{ dbLog, err := s.db.CreateLog(ctx, db.CreateLogParams{
ID: uuid.New().String(), ID: uuid.New().String(),
SessionID: sql.NullString{String: sessionID, Valid: sessionID != ""}, SessionID: sql.NullString{String: sessionID, Valid: sessionID != ""},
Timestamp: timestamp.UnixMilli(), Timestamp: timestamp.UTC().Format(time.RFC3339Nano),
Level: level, Level: level,
Message: message, Message: message,
Attributes: attributesJSON, Attributes: attributesJSON,
CreatedAt: time.Now().UnixMilli(),
}) })
if err != nil { if err != nil {
@ -135,10 +134,24 @@ func (s *service) fromDBItem(item db.Log) Log {
log := Log{ log := Log{
ID: item.ID, ID: item.ID,
SessionID: item.SessionID.String, SessionID: item.SessionID.String,
Timestamp: time.UnixMilli(item.Timestamp),
Level: item.Level, Level: item.Level,
Message: item.Message, Message: item.Message,
CreatedAt: time.UnixMilli(item.CreatedAt), }
// Parse timestamp from ISO string
timestamp, err := time.Parse(time.RFC3339Nano, item.Timestamp)
if err == nil {
log.Timestamp = timestamp
} else {
log.Timestamp = time.Now() // Fallback
}
// Parse created_at from ISO string
createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
if err == nil {
log.CreatedAt = createdAt
} else {
log.CreatedAt = time.Now() // Fallback
} }
if item.Attributes.Valid && item.Attributes.String != "" { if item.Attributes.Valid && item.Attributes.String != "" {
@ -195,7 +208,7 @@ func (sw *slogWriter) Write(p []byte) (n int, err error) {
parsedTime, timeErr = time.Parse(time.RFC3339, value) parsedTime, timeErr = time.Parse(time.RFC3339, value)
if timeErr != nil { if timeErr != nil {
slog.Error("Failed to parse time in slog writer", "value", value, "error", timeErr) slog.Error("Failed to parse time in slog writer", "value", value, "error", timeErr)
timestamp = time.Now() timestamp = time.Now().UTC()
hasTimestamp = true hasTimestamp = true
continue continue
} }

View file

@ -45,7 +45,7 @@ type Service interface {
Update(ctx context.Context, message Message) (Message, error) Update(ctx context.Context, message Message) (Message, error)
Get(ctx context.Context, id string) (Message, error) Get(ctx context.Context, id string) (Message, error)
List(ctx context.Context, sessionID string) ([]Message, error) List(ctx context.Context, sessionID string) ([]Message, error)
ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error)
Delete(ctx context.Context, id string) error Delete(ctx context.Context, id string) error
DeleteSessionMessages(ctx context.Context, sessionID string) error DeleteSessionMessages(ctx context.Context, sessionID string) error
} }
@ -134,12 +134,12 @@ func (s *service) Update(ctx context.Context, message Message) (Message, error)
return Message{}, fmt.Errorf("failed to marshal message parts for update: %w", err) return Message{}, fmt.Errorf("failed to marshal message parts for update: %w", err)
} }
var dbFinishedAt sql.NullInt64 var dbFinishedAt sql.NullString
finishPart := message.FinishPart() finishPart := message.FinishPart()
if finishPart != nil && !finishPart.Time.IsZero() { if finishPart != nil && !finishPart.Time.IsZero() {
dbFinishedAt = sql.NullInt64{ dbFinishedAt = sql.NullString{
Int64: finishPart.Time.UnixMilli(), String: finishPart.Time.UTC().Format(time.RFC3339Nano),
Valid: true, Valid: true,
} }
} }
@ -199,13 +199,13 @@ func (s *service) List(ctx context.Context, sessionID string) ([]Message, error)
return messages, nil return messages, nil
} }
func (s *service) ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) { func (s *service) ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error) {
s.mu.RLock() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
dbMessages, err := s.db.ListMessagesBySessionAfter(ctx, db.ListMessagesBySessionAfterParams{ dbMessages, err := s.db.ListMessagesBySessionAfter(ctx, db.ListMessagesBySessionAfterParams{
SessionID: sessionID, SessionID: sessionID,
CreatedAt: timestampMillis, CreatedAt: timestamp.Format(time.RFC3339Nano),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("db.ListMessagesBySessionAfter: %w", err) return nil, fmt.Errorf("db.ListMessagesBySessionAfter: %w", err)
@ -294,14 +294,27 @@ func (s *service) fromDBItem(item db.Message) (Message, error) {
return Message{}, fmt.Errorf("unmarshallParts for message ID %s: %w. Raw parts: %s", item.ID, err, item.Parts) return Message{}, fmt.Errorf("unmarshallParts for message ID %s: %w. Raw parts: %s", item.ID, err, item.Parts)
} }
// Parse timestamps from ISO strings
createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
if err != nil {
slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
createdAt = time.Now() // Fallback
}
updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt)
if err != nil {
slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
updatedAt = time.Now() // Fallback
}
msg := Message{ msg := Message{
ID: item.ID, ID: item.ID,
SessionID: item.SessionID, SessionID: item.SessionID,
Role: MessageRole(item.Role), Role: MessageRole(item.Role),
Parts: parts, Parts: parts,
Model: models.ModelID(item.Model.String), Model: models.ModelID(item.Model.String),
CreatedAt: time.UnixMilli(item.CreatedAt), CreatedAt: createdAt,
UpdatedAt: time.UnixMilli(item.UpdatedAt), UpdatedAt: updatedAt,
} }
return msg, nil return msg, nil
@ -323,8 +336,8 @@ func List(ctx context.Context, sessionID string) ([]Message, error) {
return GetService().List(ctx, sessionID) return GetService().List(ctx, sessionID)
} }
func ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) { func ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error) {
return GetService().ListAfter(ctx, sessionID, timestampMillis) return GetService().ListAfter(ctx, sessionID, timestamp)
} }
func Delete(ctx context.Context, id string) error { func Delete(ctx context.Context, id string) error {

View file

@ -153,10 +153,6 @@ func (s *service) Update(ctx context.Context, session Session) (Session, error)
if session.ID == "" { if session.ID == "" {
return Session{}, fmt.Errorf("cannot update session with empty ID") return Session{}, fmt.Errorf("cannot update session with empty ID")
} }
var summarizedAt sql.NullInt64
if !session.SummarizedAt.IsZero() {
summarizedAt = sql.NullInt64{Int64: session.SummarizedAt.UnixMilli(), Valid: true}
}
params := db.UpdateSessionParams{ params := db.UpdateSessionParams{
ID: session.ID, ID: session.ID,
@ -165,7 +161,7 @@ func (s *service) Update(ctx context.Context, session Session) (Session, error)
CompletionTokens: session.CompletionTokens, CompletionTokens: session.CompletionTokens,
Cost: session.Cost, Cost: session.Cost,
Summary: sql.NullString{String: session.Summary, Valid: session.Summary != ""}, Summary: sql.NullString{String: session.Summary, Valid: session.Summary != ""},
SummarizedAt: summarizedAt, SummarizedAt: sql.NullString{String: session.SummarizedAt.UTC().Format(time.RFC3339Nano), Valid: !session.SummarizedAt.IsZero()},
} }
dbSession, err := s.db.UpdateSession(ctx, params) dbSession, err := s.db.UpdateSession(ctx, params)
if err != nil { if err != nil {
@ -206,9 +202,15 @@ func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[Session] {
func (s *service) fromDBItem(item db.Session) Session { func (s *service) fromDBItem(item db.Session) Session {
var summarizedAt time.Time var summarizedAt time.Time
if item.SummarizedAt.Valid { if item.SummarizedAt.Valid {
summarizedAt = time.UnixMilli(item.SummarizedAt.Int64) parsedTime, err := time.Parse(time.RFC3339Nano, item.SummarizedAt.String)
if err == nil {
summarizedAt = parsedTime
}
} }
createdAt, _ := time.Parse(time.RFC3339Nano, item.CreatedAt)
updatedAt, _ := time.Parse(time.RFC3339Nano, item.UpdatedAt)
return Session{ return Session{
ID: item.ID, ID: item.ID,
ParentSessionID: item.ParentSessionID.String, ParentSessionID: item.ParentSessionID.String,
@ -219,8 +221,8 @@ func (s *service) fromDBItem(item db.Session) Session {
Cost: item.Cost, Cost: item.Cost,
Summary: item.Summary.String, Summary: item.Summary.String,
SummarizedAt: summarizedAt, SummarizedAt: summarizedAt,
CreatedAt: time.UnixMilli(item.CreatedAt), CreatedAt: createdAt,
UpdatedAt: time.UnixMilli(item.UpdatedAt), UpdatedAt: updatedAt,
} }
} }

View file

@ -605,7 +605,7 @@ func renderToolMessage(
return toolMsg return toolMsg
} }
params := renderToolParams(width-2-lipgloss.Width(toolNameText), toolCall) params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
responseContent := "" responseContent := ""
if response != nil { if response != nil {
responseContent = renderToolResponse(toolCall, *response, width-2) responseContent = renderToolResponse(toolCall, *response, width-2)

View file

@ -159,8 +159,7 @@ func (i *tableCmp) updateRows() {
rows := make([]table.Row, 0, len(i.logs)) rows := make([]table.Row, 0, len(i.logs))
for _, log := range i.logs { for _, log := range i.logs {
// Format timestamp as time timeStr := log.Timestamp.Local().Format("15:04:05")
timeStr := log.Timestamp.Format("15:04:05")
// Include ID as hidden first column for selection // Include ID as hidden first column for selection
row := table.Row{ row := table.Row{