AkurAI Build
Menu

Public repository / master

AkurAI-Notes / crates/notes-cli/src/cmd_seed.rs

Latest change 7f7b543549487ff8ff6802fb420feff1a197c507 - Checkpoint WIP + add AkurAI Build CI/CD pipeline by Γ“lafur BΓΊi Γ“lafsson

use akurai_json::Value;
use notes_api::auth::idp_client::IdpConfig;
use notes_api::router_setup::NotesState;

pub fn run(db_path: &str) -> Result<(), String> {
    println!("🌱 Seeding Notes database: {db_path}");

    let frontend_dir = std::path::PathBuf::from("site/frontend");
    let state = NotesState::new(db_path, frontend_dir, IdpConfig::from_env())?;
    let mut db = state.db.lock().map_err(|e| format!("lock: {e}"))?;

    let owner_sub = "olibuijr@olibuijr.com";

    let mut insert = |entity: &str, id: u64, fields: Vec<(&str, Value)>| -> Result<(), String> {
        use std::time::{SystemTime, UNIX_EPOCH};
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs() as i64;
        let mut obj = Vec::new();
        obj.push(("id".into(), Value::Int(id as i64)));
        obj.push(("createdAt".into(), Value::Int(now)));
        obj.push(("updatedAt".into(), Value::Int(now)));
        for (k, v) in fields {
            obj.push((k.to_string(), v));
        }
        let record = Value::Object(obj);
        let mut key = format!("{}:", entity).into_bytes();
        key.extend_from_slice(&id.to_be_bytes());
        let json = record.to_json();
        db.insert(&key, json.as_bytes())
            .map_err(|e| format!("insert {entity}:{id}: {e}"))
    };

    // Tags
    insert(
        "tags",
        1,
        vec![
            ("name", Value::Str("important".into())),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#ef4444".into())),
        ],
    )?;
    insert(
        "tags",
        2,
        vec![
            ("name", Value::Str("todo".into())),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#f59e0b".into())),
        ],
    )?;
    insert(
        "tags",
        3,
        vec![
            ("name", Value::Str("meeting".into())),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#8b5cf6".into())),
        ],
    )?;
    insert(
        "tags",
        4,
        vec![
            ("name", Value::Str("idea".into())),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#22c55e".into())),
        ],
    )?;
    insert(
        "tags",
        5,
        vec![
            ("name", Value::Str("reference".into())),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#3b82f6".into())),
        ],
    )?;
    insert(
        "tags",
        6,
        vec![
            ("name", Value::Str("archive".into())),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#64748b".into())),
        ],
    )?;

    println!("  βœ“ 6 tags created");

    // Notebooks
    insert(
        "notebooks",
        1,
        vec![
            ("name", Value::Str("Personal".into())),
            (
                "description",
                Value::Str("Personal notes and journal entries".into()),
            ),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#5b8cff".into())),
        ],
    )?;
    insert(
        "notebooks",
        2,
        vec![
            ("name", Value::Str("Work".into())),
            (
                "description",
                Value::Str("Work-related notes and projects".into()),
            ),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#f59e0b".into())),
        ],
    )?;
    insert(
        "notebooks",
        3,
        vec![
            ("name", Value::Str("Ideas".into())),
            (
                "description",
                Value::Str("Random ideas and inspiration".into()),
            ),
            ("ownerSub", Value::Str(owner_sub.into())),
            ("color", Value::Str("#22c55e".into())),
        ],
    )?;

    println!("  βœ“ 3 notebooks created");

    // Notes β€” 12 total across 3 notebooks
    insert("notes", 1, vec![
        ("title", Value::Str("Weekly review β€” w24".into())),
        ("body", Value::Str("Completed the Q2 planning doc. Key outcomes: revenue target of $2.5M, 3 new hires approved, product launch pushed to August. Need to follow up with legal on the partnership agreement.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(2)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("1,3".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 2, vec![
        ("title", Value::Str("Book notes β€” The Mom Test".into())),
        ("body", Value::Str("Key insight: people lie to be nice. Don't ask 'would you buy this?' β€” ask about their current behavior. Real signal comes from commitments: time, money, reputation. Three rules: talk about their life (not your idea), ask about specifics (not hypotheticals), listen for generics (they're hiding the truth).".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(1)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("5".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 3, vec![
        ("title", Value::Str("Product idea β€” AI meeting summarizer".into())),
        ("body", Value::Str("A Slack bot that joins meetings, transcribes them, and posts a summary + action items to the thread. Key differentiator: it learns each team's categories and formats the output to match. Could monetize per-seat or per-meeting.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(3)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("4".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 4, vec![
        ("title", Value::Str("Meeting notes β€” All-hands 2026-06-01".into())),
        ("body", Value::Str("CEO announced restructuring: engineering splits into platform and product teams. Timeline is Q3. No layoffs β€” mostly re-org. New CTO starts July 1. Action: update my team's OKRs to reflect the new structure.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(2)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("1,3".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 5, vec![
        ("title", Value::Str("Journal β€” June 15".into())),
        ("body", Value::Str("Woke up early, went for a run by the lake. Felt good. Reading 'Deep Work' by Cal Newport β€” the argument that deep concentration is becoming a稀缺 resource resonates. Need to block 3 hours every morning for focused work.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(1)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str(String::new())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 6, vec![
        ("title", Value::Str("Architecture sketch β€” offline-first sync".into())),
        ("body", Value::Str("CRDT-based sync engine. Clients have local SQLite + remote BTree. Two ops: push local changes (with vector clock) and pull remote changes (with merge). Conflict resolution: last-write-wins for scalar fields, CRDT merge for arrays. Design doc needed before implementation.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(3)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("4,5".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 7, vec![
        ("title", Value::Str("Grocery list".into())),
        ("body", Value::Str("Milk, eggs, bread, avocados, chicken, broccoli, rice, olive oil, coffee filters, dark chocolate.".into())),
        ("format", Value::Str("plain".into())),
        ("notebookId", Value::Int(1)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("2".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 8, vec![
        ("title", Value::Str("OKR review β€” Q2".into())),
        ("body", Value::Str("KR1: Ship v0.1 of the API β€” on track. KR2: 10 beta users β€” 7 signed up, need 3 more. KR3: Blog post about the architecture β€” drafted, needs editing. Overall: yellow (needs attention on KR2).".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(2)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("1".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 9, vec![
        ("title", Value::Str("Deep Work β€” chapter summaries".into())),
        ("body", Value::Str("Ch 1: Deep work is rare because it's hard and because the culture of connectivity rewards shallow work. Ch 2: The deep life is a good life β€” meaning comes from attention. Ch 3: Decide on your depth philosophy (monastic, bimodal, rhythmic, journalistic).".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(1)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("5".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 10, vec![
        ("title", Value::Str("Startup idea β€” developer billing API".into())),
        ("body", Value::Str("Stripe for B2B SaaS billing. Companies waste weeks building invoicing, dunning, metered billing. API-first, usage-based pricing, Stripe Connect under the hood. Target market: early-stage B2B SaaS. Revenue model: 1% + $0.10 per transaction.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(3)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("4".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 11, vec![
        ("title", Value::Str("Performance review prep".into())),
        ("body", Value::Str("Achievements this half: shipped the collections API (E04), documented the deployment workflow, mentored 2 juniors. Areas to improve: code review turnaround time, async communication in Slack. Goals for next half: lead the vector search work (E05), write 3 ADRs.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(2)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("1".into())),
        ("archived", Value::Bool(false)),
    ])?;
    insert("notes", 12, vec![
        ("title", Value::Str("Old notes β€” project archive".into())),
        ("body", Value::Str("These are notes from the previous project that are no longer relevant but kept for reference.".into())),
        ("format", Value::Str("markdown".into())),
        ("notebookId", Value::Int(1)),
        ("ownerSub", Value::Str(owner_sub.into())),
        ("tagIds", Value::Str("6".into())),
        ("archived", Value::Bool(true)),
    ])?;

    // Dev-only session so the web UI can be exercised locally without the OAuth
    // round-trip. Present only in manually-seeded databases; never created by the
    // deploy path. Use cookie `notes_session=dev` (or Authorization: Bearer dev).
    {
        let session = Value::Object(vec![
            ("sub".into(), Value::Str(owner_sub.into())),
            ("email".into(), Value::Str(owner_sub.into())),
            ("createdAt".into(), Value::Int(0)),
        ]);
        db.insert(b"_session:dev", session.to_json().as_bytes())
            .map_err(|e| format!("insert _session:dev: {e}"))?;
        println!("  βœ“ dev session created (cookie notes_session=dev)");
    }
    db.commit().map_err(|e| format!("commit: {e}"))?;
    println!("  βœ“ 12 notes created");
    println!("βœ… Database seeded successfully!");
    Ok(())
}