From bd937d8472e9b45262ee492e5cdd5801e0023751 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Thu, 16 Apr 2026 00:00:00 +0100 Subject: [PATCH] Fix: add github_config column and update protocol CHECK constraint The SQLite schema had CHECK(protocol IN ('ssh','telnet','raw')) which rejected GitHub profiles. Added migration 002 that recreates the profiles table with the 'github' value in the constraint and a github_config column. Updated all storage queries (CREATE, UPDATE, SELECT) and both scan functions to include the github_config column. --- .../migrations/002_github_protocol.sql | 59 +++++++++++++++++++ internal/storage/store.go | 56 +++++++++++------- 2 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 internal/storage/migrations/002_github_protocol.sql diff --git a/internal/storage/migrations/002_github_protocol.sql b/internal/storage/migrations/002_github_protocol.sql new file mode 100644 index 0000000..c13e807 --- /dev/null +++ b/internal/storage/migrations/002_github_protocol.sql @@ -0,0 +1,59 @@ +-- Migration 002: Add GitHub protocol support +-- Adds the 'github' value to the protocol CHECK constraint and a github_config column. + +-- SQLite does not support ALTER TABLE to modify CHECK constraints or add columns +-- with complex defaults atomically. We recreate the profiles table. + +-- Step 1: Create the new table with the updated constraint. +CREATE TABLE IF NOT EXISTS profiles_new ( + id TEXT PRIMARY KEY, + display_name TEXT NOT NULL, + description TEXT DEFAULT '', + protocol TEXT NOT NULL CHECK(protocol IN ('ssh', 'telnet', 'raw', 'github')), + host TEXT NOT NULL, + port INTEGER NOT NULL, + username TEXT DEFAULT '', + tags TEXT DEFAULT '[]', + folder TEXT DEFAULT '', + environment TEXT DEFAULT '', + notes TEXT DEFAULT '', + favourite INTEGER DEFAULT 0, + last_connected TEXT, + connect_timeout INTEGER DEFAULT 30, + read_timeout INTEGER DEFAULT 0, + terminal_font_family TEXT DEFAULT '', + terminal_font_size INTEGER DEFAULT 14, + terminal_scrollback INTEGER DEFAULT 10000, + terminal_cursor_style TEXT DEFAULT 'block', + ssh_config TEXT, + telnet_config TEXT, + raw_config TEXT, + github_config TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +-- Step 2: Copy existing data (github_config will be NULL for legacy rows). +INSERT OR IGNORE INTO profiles_new ( + id, display_name, description, protocol, host, port, username, + tags, folder, environment, notes, favourite, last_connected, + connect_timeout, read_timeout, + terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, + ssh_config, telnet_config, raw_config, github_config, + created_at, updated_at +) +SELECT + id, display_name, description, protocol, host, port, username, + tags, folder, environment, notes, favourite, last_connected, + connect_timeout, read_timeout, + terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, + ssh_config, telnet_config, raw_config, NULL, + created_at, updated_at +FROM profiles; + +-- Step 3: Drop old table and rename. +DROP TABLE profiles; +ALTER TABLE profiles_new RENAME TO profiles; + +-- Step 4: Record the migration version. +INSERT INTO schema_version (version, applied_at) VALUES (2, datetime('now')); diff --git a/internal/storage/store.go b/internal/storage/store.go index 9386094..02f7749 100644 --- a/internal/storage/store.go +++ b/internal/storage/store.go @@ -109,15 +109,16 @@ func (s *Store) CreateProfile(p *models.Profile) error { sshJSON, _ := json.Marshal(p.SSH) telnetJSON, _ := json.Marshal(p.Telnet) rawJSON, _ := json.Marshal(p.RAW) + githubJSON, _ := json.Marshal(p.GitHub) _, err := s.db.Exec(`INSERT INTO profiles ( id, display_name, description, protocol, host, port, username, tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, p.ID, p.DisplayName, p.Description, string(p.Protocol), p.Host, p.Port, p.Username, string(tagsJSON), p.Folder, p.Environment, p.Notes, @@ -125,6 +126,7 @@ func (s *Store) CreateProfile(p *models.Profile) error { p.ConnectTimeout, p.ReadTimeout, p.TerminalFontFamily, p.TerminalFontSize, p.TerminalScrollback, p.TerminalCursorStyle, nullableJSON(p.SSH, sshJSON), nullableJSON(p.Telnet, telnetJSON), nullableJSON(p.RAW, rawJSON), + nullableJSON(p.GitHub, githubJSON), p.CreatedAt.Format(time.RFC3339), p.UpdatedAt.Format(time.RFC3339), ) if err != nil { @@ -140,7 +142,7 @@ func (s *Store) GetProfile(id string) (*models.Profile, error) { tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles WHERE id = ?`, id) @@ -160,13 +162,14 @@ func (s *Store) UpdateProfile(p *models.Profile) error { sshJSON, _ := json.Marshal(p.SSH) telnetJSON, _ := json.Marshal(p.Telnet) rawJSON, _ := json.Marshal(p.RAW) + githubJSON, _ := json.Marshal(p.GitHub) res, err := s.db.Exec(`UPDATE profiles SET display_name=?, description=?, protocol=?, host=?, port=?, username=?, tags=?, folder=?, environment=?, notes=?, favourite=?, last_connected=?, connect_timeout=?, read_timeout=?, terminal_font_family=?, terminal_font_size=?, terminal_scrollback=?, terminal_cursor_style=?, - ssh_config=?, telnet_config=?, raw_config=?, + ssh_config=?, telnet_config=?, raw_config=?, github_config=?, updated_at=? WHERE id=?`, p.DisplayName, p.Description, string(p.Protocol), @@ -176,6 +179,7 @@ func (s *Store) UpdateProfile(p *models.Profile) error { p.ConnectTimeout, p.ReadTimeout, p.TerminalFontFamily, p.TerminalFontSize, p.TerminalScrollback, p.TerminalCursorStyle, nullableJSON(p.SSH, sshJSON), nullableJSON(p.Telnet, telnetJSON), nullableJSON(p.RAW, rawJSON), + nullableJSON(p.GitHub, githubJSON), p.UpdatedAt.Format(time.RFC3339), p.ID, ) @@ -209,7 +213,7 @@ func (s *Store) ListProfiles() ([]*models.Profile, error) { tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles ORDER BY display_name`) if err != nil { @@ -228,7 +232,7 @@ func (s *Store) SearchProfiles(query string) ([]*models.Profile, error) { tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles WHERE display_name LIKE ? OR host LIKE ? OR tags LIKE ? @@ -247,7 +251,7 @@ func (s *Store) ListProfilesByProtocol(protocol string) ([]*models.Profile, erro tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles WHERE protocol = ? ORDER BY display_name`, protocol) if err != nil { @@ -265,7 +269,7 @@ func (s *Store) ListProfilesByTag(tag string) ([]*models.Profile, error) { tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles WHERE tags LIKE ? ORDER BY display_name`, q) if err != nil { @@ -282,7 +286,7 @@ func (s *Store) ListProfilesByFolder(folder string) ([]*models.Profile, error) { tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles WHERE folder = ? ORDER BY display_name`, folder) if err != nil { @@ -299,7 +303,7 @@ func (s *Store) ListFavourites() ([]*models.Profile, error) { tags, folder, environment, notes, favourite, last_connected, connect_timeout, read_timeout, terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, + ssh_config, telnet_config, raw_config, github_config, created_at, updated_at FROM profiles WHERE favourite = 1 ORDER BY display_name`) if err != nil { @@ -561,11 +565,11 @@ func (s *Store) SavePreferences(prefs *models.AppPreferences) error { func scanProfile(row *sql.Row) (*models.Profile, error) { p := &models.Profile{} var ( - protocol, tagsJSON string - sshJSON, telnetJSON, rawJSON sql.NullString - lastConn sql.NullString - favourite int - createdAt, updatedAt string + protocol, tagsJSON string + sshJSON, telnetJSON, rawJSON, githubJSON sql.NullString + lastConn sql.NullString + favourite int + createdAt, updatedAt string ) err := row.Scan( @@ -575,7 +579,7 @@ func scanProfile(row *sql.Row) (*models.Profile, error) { &favourite, &lastConn, &p.ConnectTimeout, &p.ReadTimeout, &p.TerminalFontFamily, &p.TerminalFontSize, &p.TerminalScrollback, &p.TerminalCursorStyle, - &sshJSON, &telnetJSON, &rawJSON, + &sshJSON, &telnetJSON, &rawJSON, &githubJSON, &createdAt, &updatedAt, ) if err != nil { @@ -610,6 +614,10 @@ func scanProfile(row *sql.Row) (*models.Profile, error) { p.RAW = &models.RAWConfig{} _ = json.Unmarshal([]byte(rawJSON.String), p.RAW) } + if githubJSON.Valid && githubJSON.String != "" { + p.GitHub = &models.GitHubConfig{} + _ = json.Unmarshal([]byte(githubJSON.String), p.GitHub) + } return p, nil } @@ -620,11 +628,11 @@ func scanProfiles(rows *sql.Rows) ([]*models.Profile, error) { for rows.Next() { p := &models.Profile{} var ( - protocol, tagsJSON string - sshJSON, telnetJSON, rawJSON sql.NullString - lastConn sql.NullString - favourite int - createdAt, updatedAt string + protocol, tagsJSON string + sshJSON, telnetJSON, rawJSON, githubJSON sql.NullString + lastConn sql.NullString + favourite int + createdAt, updatedAt string ) err := rows.Scan( @@ -634,7 +642,7 @@ func scanProfiles(rows *sql.Rows) ([]*models.Profile, error) { &favourite, &lastConn, &p.ConnectTimeout, &p.ReadTimeout, &p.TerminalFontFamily, &p.TerminalFontSize, &p.TerminalScrollback, &p.TerminalCursorStyle, - &sshJSON, &telnetJSON, &rawJSON, + &sshJSON, &telnetJSON, &rawJSON, &githubJSON, &createdAt, &updatedAt, ) if err != nil { @@ -669,6 +677,10 @@ func scanProfiles(rows *sql.Rows) ([]*models.Profile, error) { p.RAW = &models.RAWConfig{} _ = json.Unmarshal([]byte(rawJSON.String), p.RAW) } + if githubJSON.Valid && githubJSON.String != "" { + p.GitHub = &models.GitHubConfig{} + _ = json.Unmarshal([]byte(githubJSON.String), p.GitHub) + } result = append(result, p) }