- Store struct wrapping pgxpool.Pool with NewStore/Close/Pool - RunMigrations creates conversations + messages tables idempotently - DSN never logged to avoid credential exposure (T-06-01-02) - All queries parameterized (T-06-01-01)
40 lines
1.2 KiB
Go
40 lines
1.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// RunMigrations creates the conversations and messages tables idempotently.
|
|
// It is safe to call multiple times; existing tables are left untouched.
|
|
//
|
|
// Schema:
|
|
//
|
|
// conversations(id UUID PK, started_at TIMESTAMPTZ, model TEXT)
|
|
// messages(id UUID PK, conversation_id UUID FK, role TEXT CHECK, content TEXT, created_at TIMESTAMPTZ)
|
|
func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
|
|
statements := []string{
|
|
`CREATE TABLE IF NOT EXISTS conversations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
model TEXT NOT NULL DEFAULT ''
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL CHECK (role IN ('user','assistant','system')),
|
|
content TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)`,
|
|
}
|
|
|
|
for i, stmt := range statements {
|
|
if _, err := pool.Exec(ctx, stmt); err != nil {
|
|
return fmt.Errorf("store: migration statement %d: %w", i, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|