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) }