//! Deterministic local/demo sample data for appview read paths. //! //! This module writes projection-shaped SQLite rows only when explicitly enabled //! and the local/demo database has no Polymodel things. It lets e2e and local //! demo runs exercise the same `getFeed`, `getThing`, and `getModel` appview //! contracts as production reads without UI-only fixtures. use jacquard_common::types::blob::BlobRef; use polymodel_api::space_polymodel::{ actor::profile::Profile, library::{File, model::Model, part::Part, thing::Thing}, }; use serde::{Serialize, de::DeserializeOwned}; use serde_json::{Value, json}; use sqlx::{SqliteConnection, SqlitePool}; pub const SAMPLE_DID: &str = "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa"; pub const SAMPLE_HANDLE: &str = "ari.model.tools"; pub const SAMPLE_THING_RKEY: &str = "parametric-enclosure"; pub const SAMPLE_THING_URI: &str = "at://did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/space.polymodel.library.thing/parametric-enclosure"; const SAMPLE_TIME: &str = "2026-06-21T12:00:00.000Z"; const SAMPLE_TIME_MILLIS: i64 = 1_750_507_200_000; const SAMPLE_CID: &str = "bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku"; const LDRAW_ROOT_CID: &str = "bafkreiblomfprrld3bjvf5ktjw2trnjl2tbxg6rx5uvzaive4f35iob5ja"; const LDRAW_CHILD_CID: &str = "bafkreih5euurby4kkq76q5dlezn5qdleeqb2w4gm5zx2la6hl6o7pxporu"; const LDRAW_COMPANION_CID: &str = "bafkreie42luggp5uta6nylexcnrnpoimoa255trbl75pmp7v6bxnrdlrtu"; const LDRAW_ROOT_BYTES: &[u8] = b"0 BFC CERTIFY CCW\n1 16 -35 0 0 1 0 0 0 1 0 0 0 1 models/child.dat\n1 16 35 0 0 0 0 -1 0 1 0 1 0 0 models/child.dat\n1 16 0 0 35 1 0 0 0 1 0 0 0 1 models/companion.dat\n"; const LDRAW_CHILD_BYTES: &[u8] = b"0 BFC CERTIFY CCW\n4 4 -20 0 -20 20 0 -20 20 0 20 -20 0 20\n4 1 -20 0 -20 -20 30 -20 20 30 -20 20 0 -20\n4 2 20 0 -20 20 30 -20 20 30 20 20 0 20\n4 14 20 0 20 20 30 20 -20 30 20 -20 0 20\n4 15 -20 0 20 -20 30 20 -20 30 -20 -20 0 -20\n2 24 -20 30 -20 20 30 -20\n2 24 20 30 -20 20 30 20\n2 24 20 30 20 -20 30 20\n2 24 -20 30 20 -20 30 -20\n"; const LDRAW_COMPANION_BYTES: &[u8] = b"0 BFC CERTIFY CCW\n3 5 -25 0 -25 25 0 -25 0 40 0\n3 5 25 0 -25 25 0 25 0 40 0\n3 5 25 0 25 -25 0 25 0 40 0\n3 5 -25 0 25 -25 0 -25 0 40 0\n5 24 -25 0 -25 25 0 -25 0 40 0 0 -20 0\n"; // Additional demo makers so the discovery home shows a populated feed with // mixed media states: one thing with a cover, two cover-less things that // render the missing-media blueprint placeholder. const MIRA_DID: &str = "did:plc:bbbbbbbbbbbbbbbbbbbbbbbb"; const MIRA_HANDLE: &str = "mira.tools"; const FOUNDRY_DID: &str = "did:plc:cccccccccccccccccccccccc"; const FOUNDRY_HANDLE: &str = "foundrylab.tools"; pub async fn seed_if_empty(pool: &SqlitePool) -> anyhow::Result<()> { let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM things") .fetch_one(pool) .await?; if count > 0 { tracing::debug!( count, "sample data skipped because things table is populated" ); return Ok(()); } seed(pool).await?; tracing::info!( thing_uri = SAMPLE_THING_URI, "seeded local/demo Polymodel sample data" ); Ok(()) } async fn seed(pool: &SqlitePool) -> anyhow::Result<()> { let mut tx = pool.begin().await?; insert_identity(&mut tx, SAMPLE_DID, SAMPLE_HANDLE).await?; insert_profile(&mut tx).await?; let model_uris = [ model_uri("enclosure-v1"), model_uri("panel-variant"), model_uri("ruggedized-lid"), ]; let parts = sample_parts(); for part in &parts { insert_part(&mut tx, part).await?; } insert_ldraw_projection(&mut tx, &parts[0..3]).await?; insert_model( &mut tx, "enclosure-v1", "Snap-fit electronics enclosure", "Main printable enclosure with lid, body, buttons, and mounting hardware.", &parts[0..6], ) .await?; insert_model( &mut tx, "panel-variant", "Panel-mount variant", "Alternate front panel for flush mounting sensors and displays.", &parts[6..12], ) .await?; insert_model( &mut tx, "ruggedized-lid", "Ruggedized outdoor lid", "Weather-resistant lid and gasket parts for workshop deployments.", &parts[12..18], ) .await?; let instructions = vec![ "Print the body and lid in PETG or PLA+ with 0.2 mm layers.".to_string(), "Use the model selector to choose the standard, panel-mount, or ruggedized variant." .to_string(), "Press brass inserts into the corner bosses before final assembly.".to_string(), ]; let tags = vec!["enclosure", "parametric", "print-in-place"]; let thing_record = json!({ "name": "Parametric enclosure kit", "summary": "A configurable electronics enclosure kit with three printable model variants and detailed part files.", "instructions": instructions, "license": "CC-BY-4.0", "tags": tags, "cover": [image(SAMPLE_CID, "Rendered preview of a parametric electronics enclosure")], "previews": [image(SAMPLE_CID, "Exploded blueprint preview of the enclosure kit")], "models": model_uris.iter().map(|uri| strong_ref(uri)).collect::>(), "createdAt": SAMPLE_TIME }); let thing_record_json = typed_json::(thing_record.clone())?; sqlx::query( r#"INSERT INTO things (did, rkey, uri, cid, name, summary, license, tags_json, tags_text, instructions_text, cover_json, derived_from_uri, created_at, indexed_at, record_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)"#, ) .bind(SAMPLE_DID) .bind(SAMPLE_THING_RKEY) .bind(SAMPLE_THING_URI) .bind(SAMPLE_CID) .bind("Parametric enclosure kit") .bind("A configurable electronics enclosure kit with three printable model variants and detailed part files.") .bind("CC-BY-4.0") .bind(serde_json::to_string(&tags)?) .bind(tags.join(" ")) .bind(instructions.join("\n")) .bind(serde_json::to_string(&thing_record["cover"])? ) .bind(SAMPLE_TIME_MILLIS) .bind(SAMPLE_TIME_MILLIS) .bind(thing_record_json) .execute(&mut *tx) .await?; for (position, model_uri) in model_uris.iter().enumerate() { sqlx::query("INSERT INTO thing_models (thing_uri, model_uri, position) VALUES (?, ?, ?)") .bind(SAMPLE_THING_URI) .bind(model_uri) .bind(position as i64) .execute(&mut *tx) .await?; } sqlx::query( "INSERT INTO content_stats (uri, like_count, save_count, tag_count) VALUES (?, 42, 12, 3)", ) .bind(SAMPLE_THING_URI) .execute(&mut *tx) .await?; // Two more makers with cover-less things so the home feed is populated and // the missing-media placeholder path renders against real projected rows. insert_identity(&mut tx, MIRA_DID, MIRA_HANDLE).await?; insert_identity(&mut tx, FOUNDRY_DID, FOUNDRY_HANDLE).await?; insert_coverless_thing( &mut tx, MIRA_DID, "field-prototype", "Untitled thing", "A work-in-progress upload with no preview rendered yet.", 0, 0, ) .await?; insert_coverless_thing( &mut tx, FOUNDRY_DID, "modular-calibration-tower", "Modular calibration tower", "A calibration tower for dialing in printer tolerances before a full run.", 8, 5, ) .await?; tx.commit().await?; Ok(()) } async fn insert_identity( conn: &mut SqliteConnection, did: &str, handle: &str, ) -> anyhow::Result<()> { sqlx::query("INSERT INTO identities (did, handle, updated_at) VALUES (?, ?, ?)") .bind(did) .bind(handle) .bind(SAMPLE_TIME_MILLIS) .execute(&mut *conn) .await?; Ok(()) } async fn insert_profile(conn: &mut SqliteConnection) -> anyhow::Result<()> { let avatar = typed_json::(blob(SAMPLE_CID, "image/jpeg", 2048))?; let profile_record = typed_json::(json!({ "displayName": "Ari Chen", "description": "Maker of practical parametric fixtures and electronics housings.", "avatar": serde_json::from_str::(&avatar)?, "pronouns": "she/her", "defaultLicense": "CC-BY-4.0" }))?; sqlx::query( r#"INSERT INTO profiles (did, display_name, description, avatar_json, default_license, pronouns, printers_json, links_json, record_json, indexed_at) VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?)"#, ) .bind(SAMPLE_DID) .bind("Ari Chen") .bind("Maker of practical parametric fixtures and electronics housings.") .bind(&avatar) .bind("CC-BY-4.0") .bind("she/her") .bind(profile_record) .bind(SAMPLE_TIME_MILLIS) .execute(&mut *conn) .await?; Ok(()) } async fn insert_model( conn: &mut SqliteConnection, rkey: &str, name: &str, summary: &str, parts: &[SamplePart], ) -> anyhow::Result<()> { let uri = model_uri(rkey); let record = typed_json::(json!({ "name": name, "summary": summary, "instructions": ["Review the ordered part list before printing.", "Print fit-critical clips slowly for best tolerances."], "license": "CC-BY-4.0", "tags": ["variant", "enclosure"], "cover": [image(SAMPLE_CID, &format!("Preview of {name}"))], "previews": [image(SAMPLE_CID, &format!("Blueprint preview of {name}"))], "parts": parts.iter().map(|part| strong_ref(&part_uri(part.rkey))).collect::>(), "createdAt": SAMPLE_TIME }))?; sqlx::query( r#"INSERT INTO models (did, rkey, uri, cid, name, summary, created_at, indexed_at, record_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"#, ) .bind(SAMPLE_DID) .bind(rkey) .bind(&uri) .bind(SAMPLE_CID) .bind(name) .bind(summary) .bind(SAMPLE_TIME_MILLIS) .bind(SAMPLE_TIME_MILLIS) .bind(record) .execute(&mut *conn) .await?; for (position, part) in parts.iter().enumerate() { sqlx::query("INSERT INTO model_parts (model_uri, part_uri, position) VALUES (?, ?, ?)") .bind(&uri) .bind(part_uri(part.rkey)) .bind(position as i64) .execute(&mut *conn) .await?; } Ok(()) } async fn insert_part(conn: &mut SqliteConnection, part: &SamplePart) -> anyhow::Result<()> { let is_ldraw_root = part.rkey == "part-01"; let format = if is_ldraw_root { "LDraw" } else { "STL" }; let file = if is_ldraw_root { file_manifest_with( "application/x-ldraw", LDRAW_ROOT_BYTES.len() as i64, LDRAW_ROOT_CID, ) } else { file_manifest(part.file_size) }; let file_json = typed_json::(file.clone())?; let record = typed_json::(json!({ "name": part.name, "file": file, "format": format, "dimensions": { "x": part.dimensions.0, "y": part.dimensions.1, "z": part.dimensions.2, "unit": "mm" }, "units": "mm", "notes": part.notes, "printSettings": ["0.2 mm layer height", "15% gyroid infill", "No supports required unless noted"], "previews": [image(SAMPLE_CID, &format!("Preview of {}", part.name))], "createdAt": SAMPLE_TIME }))?; sqlx::query( r#"INSERT INTO parts (did, rkey, uri, cid, name, format, file_json, created_at, indexed_at, record_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#, ) .bind(SAMPLE_DID) .bind(part.rkey) .bind(part_uri(part.rkey)) .bind(SAMPLE_CID) .bind(part.name) .bind(format) .bind(file_json) .bind(SAMPLE_TIME_MILLIS) .bind(SAMPLE_TIME_MILLIS) .bind(record) .execute(&mut *conn) .await?; Ok(()) } async fn insert_ldraw_projection( conn: &mut SqliteConnection, parts: &[SamplePart], ) -> anyhow::Result<()> { use sha2::Digest; let resources = [ ( &parts[0], "models", "models/main.ldr", LDRAW_ROOT_BYTES, LDRAW_ROOT_CID, ), ( &parts[1], "models", "models/child.dat", LDRAW_CHILD_BYTES, LDRAW_CHILD_CID, ), ( &parts[2], "models", "models/companion.dat", LDRAW_COMPANION_BYTES, LDRAW_COMPANION_CID, ), ]; for (part, root, path, bytes, cid) in resources { let uri = part_uri(part.rkey); let digest = sha2::Sha256::digest(bytes).to_vec(); let source_identity = crate::ldraw::source_identity_for( &jacquard_common::types::string::AtUri::new_owned(&uri)?, &[cid.to_owned()], &digest, bytes.len() as i64, ); sqlx::query("INSERT INTO ldraw_resources (resource_uri, owner_did, project_uri, record_cid, rkey, root, canonical_path, mime_type, byte_length, sha256, ordered_blob_cids, provenance, licence, notices_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") .bind(&uri).bind(SAMPLE_DID).bind(SAMPLE_THING_URI).bind(SAMPLE_CID) .bind(part.rkey).bind(root).bind(path).bind("application/x-ldraw") .bind(bytes.len() as i64).bind(&digest).bind(serde_json::to_string(&[cid])?) .bind("sample-data").bind("CC-BY-4.0").bind("[]") .bind(SAMPLE_TIME_MILLIS).bind(SAMPLE_TIME_MILLIS) .execute(&mut *conn).await?; sqlx::query("INSERT INTO ldraw_resource_memberships (resource_uri, project_uri, root, canonical_path, provenance) VALUES (?, ?, ?, ?, ?)") .bind(&uri).bind(SAMPLE_THING_URI).bind(root).bind(path).bind("sample-data") .execute(&mut *conn).await?; sqlx::query("INSERT INTO ldraw_verification (resource_uri, source_identity, state, diagnostic, updated_at) VALUES (?, ?, 'verified', NULL, ?)") .bind(&uri).bind(source_identity).bind(SAMPLE_TIME_MILLIS) .execute(&mut *conn).await?; } let root_uri = part_uri(parts[0].rkey); for (ordinal, (part, path, bytes, cid)) in [ ( &parts[1], "models/child.dat", LDRAW_CHILD_BYTES, LDRAW_CHILD_CID, ), ( &parts[2], "models/companion.dat", LDRAW_COMPANION_BYTES, LDRAW_COMPANION_CID, ), ] .into_iter() .enumerate() { sqlx::query("INSERT INTO ldraw_manifest_files (resource_uri, root, canonical_path, target_resource_uri, target_cid, byte_length, sha256, mime_type, ordinal) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") .bind(&root_uri).bind("models").bind(path).bind(part_uri(part.rkey)).bind(cid) .bind(bytes.len() as i64).bind(sha2::Sha256::digest(bytes).to_vec()) .bind("application/x-ldraw").bind(ordinal as i64) .execute(&mut *conn).await?; } Ok(()) } #[derive(Clone, Copy)] struct SamplePart { rkey: &'static str, name: &'static str, file_size: i64, dimensions: (&'static str, &'static str, &'static str), notes: &'static str, } fn sample_parts() -> Vec { [ ("part-01", "Main enclosure body"), ("part-02", "Snap-fit lid"), ("part-03", "Button cap set"), ("part-04", "USB-C bezel"), ("part-05", "PCB mounting tray"), ("part-06", "Corner screw boss"), ("part-07", "Front sensor panel"), ("part-08", "Display window frame"), ("part-09", "Panel latch pair"), ("part-10", "Flush wall bracket"), ("part-11", "Cable strain relief"), ("part-12", "Label plate"), ("part-13", "Ruggedized lid shell"), ("part-14", "TPU gasket guide"), ("part-15", "Outdoor cable gland"), ("part-16", "Hinged dust cover"), ("part-17", "Drain slot insert"), ("part-18", "Mounting foot set"), ] .into_iter() .enumerate() .map(|(index, (rkey, name))| SamplePart { rkey, name, file_size: 48_000 + ((index + 1) as i64 * 2_048), dimensions: ("86.0", "54.0", "18.5"), notes: "Orient the visible face upward and verify first-layer adhesion around clips.", }) .collect() } async fn insert_coverless_thing( conn: &mut SqliteConnection, did: &str, rkey: &str, name: &str, summary: &str, like_count: i64, save_count: i64, ) -> anyhow::Result<()> { let uri = thing_uri(did, rkey); let record = typed_json::(json!({ "name": name, "summary": summary, "license": "CC-BY-4.0", "createdAt": SAMPLE_TIME }))?; sqlx::query( r#"INSERT INTO things (did, rkey, uri, cid, name, summary, license, tags_json, tags_text, instructions_text, cover_json, derived_from_uri, created_at, indexed_at, record_json) VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, NULL, NULL, ?, ?, ?)"#, ) .bind(did) .bind(rkey) .bind(&uri) .bind(SAMPLE_CID) .bind(name) .bind(summary) .bind("CC-BY-4.0") .bind(SAMPLE_TIME_MILLIS) .bind(SAMPLE_TIME_MILLIS) .bind(record) .execute(&mut *conn) .await?; if like_count > 0 || save_count > 0 { sqlx::query( "INSERT INTO content_stats (uri, like_count, save_count, tag_count) VALUES (?, ?, ?, 0)", ) .bind(&uri) .bind(like_count) .bind(save_count) .execute(&mut *conn) .await?; } Ok(()) } fn model_uri(rkey: &str) -> String { format!("at://{SAMPLE_DID}/space.polymodel.library.model/{rkey}") } fn part_uri(rkey: &str) -> String { format!("at://{SAMPLE_DID}/space.polymodel.library.part/{rkey}") } fn thing_uri(did: &str, rkey: &str) -> String { format!("at://{did}/space.polymodel.library.thing/{rkey}") } fn strong_ref(uri: &str) -> Value { json!({ "uri": uri, "cid": SAMPLE_CID }) } fn image(cid: &str, alt: &str) -> Value { json!({ "alt": alt, "aspectRatio": { "width": 4, "height": 3 }, "image": blob(cid, "image/jpeg", 1234) }) } fn file_manifest(size: i64) -> Value { file_manifest_with("model/stl", size, SAMPLE_CID) } fn file_manifest_with(mime_type: &str, size: i64, cid: &str) -> Value { json!({ "mimeType": mime_type, "size": size, "chunks": [{ "blob": blob(cid, mime_type, size), "offset": 0, "size": size }] }) } fn blob(cid: &str, mime_type: &str, size: i64) -> Value { json!({ "$type": "blob", "ref": { "$link": cid }, "mimeType": mime_type, "size": size }) } fn typed_json(value: Value) -> anyhow::Result where T: DeserializeOwned + Serialize, { let typed: T = serde_json::from_value(value)?; Ok(serde_json::to_string(&typed)?) } #[cfg(test)] mod tests { use std::str::FromStr; use std::sync::Arc; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use super::*; use crate::appview::{state::AppState, views}; async fn test_pool() -> SqlitePool { let options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap(); let pool = SqlitePoolOptions::new() .max_connections(1) .connect_with(options) .await .unwrap(); sqlx::migrate!("./migrations").run(&pool).await.unwrap(); pool } #[tokio::test] async fn seed_populates_feed_thing_and_model_read_paths() { let pool = test_pool().await; seed_if_empty(&pool).await.unwrap(); seed_if_empty(&pool).await.unwrap(); let bootstrap = crate::oauth::bootstrap_oauth(pool.clone(), Some("http://localhost")) .expect("ephemeral OAuth bootstrap for tests"); let state = Arc::new(AppState::new(pool, bootstrap)); let feed = views::feed_recent(&state, 10, None, None).await.unwrap(); let feed_things = feed .items .iter() .map(|item| item.thing.clone()) .collect::>(); let transport_bytes = dioxus::fullstack::Transportable::transport_to_bytes(&feed_things); assert!(!transport_bytes.is_empty()); // Three seeded things: the model-rich parametric kit plus two cover-less // demo things that exercise the missing-media placeholder path. assert_eq!(feed.items.len(), 3); // feed_recent is rkey DESC, so "parametric-enclosure" sorts first. assert_eq!( feed.items[0].thing.name.as_str(), "Parametric enclosure kit" ); assert_eq!(feed.items[0].thing.model_count, 3); assert_eq!(feed.items[0].thing.part_count, 18); let names: Vec<&str> = feed .items .iter() .map(|item| item.thing.name.as_str()) .collect(); assert!(names.contains(&"Untitled thing")); assert!(names.contains(&"Modular calibration tower")); let (thing, models) = views::get_thing(&state, SAMPLE_THING_URI, None) .await .unwrap(); assert_eq!( thing.author.display_name.as_ref().unwrap().as_str(), "Ari Chen" ); assert_eq!(models.len(), 3); let detail = views::get_model(&state, models[0].uri.as_ref(), None) .await .unwrap(); assert_eq!(detail.parts.len(), 6); assert_eq!(detail.parts[0].name.as_str(), "Main enclosure body"); } }