package main import ( "database/sql" "log" "time" _ "github.com/mattn/go-sqlite3" ) var db *sql.DB func initDB(path string) { var err error db, err = sql.Open("sqlite3", path+"?_journal=WAL&_busy_timeout=5000") if err != nil { log.Fatalf("db open: %v", err) } migrations := []string{ `CREATE TABLE IF NOT EXISTS accounts ( email TEXT PRIMARY KEY, stripe_id TEXT DEFAULT '', created_at TEXT NOT NULL DEFAULT (datetime('now')) )`, `CREATE TABLE IF NOT EXISTS vaults ( vault_id TEXT PRIMARY KEY, account_email TEXT NOT NULL REFERENCES accounts(email), region TEXT NOT NULL, expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')) )`, `CREATE TABLE IF NOT EXISTS sessions ( token TEXT PRIMARY KEY, email TEXT NOT NULL REFERENCES accounts(email), expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')) )`, `CREATE TABLE IF NOT EXISTS login_codes ( email TEXT PRIMARY KEY, code TEXT NOT NULL, expires_at TEXT NOT NULL )`, } for _, m := range migrations { if _, err := db.Exec(m); err != nil { log.Fatalf("migration: %v", err) } } log.Println(" database ready") } // Accounts func accountGet(email string) (bool, string, error) { var stripeID string err := db.QueryRow("SELECT stripe_id FROM accounts WHERE email = ?", email).Scan(&stripeID) if err == sql.ErrNoRows { return false, "", nil } if err != nil { return false, "", err } return true, stripeID, nil } func accountCreate(email string) error { _, err := db.Exec("INSERT OR IGNORE INTO accounts (email) VALUES (?)", email) return err } // Vaults func vaultList(email string) ([]Vault, error) { rows, err := db.Query( "SELECT vault_id, region, expires_at, created_at FROM vaults WHERE account_email = ? ORDER BY created_at", email, ) if err != nil { return nil, err } defer rows.Close() var vaults []Vault for rows.Next() { var v Vault if err := rows.Scan(&v.ID, &v.Region, &v.ExpiresAt, &v.CreatedAt); err != nil { return nil, err } vaults = append(vaults, v) } return vaults, nil } func vaultCount(email string) (int, error) { var n int err := db.QueryRow("SELECT COUNT(*) FROM vaults WHERE account_email = ?", email).Scan(&n) return n, err } func vaultCreate(id, email, region string) error { expires := time.Now().AddDate(1, 0, 0).UTC().Format(time.RFC3339) _, err := db.Exec( "INSERT INTO vaults (vault_id, account_email, region, expires_at) VALUES (?, ?, ?, ?)", id, email, region, expires, ) return err } func vaultDelete(id, email string) error { res, err := db.Exec("DELETE FROM vaults WHERE vault_id = ? AND account_email = ?", id, email) if err != nil { return err } n, _ := res.RowsAffected() if n == 0 { return sql.ErrNoRows } return nil } // Sessions func sessionCreate(email string) (string, error) { token := randomToken(32) expires := time.Now().Add(24 * time.Hour).UTC().Format(time.RFC3339) _, err := db.Exec( "INSERT INTO sessions (token, email, expires_at) VALUES (?, ?, ?)", token, email, expires, ) return token, err } func sessionGet(token string) (string, error) { var email string var expires string err := db.QueryRow("SELECT email, expires_at FROM sessions WHERE token = ?", token).Scan(&email, &expires) if err != nil { return "", err } t, _ := time.Parse(time.RFC3339, expires) if time.Now().After(t) { db.Exec("DELETE FROM sessions WHERE token = ?", token) return "", sql.ErrNoRows } return email, nil } func sessionDelete(token string) { db.Exec("DELETE FROM sessions WHERE token = ?", token) } // Login codes func loginCodeSet(email, code string) error { expires := time.Now().Add(10 * time.Minute).UTC().Format(time.RFC3339) _, err := db.Exec( "INSERT OR REPLACE INTO login_codes (email, code, expires_at) VALUES (?, ?, ?)", email, code, expires, ) return err } func loginCodeVerify(email, code string) (bool, error) { var stored string var expires string err := db.QueryRow("SELECT code, expires_at FROM login_codes WHERE email = ?", email).Scan(&stored, &expires) if err != nil { return false, nil } t, _ := time.Parse(time.RFC3339, expires) if time.Now().After(t) { db.Exec("DELETE FROM login_codes WHERE email = ?", email) return false, nil } if stored != code { return false, nil } db.Exec("DELETE FROM login_codes WHERE email = ?", email) return true, nil } // Types type Vault struct { ID string `json:"vault_id"` Region string `json:"region"` ExpiresAt string `json:"expires_at"` CreatedAt string `json:"created_at"` }