huskies: merge 1061
This commit is contained in:
@@ -107,6 +107,115 @@ pub(crate) async fn open_project_root(
|
||||
}
|
||||
}
|
||||
|
||||
/// One-shot migration: read existing JSON store files and insert their rows
|
||||
/// into the SQLite tables created by the migration scripts, then rename the
|
||||
/// originals to `*.migrated` so this function is a no-op on the next startup.
|
||||
///
|
||||
/// Runs after `db::init()` so the tables already exist and the shared pool is
|
||||
/// available. Errors are logged but never fatal.
|
||||
async fn migrate_json_stores_to_sqlite(huskies_dir: &Path) {
|
||||
let pool = match crate::db::get_shared_pool() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
crate::slog!("[db-migrate] Shared pool not available; skipping JSON migration");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// ── event_triggers.json ───────────────────────────────────────────────────
|
||||
let et_path = huskies_dir.join("event_triggers.json");
|
||||
if et_path.exists() {
|
||||
match std::fs::read_to_string(&et_path) {
|
||||
Ok(s) => {
|
||||
let triggers: Vec<crate::service::event_triggers::EventTrigger> =
|
||||
serde_json::from_str(&s).unwrap_or_default();
|
||||
for t in triggers {
|
||||
let predicate_json = serde_json::to_string(&t.predicate).unwrap_or_default();
|
||||
let action_json = serde_json::to_string(&t.action).unwrap_or_default();
|
||||
let mode = match t.mode {
|
||||
crate::service::event_triggers::FireMode::Once => "once",
|
||||
crate::service::event_triggers::FireMode::Persistent => "persistent",
|
||||
};
|
||||
let created_at = t.created_at.to_rfc3339();
|
||||
let _ = sqlx::query(
|
||||
"INSERT OR IGNORE INTO event_triggers \
|
||||
(id, predicate_json, action_json, mode, created_at) \
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
)
|
||||
.bind(&t.id)
|
||||
.bind(&predicate_json)
|
||||
.bind(&action_json)
|
||||
.bind(mode)
|
||||
.bind(&created_at)
|
||||
.execute(pool)
|
||||
.await;
|
||||
}
|
||||
let migrated = huskies_dir.join("event_triggers.json.migrated");
|
||||
let _ = std::fs::rename(&et_path, &migrated);
|
||||
crate::slog!("[db-migrate] Migrated event_triggers.json → SQLite");
|
||||
}
|
||||
Err(e) => crate::slog!("[db-migrate] Could not read event_triggers.json: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// ── timers.json ───────────────────────────────────────────────────────────
|
||||
let timers_path = huskies_dir.join("timers.json");
|
||||
if timers_path.exists() {
|
||||
match std::fs::read_to_string(&timers_path) {
|
||||
Ok(s) => {
|
||||
let entries: Vec<crate::service::timer::TimerEntry> =
|
||||
serde_json::from_str(&s).unwrap_or_default();
|
||||
for e in entries {
|
||||
let _ = sqlx::query(
|
||||
"INSERT OR IGNORE INTO timers (story_id, scheduled_at) \
|
||||
VALUES (?1, ?2)",
|
||||
)
|
||||
.bind(&e.story_id)
|
||||
.bind(e.scheduled_at.to_rfc3339())
|
||||
.execute(pool)
|
||||
.await;
|
||||
}
|
||||
let migrated = huskies_dir.join("timers.json.migrated");
|
||||
let _ = std::fs::rename(&timers_path, &migrated);
|
||||
crate::slog!("[db-migrate] Migrated timers.json → SQLite");
|
||||
}
|
||||
Err(e) => crate::slog!("[db-migrate] Could not read timers.json: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// ── scheduled_timers.json ─────────────────────────────────────────────────
|
||||
let st_path = huskies_dir.join("scheduled_timers.json");
|
||||
if st_path.exists() {
|
||||
match std::fs::read_to_string(&st_path) {
|
||||
Ok(s) => {
|
||||
use crate::service::timer::scheduled::ScheduledTimer;
|
||||
let entries: Vec<ScheduledTimer> = serde_json::from_str(&s).unwrap_or_default();
|
||||
for t in entries {
|
||||
let action_json = serde_json::to_string(&t.action).unwrap_or_default();
|
||||
let mode_json = serde_json::to_string(&t.mode).unwrap_or_default();
|
||||
let _ = sqlx::query(
|
||||
"INSERT OR IGNORE INTO scheduled_timers \
|
||||
(id, label, fire_at, action_json, mode_json, created_at) \
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
)
|
||||
.bind(&t.id)
|
||||
.bind(&t.label)
|
||||
.bind(t.fire_at.to_rfc3339())
|
||||
.bind(&action_json)
|
||||
.bind(&mode_json)
|
||||
.bind(t.created_at.to_rfc3339())
|
||||
.execute(pool)
|
||||
.await;
|
||||
}
|
||||
let migrated = huskies_dir.join("scheduled_timers.json.migrated");
|
||||
let _ = std::fs::rename(&st_path, &migrated);
|
||||
crate::slog!("[db-migrate] Migrated scheduled_timers.json → SQLite");
|
||||
}
|
||||
Err(e) => crate::slog!("[db-migrate] Could not read scheduled_timers.json: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up the server log file, node identity keypair, pipeline DB, and CRDT state.
|
||||
pub(crate) async fn init_subsystems(app_state: &Arc<SessionState>, cwd: &Path) {
|
||||
// Enable persistent server log file now that the project root is known.
|
||||
@@ -146,6 +255,10 @@ pub(crate) async fn init_subsystems(app_state: &Arc<SessionState>, cwd: &Path) {
|
||||
if let Some(ref db_path) = pipeline_db_path {
|
||||
if let Err(e) = db::init(db_path).await {
|
||||
crate::slog!("[db] Failed to initialise pipeline.db: {e}");
|
||||
} else {
|
||||
// One-shot migration: move any existing JSON store files into SQLite.
|
||||
let huskies_dir = db_path.parent().unwrap_or(db_path);
|
||||
migrate_json_stores_to_sqlite(huskies_dir).await;
|
||||
}
|
||||
if let Err(e) = crdt_state::init(db_path).await {
|
||||
crate::slog!("[crdt] Failed to initialise CRDT state layer: {e}");
|
||||
|
||||
Reference in New Issue
Block a user