internal/db/db.go
3.2 KB
| 1 | package db |
| 2 | |
| 3 | import ( |
| 4 | "database/sql" |
| 5 | "fmt" |
| 6 | |
| 7 | _ "modernc.org/sqlite" |
| 8 | ) |
| 9 | |
| 10 | var DB *sql.DB |
| 11 | |
| 12 | func Init(dbPath string) error { |
| 13 | var err error |
| 14 | DB, err = sql.Open("sqlite", dbPath) |
| 15 | if err != nil { |
| 16 | return fmt.Errorf("open db: %w", err) |
| 17 | } |
| 18 | |
| 19 | DB.SetMaxOpenConns(1) |
| 20 | DB.SetMaxIdleConns(1) |
| 21 | |
| 22 | if err := DB.Ping(); err != nil { |
| 23 | return fmt.Errorf("ping db: %w", err) |
| 24 | } |
| 25 | |
| 26 | for _, pragma := range []string{ |
| 27 | `PRAGMA busy_timeout = 5000`, |
| 28 | `PRAGMA journal_mode = WAL`, |
| 29 | `PRAGMA foreign_keys = ON`, |
| 30 | } { |
| 31 | if _, err := DB.Exec(pragma); err != nil { |
| 32 | return fmt.Errorf("%s: %w", pragma, err) |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | if err := createTables(); err != nil { |
| 37 | return fmt.Errorf("create tables: %w", err) |
| 38 | } |
| 39 | |
| 40 | if err := migrateCloneURLs(); err != nil { |
| 41 | return fmt.Errorf("migrate clone urls: %w", err) |
| 42 | } |
| 43 | |
| 44 | // После перезапуска зависшие сборки некому завершить — помечаем их failed. |
| 45 | if _, err := DB.Exec( |
| 46 | `UPDATE builds SET status = 'failed', finished_at = CURRENT_TIMESTAMP WHERE status = 'running'`, |
| 47 | ); err != nil { |
| 48 | return fmt.Errorf("reset running builds: %w", err) |
| 49 | } |
| 50 | |
| 51 | return nil |
| 52 | } |
| 53 | |
| 54 | func createTables() error { |
| 55 | schema := ` |
| 56 | CREATE TABLE IF NOT EXISTS users ( |
| 57 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 58 | username TEXT UNIQUE NOT NULL, |
| 59 | email TEXT UNIQUE NOT NULL, |
| 60 | password_hash TEXT NOT NULL, |
| 61 | avatar_url TEXT DEFAULT '', |
| 62 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| 63 | ); |
| 64 | |
| 65 | CREATE TABLE IF NOT EXISTS repositories ( |
| 66 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 67 | name TEXT NOT NULL, |
| 68 | owner_id INTEGER NOT NULL REFERENCES users(id), |
| 69 | description TEXT DEFAULT '', |
| 70 | is_private BOOLEAN DEFAULT 0, |
| 71 | is_archived BOOLEAN DEFAULT 0, |
| 72 | clone_url TEXT NOT NULL, |
| 73 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| 74 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| 75 | UNIQUE(name, owner_id) |
| 76 | ); |
| 77 | |
| 78 | CREATE TABLE IF NOT EXISTS builds ( |
| 79 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 80 | repo_id INTEGER NOT NULL REFERENCES repositories(id), |
| 81 | branch TEXT DEFAULT 'main', |
| 82 | build_type TEXT NOT NULL, |
| 83 | status TEXT NOT NULL DEFAULT 'pending', |
| 84 | log_file TEXT DEFAULT '', |
| 85 | started_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| 86 | finished_at DATETIME, |
| 87 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP |
| 88 | ); |
| 89 | |
| 90 | CREATE TABLE IF NOT EXISTS releases ( |
| 91 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 92 | repo_id INTEGER NOT NULL REFERENCES repositories(id), |
| 93 | tag TEXT NOT NULL, |
| 94 | title TEXT NOT NULL, |
| 95 | body TEXT DEFAULT '', |
| 96 | is_prerelease BOOLEAN DEFAULT 0, |
| 97 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| 98 | UNIQUE(repo_id, tag) |
| 99 | ); |
| 100 | |
| 101 | CREATE INDEX IF NOT EXISTS idx_repos_owner ON repositories(owner_id); |
| 102 | CREATE INDEX IF NOT EXISTS idx_builds_repo ON builds(repo_id); |
| 103 | CREATE INDEX IF NOT EXISTS idx_builds_status ON builds(status); |
| 104 | CREATE INDEX IF NOT EXISTS idx_releases_repo ON releases(repo_id); |
| 105 | ` |
| 106 | |
| 107 | _, err := DB.Exec(schema) |
| 108 | return err |
| 109 | } |
| 110 | |
| 111 | // migrateCloneURLs переводит старые имена вида "owner--repo" в "owner/repo" |
| 112 | // (git-URL теперь /owner/repo.git вместо owner--repo.git). |
| 113 | func migrateCloneURLs() error { |
| 114 | _, err := DB.Exec( |
| 115 | `UPDATE repositories |
| 116 | SET clone_url = substr(clone_url, 1, instr(clone_url, '--') - 1) |
| 117 | || '/' || substr(clone_url, instr(clone_url, '--') + 2) |
| 118 | WHERE instr(clone_url, '--') > 0 AND instr(clone_url, '/') = 0`, |
| 119 | ) |
| 120 | return err |
| 121 | } |