feat(db): add object.fields jsonb column, read it into CatalogueObject
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,3 +15,4 @@ serde_json.workspace = true
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Flexible field values for a catalogue object, keyed by field-definition key.
|
||||||
|
ALTER TABLE object ADD COLUMN fields JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||||
@@ -15,7 +15,7 @@ const ENTITY_TYPE: &str = "object";
|
|||||||
|
|
||||||
const OBJECT_COLUMNS: &str = "id, object_number, object_name, number_of_objects, \
|
const OBJECT_COLUMNS: &str = "id, object_number, object_name, number_of_objects, \
|
||||||
brief_description, current_location, current_owner, recorder, recording_date, \
|
brief_description, current_location, current_owner, recorder, recording_date, \
|
||||||
visibility, created_at, updated_at";
|
visibility, fields, created_at, updated_at";
|
||||||
|
|
||||||
/// Create an object and record a `created` audit entry, both on `conn`
|
/// Create an object and record a `created` audit entry, both on `conn`
|
||||||
/// (pass a transaction connection `&mut *tx` so they commit atomically).
|
/// (pass a transaction connection `&mut *tx` so they commit atomically).
|
||||||
@@ -110,6 +110,7 @@ fn map_object(row: sqlx::postgres::PgRow) -> Result<CatalogueObject, sqlx::Error
|
|||||||
recorder: row.try_get("recorder")?,
|
recorder: row.try_get("recorder")?,
|
||||||
recording_date: row.try_get("recording_date")?,
|
recording_date: row.try_get("recording_date")?,
|
||||||
visibility,
|
visibility,
|
||||||
|
fields: row.try_get("fields")?,
|
||||||
created_at: row.try_get("created_at")?,
|
created_at: row.try_get("created_at")?,
|
||||||
updated_at: row.try_get("updated_at")?,
|
updated_at: row.try_get("updated_at")?,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -116,3 +116,16 @@ async fn object_with_date_and_all_none_optionals_round_trips(pool: PgPool) {
|
|||||||
.any(|c| c.field == "recording_date")
|
.any(|c| c.field == "recording_date")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
async fn new_object_has_empty_fields(pool: PgPool) {
|
||||||
|
let db = Db::from_pool(pool);
|
||||||
|
let mut tx = db.pool().begin().await.unwrap();
|
||||||
|
let id = catalog::create_object(&mut tx, AuditActor::System, &sample_input("LM-9"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
|
let obj = catalog::object_by_id(db.pool(), id).await.unwrap().unwrap();
|
||||||
|
assert_eq!(obj.fields, serde_json::json!({}));
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,3 +71,16 @@ async fn migrate_creates_field_definition_tables(pool: PgPool) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
async fn migrate_adds_object_fields_column(pool: PgPool) {
|
||||||
|
let db = Db::from_pool(pool);
|
||||||
|
let exists: Option<bool> = sqlx::query_scalar(
|
||||||
|
"SELECT true FROM information_schema.columns \
|
||||||
|
WHERE table_name = 'object' AND column_name = 'fields'",
|
||||||
|
)
|
||||||
|
.fetch_optional(db.pool())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(exists, Some(true));
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ pub struct CatalogueObject {
|
|||||||
pub recorder: Option<String>,
|
pub recorder: Option<String>,
|
||||||
pub recording_date: Option<Date>,
|
pub recording_date: Option<Date>,
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
|
/// Flexible field values (field key -> value), validated against the registry.
|
||||||
|
pub fields: serde_json::Value,
|
||||||
pub created_at: OffsetDateTime,
|
pub created_at: OffsetDateTime,
|
||||||
pub updated_at: OffsetDateTime,
|
pub updated_at: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user