11package main
22
33import (
4+ "crypto/rand"
45 "database/sql"
56 "encoding/json"
67 "fmt"
8+ "math/big"
79 "os"
810 "path/filepath"
11+ "time"
912
1013 _ "modernc.org/sqlite"
1114)
@@ -47,12 +50,13 @@ func OpenDB(path string) (*DB, error) {
4750 return & DB {db : db }, nil
4851}
4952
50- // InitDB creates the events table
53+ // InitDB creates the events table and metadata table
5154func (d * DB ) InitDB () error {
5255 schema := `
5356 CREATE TABLE IF NOT EXISTS events (
5457 id TEXT PRIMARY KEY,
5558 ts INTEGER NOT NULL,
59+ created_at INTEGER NOT NULL,
5660 actor TEXT NOT NULL,
5761 role TEXT NOT NULL,
5862 kind TEXT NOT NULL,
@@ -65,23 +69,44 @@ func (d *DB) InitDB() error {
6569 );
6670 CREATE INDEX IF NOT EXISTS idx_events_ts ON events(ts, id);
6771 CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind);
72+
73+ CREATE TABLE IF NOT EXISTS metadata (
74+ key TEXT PRIMARY KEY,
75+ value TEXT NOT NULL
76+ );
77+
78+ CREATE TABLE IF NOT EXISTS task_counter (
79+ last_id INTEGER NOT NULL
80+ );
6881 `
6982
7083 if _ , err := d .db .Exec (schema ); err != nil {
7184 return fmt .Errorf ("failed to create schema: %w" , err )
7285 }
7386
87+ // Initialize counter if it doesn't exist
88+ var count int
89+ err := d .db .QueryRow ("SELECT COUNT(*) FROM task_counter" ).Scan (& count )
90+ if err != nil {
91+ return fmt .Errorf ("failed to check counter: %w" , err )
92+ }
93+ if count == 0 {
94+ if _ , err := d .db .Exec ("INSERT INTO task_counter (last_id) VALUES (0)" ); err != nil {
95+ return fmt .Errorf ("failed to initialize counter: %w" , err )
96+ }
97+ }
98+
7499 return nil
75100}
76101
77102// InsertEvent adds an event to the database
78103func (d * DB ) InsertEvent (e Event ) error {
79104 query := `
80- INSERT INTO events (id, ts, actor, role, kind, payload, ctx, repo_uuid, branch, commit_sha, jj_op_id)
81- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
105+ INSERT INTO events (id, ts, created_at, actor, role, kind, payload, ctx, repo_uuid, branch, commit_sha, jj_op_id)
106+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
82107 `
83108
84- _ , err := d .db .Exec (query , e .ID , e .TS , e .Actor , e .Role , e .Kind , e .Payload , e .Ctx , e .RepoUUID , e .Branch , e .Commit , e .JJOpID )
109+ _ , err := d .db .Exec (query , e .ID , e .TS , e .CreatedAt . UnixNano (), e . Actor , e .Role , e .Kind , e .Payload , e .Ctx , e .RepoUUID , e .Branch , e .Commit , e .JJOpID )
85110 if err != nil {
86111 return fmt .Errorf ("failed to insert event: %w" , err )
87112 }
@@ -91,7 +116,7 @@ func (d *DB) InsertEvent(e Event) error {
91116
92117// GetEvents retrieves all events in chronological order
93118func (d * DB ) GetEvents () ([]Event , error ) {
94- query := `SELECT id, ts, actor, role, kind, payload, ctx, repo_uuid, branch, commit_sha, jj_op_id
119+ query := `SELECT id, ts, created_at, actor, role, kind, payload, ctx, repo_uuid, branch, commit_sha, jj_op_id
95120 FROM events ORDER BY ts, id`
96121
97122 rows , err := d .db .Query (query )
@@ -104,12 +129,14 @@ func (d *DB) GetEvents() ([]Event, error) {
104129 for rows .Next () {
105130 var e Event
106131 var ctx , repoUUID , branch , commit , jjOpID sql.NullString
132+ var createdAtNano int64
107133
108- err := rows .Scan (& e .ID , & e .TS , & e .Actor , & e .Role , & e .Kind , & e .Payload , & ctx , & repoUUID , & branch , & commit , & jjOpID )
134+ err := rows .Scan (& e .ID , & e .TS , & createdAtNano , & e .Actor , & e .Role , & e .Kind , & e .Payload , & ctx , & repoUUID , & branch , & commit , & jjOpID )
109135 if err != nil {
110136 return nil , fmt .Errorf ("failed to scan event row: %w" , err )
111137 }
112138
139+ e .CreatedAt = time .Unix (0 , createdAtNano )
113140 if ctx .Valid {
114141 e .Ctx = json .RawMessage (ctx .String )
115142 }
@@ -135,7 +162,7 @@ func (d *DB) GetEvents() ([]Event, error) {
135162// GetEventsByTaskID retrieves events for a specific task
136163func (d * DB ) GetEventsByTaskID (taskID string ) ([]Event , error ) {
137164 query := `
138- SELECT id, ts, actor, role, kind, payload, ctx, repo_uuid, branch, commit_sha, jj_op_id
165+ SELECT id, ts, created_at, actor, role, kind, payload, ctx, repo_uuid, branch, commit_sha, jj_op_id
139166 FROM events
140167 WHERE json_extract(payload, '$.task_id') = ?
141168 ORDER BY ts, id
@@ -151,12 +178,14 @@ func (d *DB) GetEventsByTaskID(taskID string) ([]Event, error) {
151178 for rows .Next () {
152179 var e Event
153180 var ctx , repoUUID , branch , commit , jjOpID sql.NullString
181+ var createdAtNano int64
154182
155- err := rows .Scan (& e .ID , & e .TS , & e .Actor , & e .Role , & e .Kind , & e .Payload , & ctx , & repoUUID , & branch , & commit , & jjOpID )
183+ err := rows .Scan (& e .ID , & e .TS , & createdAtNano , & e .Actor , & e .Role , & e .Kind , & e .Payload , & ctx , & repoUUID , & branch , & commit , & jjOpID )
156184 if err != nil {
157185 return nil , fmt .Errorf ("failed to scan event row: %w" , err )
158186 }
159187
188+ e .CreatedAt = time .Unix (0 , createdAtNano )
160189 if ctx .Valid {
161190 e .Ctx = json .RawMessage (ctx .String )
162191 }
@@ -200,3 +229,68 @@ func DBExists(path string) bool {
200229 _ , err := os .Stat (path )
201230 return err == nil
202231}
232+
233+ // GetOrCreateInstallationSuffix gets the installation suffix or creates one if it doesn't exist
234+ func (d * DB ) GetOrCreateInstallationSuffix () (string , error ) {
235+ // Try to get existing suffix
236+ var suffix string
237+ err := d .db .QueryRow ("SELECT value FROM metadata WHERE key = 'installation_suffix'" ).Scan (& suffix )
238+ if err == nil {
239+ return suffix , nil
240+ }
241+ if err != sql .ErrNoRows {
242+ return "" , fmt .Errorf ("failed to query installation suffix: %w" , err )
243+ }
244+
245+ // Generate new suffix (6 random alphanumeric characters)
246+ suffix = generateRandomSuffix (6 )
247+
248+ // Store it
249+ _ , err = d .db .Exec ("INSERT INTO metadata (key, value) VALUES ('installation_suffix', ?)" , suffix )
250+ if err != nil {
251+ return "" , fmt .Errorf ("failed to store installation suffix: %w" , err )
252+ }
253+
254+ return suffix , nil
255+ }
256+
257+ // GetNextTaskNumber gets the next task number and increments the counter
258+ func (d * DB ) GetNextTaskNumber () (int64 , error ) {
259+ tx , err := d .db .Begin ()
260+ if err != nil {
261+ return 0 , fmt .Errorf ("failed to begin transaction: %w" , err )
262+ }
263+ defer tx .Rollback ()
264+
265+ var lastID int64
266+ err = tx .QueryRow ("SELECT last_id FROM task_counter" ).Scan (& lastID )
267+ if err != nil {
268+ return 0 , fmt .Errorf ("failed to get last task ID: %w" , err )
269+ }
270+
271+ nextID := lastID + 1
272+ _ , err = tx .Exec ("UPDATE task_counter SET last_id = ?" , nextID )
273+ if err != nil {
274+ return 0 , fmt .Errorf ("failed to update task counter: %w" , err )
275+ }
276+
277+ if err := tx .Commit (); err != nil {
278+ return 0 , fmt .Errorf ("failed to commit transaction: %w" , err )
279+ }
280+
281+ return nextID , nil
282+ }
283+
284+ // generateRandomSuffix generates a random alphanumeric suffix
285+ func generateRandomSuffix (length int ) string {
286+ const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
287+ b := make ([]byte , length )
288+ for i := range b {
289+ n , err := rand .Int (rand .Reader , big .NewInt (int64 (len (charset ))))
290+ if err != nil {
291+ panic (err )
292+ }
293+ b [i ] = charset [n .Int64 ()]
294+ }
295+ return string (b )
296+ }
0 commit comments