~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

grain-serve import reads hatk's grain.db into a new index

Merged chadtmiller.com asked to merge into feature/rust from grain.social/grain:index/import

Only what this pull adds. It is built on grain runs on a Rust server modelled on knotless, without hatk.

crates/grain-serve/src/index/import/tests.rsnew+786−0
786 lines changed: not loaded until asked for.
crates/grain-serve/src/index/import.rsnew+698−0
698 lines changed: not loaded until asked for.
crates/grain-serve/src/index/mod.rs+1−0
@@ -14,6 +14,7 @@
//! leaves alone what the index has from a later one ([`Applied::Stale`]).
pub mod backfill;
pub mod import;
pub mod records;
pub mod schema;
crates/grain-serve/src/main.rs+8−0
@@ -3,6 +3,9 @@
//! set and fed from `GRAIN_JETSTREAM` when that is, and taking service
//! tokens for the DID of `GRAIN_PUBLIC_URL` when set
//! ([`grain_serve::Settings`]).
//!
//! `grain-serve import <hatk's grain.db>` reads hatk's database into a new
//! index, at `GRAIN_INDEX` or `--into` ([`grain_serve::index::import`]).
use anyhow::Context;
use grain::env::{Ctx, Env};
@@ -10,6 +13,11 @@ use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args: Vec<String> = std::env::args().collect();
if args.get(1).is_some_and(|a| a == "import") {
let var = |name: &str| std::env::var(name).ok();
std::process::exit(grain_serve::index::import::main(&args[2..], var));
}
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
crates/grain-serve/tests/main.rs+43−1
@@ -1,6 +1,7 @@
//! The `grain-serve` binary: it serves where `GRAIN_LISTEN` says, keeps its
//! index where `GRAIN_INDEX` says, stops
//! when told to, and says why when it cannot listen.
//! when told to, says why when it cannot listen, and imports hatk's
//! database with `import`.
use std::process::{Command, Stdio};
use std::time::Duration;
@@ -70,3 +71,44 @@ fn says_it_cannot_follow_jetstream_without_an_index() {
let said = String::from_utf8_lossy(&out.stderr);
assert!(said.contains("GRAIN_JETSTREAM needs GRAIN_INDEX"), "{said}");
}
#[test]
fn imports_hatks_database_into_its_index() {
let dir = tempfile::tempdir().unwrap();
let old = dir.path().join("grain.db");
let db = rusqlite::Connection::open(&old).unwrap();
db.execute_batch(
"CREATE TABLE _repos (did TEXT PRIMARY KEY, status TEXT NOT NULL DEFAULT 'pending',
handle TEXT, backfilled_at TEXT, rev TEXT, retry_count INTEGER NOT NULL DEFAULT 0,
retry_after INTEGER NOT NULL DEFAULT 0);
INSERT INTO _repos (did, status) VALUES ('did:plc:alice', 'takendown');",
)
.unwrap();
drop(db);
let index = dir.path().join("index.db");
let out = Command::new(env!("CARGO_BIN_EXE_grain-serve"))
.arg("import")
.arg(&old)
.env("GRAIN_INDEX", &index)
.output()
.unwrap();
let said = String::from_utf8_lossy(&out.stdout);
assert!(out.status.success(), "{said}");
assert!(
said.contains("takedown (_repos, status takendown)"),
"{said}"
);
assert!(said.ends_with("written\n"), "{said}");
let held = rusqlite::Connection::open(&index).unwrap();
let did: String = held
.query_row("SELECT did FROM takedown", [], |r| r.get(0))
.unwrap();
assert_eq!(did, "did:plc:alice");
let usage = Command::new(env!("CARGO_BIN_EXE_grain-serve"))
.arg("import")
.output()
.unwrap();
assert_eq!(usage.status.code(), Some(2));
assert!(String::from_utf8_lossy(&usage.stderr).starts_with("usage: grain-serve import"));
}
crates/grain-serve/Cargo.toml+1−0
@@ -10,6 +10,7 @@ rust-version.workspace = true
grain.workspace = true
anyhow.workspace = true
axum.workspace = true
base64.workspace = true
bytes.workspace = true
futures.workspace = true
reqwest.workspace = true
Cargo.lock+1−0
@@ -620,6 +620,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"base64 0.22.1",
"bytes",
"futures",
"grain",