Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614//! 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::<Vec<_>>(), "createdAt": SAMPLE_TIME });
let thing_record_json = typed_json::<Thing>(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::<BlobRef>(blob(SAMPLE_CID, "image/jpeg", 2048))?; let profile_record = typed_json::<Profile>(json!({ "displayName": "Ari Chen", "description": "Maker of practical parametric fixtures and electronics housings.", "avatar": serde_json::from_str::<Value>(&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::<Model>(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::<Vec<_>>(), "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>(file.clone())?; let record = typed_json::<Part>(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<SamplePart> { [ ("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::<Thing>(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<T>(value: Value) -> anyhow::Result<String>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::<Vec<_>>(); 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"); }}