package database import ( "log" "database/sql" _ "github.com/mattn/go-sqlite3" ) type Migrator func(*sql.DB) (*sql.DB, error) func MigrateUsers(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating users table") _, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, mail TEXT NOT NULL, username TEXT NOT NULL, display_name TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );`) if err != nil { return dbConn, err } log.Println("creating unique index on users table") _, err = dbConn.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users (username);`) if err != nil { return dbConn, err } return dbConn, nil } func MigrateApiKeys(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating api_keys table") _, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS api_keys ( key TEXT PRIMARY KEY, user_id INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE );`) if err != nil { return dbConn, err } return dbConn, nil } func MigrateDNSRecords(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating dns_records table") _, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS dns_records ( id TEXT PRIMARY KEY, user_id INTEGER NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL, content TEXT NOT NULL, ttl INTEGER NOT NULL, internal BOOLEAN NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE );`) if err != nil { return dbConn, err } _, err = dbConn.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_dns_records_name_content_type ON dns_records (name, type, content);`) if err != nil { return dbConn, err } return dbConn, nil } func MigrateDomainOwners(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating domain_owners table") _, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS domain_owners ( user_id INTEGER NOT NULL, domain TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE );`) if err != nil { return dbConn, err } _, err = dbConn.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_domain_owners_domain ON domain_owners (domain);`) if err != nil { return dbConn, err } return dbConn, nil } func MigrateUserSessions(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating user_sessions table") _, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS user_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, expire_at TIMESTAMP NOT NULL, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE );`) if err != nil { return dbConn, err } return dbConn, nil } func MigrateGuestBook(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating guest_book table") _, err := dbConn.Exec(`CREATE TABLE IF NOT EXISTS guest_book ( id TEXT PRIMARY KEY, name TEXT NOT NULL, message TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );`) if err != nil { return dbConn, err } _, err = dbConn.Exec(`CREATE INDEX IF NOT EXISTS idx_guest_book_created_at ON guest_book (created_at);`) return dbConn, nil } func MigrateProfiles(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating profiles columns") userColumns := map[string]bool{} row, err := dbConn.Query(`PRAGMA table_info(users);`) if err != nil { return dbConn, err } defer row.Close() for row.Next() { var columnName string row.Scan(nil, &columnName, nil, nil, nil, nil) userColumns[columnName] = true } columns := map[string]string{} columns["pronouns"] = "unspecified" columns["bio"] = "a computer hater" columns["location"] = "earth" columns["website"] = "https://hatecomputers.club" columns["avatar"] = "/static/img/default-avatar.png" for column, defaultValue := range columns { if userColumns[column] { continue } log.Println("migrating column", column) _, err = dbConn.Exec(`ALTER TABLE users ADD COLUMN ` + column + ` TEXT NOT NULL DEFAULT '` + defaultValue + `';`) } return dbConn, nil } func Migrate(dbConn *sql.DB) (*sql.DB, error) { log.Println("migrating database") migrations := []Migrator{ MigrateUsers, MigrateUserSessions, MigrateApiKeys, MigrateDomainOwners, MigrateDNSRecords, MigrateGuestBook, MigrateProfiles, } for _, migration := range migrations { dbConn, err := migration(dbConn) if err != nil { return dbConn, err } } return dbConn, nil }