Skip to content
quarry Artifact [dfb6e58445]
Login

Artifact [dfb6e58445]

Artifact dfb6e58445137e65bb7db930ba810dd89d75d2f3a8c3aacb61f88aef6900a71d:


// Package oltp is quarry's forge OLTP database (0001 §8): the only
// authoritative non-DAG state. v1 holds sync users — per-repo login
// credentials for push gating. Small by construction; the litestream
// replica is the recovery story when deployment warrants it.
package oltp

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/ncruces/go-sqlite3/driver"
)

const schema = `
CREATE TABLE IF NOT EXISTS sync_user (
	repo   TEXT NOT NULL,
	user   TEXT NOT NULL,
	secret TEXT NOT NULL, -- RFC §6.3 shared secret (40 hex); plaintext never stored
	PRIMARY KEY (repo, user)
);`

type DB struct {
	db *sql.DB
}

func Open(path string) (*DB, error) {
	db, err := driver.Open("file:" + path)
	if err != nil {
		return nil, err
	}
	if _, err := db.Exec(`PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;`); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: pragmas: %w", err)
	}
	if _, err := db.Exec(schema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: schema: %w", err)
	}
	if _, err := db.Exec(accountSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: account schema: %w", err)
	}
	if _, err := db.Exec(webhookSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: webhook schema: %w", err)
	}
	if _, err := db.Exec(sessionSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: session schema: %w", err)
	}
	if _, err := db.Exec(ciSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: ci schema: %w", err)
	}
	if _, err := db.Exec(mirrorSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: mirror schema: %w", err)
	}
	if _, err := db.Exec(repoSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: repo schema: %w", err)
	}
	if _, err := db.Exec(voteSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: vote schema: %w", err)
	}
	if _, err := db.Exec(categorySchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: category schema: %w", err)
	}
	if _, err := db.Exec(pollSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: poll schema: %w", err)
	}
	if _, err := db.Exec(moderationSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: moderation schema: %w", err)
	}
	if _, err := db.Exec(subsSchema); err != nil {
		db.Close()
		return nil, fmt.Errorf("oltp: subs schema: %w", err)
	}
	for _, alter := range append(repoMigrations, mirrorMigrations...) {
		if _, err := db.Exec(alter); err != nil && !strings.Contains(err.Error(), "duplicate column") {
			db.Close()
			return nil, fmt.Errorf("oltp: migrate: %w", err)
		}
	}
	return &DB{db: db}, nil
}

func (d *DB) Close() error { return d.db.Close() }

// SetSyncUser stores (replacing) one repo user's shared secret.
func (d *DB) SetSyncUser(repo, user, secret string) error {
	_, err := d.db.Exec(`INSERT OR REPLACE INTO sync_user (repo, user, secret)
		VALUES (?, ?, ?)`, repo, user, secret)
	return err
}

// Secret returns the stored shared secret for one repo user.
func (d *DB) Secret(repo, user string) (string, bool) {
	var s string
	err := d.db.QueryRow(`SELECT secret FROM sync_user WHERE repo = ? AND user = ?`,
		repo, user).Scan(&s)
	return s, err == nil
}

// Gated reports whether a repo has any sync users — the v1 policy
// switch: no users means anonymous push stays allowed (local use),
// one or more users means pushes must authenticate.
func (d *DB) Gated(repo string) bool {
	var n int
	if err := d.db.QueryRow(`SELECT count(*) FROM sync_user WHERE repo = ?`, repo).Scan(&n); err != nil {
		return false
	}
	return n > 0
}