feat: audit vocabulary/term/authority creation, attributing the acting user (#21)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+46
-10
@@ -1,25 +1,44 @@
|
||||
//! Controlled vocabularies and terms.
|
||||
|
||||
use domain::{LocalizedLabel, NewTerm, Term, TermId, TermRef, Vocabulary, VocabularyId};
|
||||
use domain::{
|
||||
AuditAction, AuditActor, LocalizedLabel, NewAuditEvent, NewTerm, Term, TermId, TermRef,
|
||||
Vocabulary, VocabularyId,
|
||||
};
|
||||
use sqlx::Row;
|
||||
|
||||
use crate::audit;
|
||||
|
||||
/// Labels aggregated per row as JSON, to read a term and its labels in one query.
|
||||
const LABELS_JSON: &str = "COALESCE(json_agg(json_build_object('lang', tl.lang, 'label', tl.label) \
|
||||
ORDER BY tl.lang) FILTER (WHERE tl.term_id IS NOT NULL), '[]'::json)";
|
||||
|
||||
/// Create a vocabulary with the given key.
|
||||
pub async fn create_vocabulary<'e, E>(executor: E, key: &str) -> Result<Vocabulary, sqlx::Error>
|
||||
where
|
||||
E: sqlx::PgExecutor<'e>,
|
||||
{
|
||||
/// Create a vocabulary with the given key and record a `created` audit entry, both on
|
||||
/// `conn` (pass a transaction connection `&mut *tx` so they commit atomically).
|
||||
pub async fn create_vocabulary(
|
||||
conn: &mut sqlx::PgConnection,
|
||||
actor: AuditActor,
|
||||
key: &str,
|
||||
) -> Result<Vocabulary, sqlx::Error> {
|
||||
let id = VocabularyId::new();
|
||||
|
||||
sqlx::query("INSERT INTO vocabulary (id, key) VALUES ($1, $2)")
|
||||
.bind(id.to_uuid())
|
||||
.bind(key)
|
||||
.execute(executor)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
audit::record(
|
||||
&mut *conn,
|
||||
&NewAuditEvent {
|
||||
actor,
|
||||
action: AuditAction::Created,
|
||||
entity_type: "vocabulary".to_owned(),
|
||||
entity_id: id.to_uuid(),
|
||||
changes: Vec::new(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Vocabulary {
|
||||
id,
|
||||
key: key.to_owned(),
|
||||
@@ -54,9 +73,14 @@ where
|
||||
row.map(map_vocabulary).transpose()
|
||||
}
|
||||
|
||||
/// Insert a term and its labels. Multiple statements — pass a transaction
|
||||
/// connection (`&mut *tx`) so the term and its labels commit atomically.
|
||||
pub async fn add_term(conn: &mut sqlx::PgConnection, new: &NewTerm) -> Result<TermId, sqlx::Error> {
|
||||
/// Insert a term and its labels, then record a `created` audit entry. Multiple
|
||||
/// statements — pass a transaction connection (`&mut *tx`) so everything commits
|
||||
/// atomically.
|
||||
pub async fn add_term(
|
||||
conn: &mut sqlx::PgConnection,
|
||||
actor: AuditActor,
|
||||
new: &NewTerm,
|
||||
) -> Result<TermId, sqlx::Error> {
|
||||
let id = TermId::new();
|
||||
|
||||
sqlx::query("INSERT INTO term (id, vocabulary_id, external_uri) VALUES ($1, $2, $3)")
|
||||
@@ -75,6 +99,18 @@ pub async fn add_term(conn: &mut sqlx::PgConnection, new: &NewTerm) -> Result<Te
|
||||
.await?;
|
||||
}
|
||||
|
||||
audit::record(
|
||||
&mut *conn,
|
||||
&NewAuditEvent {
|
||||
actor,
|
||||
action: AuditAction::Created,
|
||||
entity_type: "term".to_owned(),
|
||||
entity_id: id.to_uuid(),
|
||||
changes: Vec::new(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user