yuki / okibi
焚き火の仲間だけの自前Gitホスティング(このサイト自身)
use crate::util::now;
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
use sqlx::ConnectOptions;
use std::str::FromStr;
pub type Db = SqlitePool;
const SCHEMA: &str = r#"
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
handle TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
display_name TEXT NOT NULL DEFAULT '',
is_admin INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL
);
-- atsm member allowlist: only these emails may sign in.
CREATE TABLE IF NOT EXISTS members (
email TEXT PRIMARY KEY,
display_name TEXT NOT NULL DEFAULT '',
is_admin INTEGER NOT NULL DEFAULT 0,
added_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS sessions (
token_hash TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS magic_links (
token_hash TEXT PRIMARY KEY,
email TEXT NOT NULL,
expires_at INTEGER NOT NULL,
used INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS pats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL DEFAULT '',
token_hash TEXT UNIQUE NOT NULL,
created_at INTEGER NOT NULL,
last_used INTEGER
);
CREATE TABLE IF NOT EXISTS repos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner_id INTEGER NOT NULL,
name TEXT NOT NULL,
visibility TEXT NOT NULL DEFAULT 'private',
default_branch TEXT NOT NULL DEFAULT 'main',
description TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
UNIQUE(owner_id, name)
);
CREATE TABLE IF NOT EXISTS ci_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repo_id INTEGER NOT NULL,
sha TEXT NOT NULL,
ref_name TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'queued',
log TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
finished_at INTEGER
);
"#;
pub async fn init(db_path: &std::path::Path, seed_members: &[String]) -> anyhow::Result<Db> {
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent).ok();
}
let url = format!("sqlite://{}", db_path.display());
let opts = sqlx::sqlite::SqliteConnectOptions::from_str(&url)?
.create_if_missing(true)
.log_statements(tracing::log::LevelFilter::Debug);
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect_with(opts)
.await?;
for stmt in SCHEMA.split(';') {
let s = stmt.trim();
if !s.is_empty() {
sqlx::query(s).execute(&pool).await?;
}
}
// Seed allowlist members (admins).
for email in seed_members {
sqlx::query(
"INSERT INTO members (email, display_name, is_admin, added_at)
VALUES (?, '', 1, ?)
ON CONFLICT(email) DO UPDATE SET is_admin = 1",
)
.bind(email)
.bind(now())
.execute(&pool)
.await?;
}
Ok(pool)
}