refactor(db): DRY object SELECT columns, consistent date json; test date + all-none round-trip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 09:29:40 +02:00
parent e0c0187f29
commit 616a6f05c6
3 changed files with 54 additions and 17 deletions
+12 -17
View File
@@ -13,13 +13,9 @@ use crate::audit;
/// The entity_type recorded in the audit log for catalogue objects.
const ENTITY_TYPE: &str = "object";
const SELECT_OBJECT_BY_ID: &str = "SELECT id, object_number, object_name, number_of_objects, brief_description, \
current_location, current_owner, recorder, recording_date, visibility, \
created_at, updated_at FROM object WHERE id = $1";
const SELECT_OBJECTS_ORDERED: &str = "SELECT id, object_number, object_name, number_of_objects, brief_description, \
current_location, current_owner, recorder, recording_date, visibility, \
created_at, updated_at FROM object ORDER BY object_number";
const OBJECT_COLUMNS: &str = "id, object_number, object_name, number_of_objects, \
brief_description, current_location, current_owner, recorder, recording_date, \
visibility, created_at, updated_at";
/// Create an object and record a `created` audit entry, both on `conn`
/// (pass a transaction connection `&mut *tx` so they commit atomically).
@@ -74,7 +70,9 @@ pub async fn object_by_id<'e, E>(
where
E: sqlx::PgExecutor<'e>,
{
let row = sqlx::query(SELECT_OBJECT_BY_ID)
let sql = format!("SELECT {OBJECT_COLUMNS} FROM object WHERE id = $1");
let row = sqlx::query(&sql)
.bind(id.to_uuid())
.fetch_optional(executor)
.await?;
@@ -88,9 +86,9 @@ where
E: sqlx::PgExecutor<'e>,
{
// TODO: add LIMIT/keyset pagination before exposing this via the API.
let rows = sqlx::query(SELECT_OBJECTS_ORDERED)
.fetch_all(executor)
.await?;
let sql = format!("SELECT {OBJECT_COLUMNS} FROM object ORDER BY object_number");
let rows = sqlx::query(&sql).fetch_all(executor).await?;
rows.into_iter().map(map_object).collect()
}
@@ -137,17 +135,14 @@ fn field_values(input: &ObjectInput) -> Vec<(&'static str, Option<Value>)> {
input.current_owner.as_ref().map(|v| json!(v)),
),
("recorder", input.recorder.as_ref().map(|v| json!(v))),
(
"recording_date",
input
.recording_date
.and_then(|d| serde_json::to_value(d).ok()),
),
("recording_date", input.recording_date.map(|d| json!(d))),
("visibility", Some(json!(input.visibility.as_str()))),
]
}
/// Audit changes for a newly created object: every set field as an `after` value.
/// Unset (`None`) optional fields are omitted — absence is conveyed by their not
/// appearing, consistent with `FieldChange`'s `None`-means-no-value convention.
fn creation_changes(input: &ObjectInput) -> Vec<FieldChange> {
field_values(input)
.into_iter()