catch model up to multi-author lines

now the only thing missing is the quote adding UI support - multiauthor
lines can already be added via API
This commit is contained in:
2026-04-23 22:48:51 +02:00
parent 48e14a5830
commit 983e1ae88f
4 changed files with 49 additions and 30 deletions

View File

@@ -33,7 +33,7 @@ pub async fn get_by_id(
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct QuoteLineForm { pub struct QuoteLineForm {
pub content: String, pub content: String,
pub name_id: Uuid, pub name_ids: Vec<Uuid>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -55,8 +55,11 @@ pub async fn create(
let mut lines = Vec::with_capacity(form.lines.len()); let mut lines = Vec::with_capacity(form.lines.len());
for l in form.lines { for l in form.lines {
let name = Name::get_by_id(&mut tx, l.name_id).await?; let mut names = Vec::with_capacity(l.name_ids.len());
lines.push((l.content, name)); for id in l.name_ids {
names.push(Name::get_by_id(&mut tx, id).await?);
}
lines.push((l.content, names));
} }
let q = Quote::create( let q = Quote::create(

View File

@@ -23,7 +23,7 @@ pub struct Quote {
#[derive(Serialize)] #[derive(Serialize)]
pub struct QuoteLine { pub struct QuoteLine {
pub id: Uuid, pub id: Uuid,
pub attribution: Name, pub attribution: Vec<Name>,
pub content: String, pub content: String,
} }
@@ -86,19 +86,26 @@ impl Quote {
.fetch_all(&mut *conn) .fetch_all(&mut *conn)
.await?; .await?;
let mut lines = Vec::with_capacity(line_rows.len()); let mut lines: Vec<QuoteLine> = Vec::new();
for r in line_rows { for r in line_rows {
lines.push(QuoteLine { let line_id: Uuid = r.try_get("id")?;
id: r.try_get("id")?, let name = Name {
content: r.try_get("content")?,
attribution: Name {
id: r.try_get("name_id")?, id: r.try_get("name_id")?,
is_primary: r.try_get("is_primary")?, is_primary: r.try_get("is_primary")?,
person_id: r.try_get("person_id")?, person_id: r.try_get("person_id")?,
name: r.try_get("name")?, name: r.try_get("name")?,
}, };
if let Some(last) = lines.last_mut().filter(|l| l.id == line_id) {
last.attribution.push(name);
} else {
lines.push(QuoteLine {
id: line_id,
content: r.try_get("content")?,
attribution: vec![name],
}); });
} }
}
Ok(Quote { Ok(Quote {
id, id,
@@ -169,7 +176,7 @@ impl Quote {
pub async fn create( pub async fn create(
conn: &mut PgConnection, conn: &mut PgConnection,
lines: Vec<(String, Name)>, lines: Vec<(String, Vec<Name>)>,
timestamp: NaiveDateTime, timestamp: NaiveDateTime,
context: Option<String>, context: Option<String>,
location: Option<String>, location: Option<String>,
@@ -181,7 +188,7 @@ impl Quote {
} }
let quote_id = Uuid::now_v7(); let quote_id = Uuid::now_v7();
let lines: Vec<(Uuid, String, Name)> = lines let lines: Vec<(Uuid, String, Vec<Name>)> = lines
.into_iter() .into_iter()
.map(|(c, a)| (Uuid::now_v7(), c, a)) .map(|(c, a)| (Uuid::now_v7(), c, a))
.collect(); .collect();
@@ -215,6 +222,7 @@ impl Quote {
.execute(&mut *conn) .execute(&mut *conn)
.await?; .await?;
for a in attr {
sqlx::query( sqlx::query(
r#" r#"
INSERT INTO line_authors (line_id, name_id) INSERT INTO line_authors (line_id, name_id)
@@ -222,10 +230,11 @@ impl Quote {
"#, "#,
) )
.bind(id) .bind(id)
.bind(attr.id) .bind(a.id)
.execute(&mut *conn) .execute(&mut *conn)
.await?; .await?;
} }
}
Ok(Quote { Ok(Quote {
id: quote_id, id: quote_id,

View File

@@ -9,7 +9,9 @@ pub fn quote(quote: &Quote) -> Markup {
(PreEscaped(icons::QUOTE)) (PreEscaped(icons::QUOTE))
} }
@for (i, line) in quote.lines.iter().enumerate() { @for (i, line) in quote.lines.iter().enumerate() {
@let show_author = i == quote.lines.len()-1 || quote.lines[i+1].attribution.id != line.attribution.id; @let is_last = i == quote.lines.len() - 1;
@let show_author = is_last || !line.attribution.iter().map(|a| a.id)
.eq(quote.lines[i + 1].attribution.iter().map(|a| a.id));
div class="mb-2" { div class="mb-2" {
span class="flex flex-row gap-2 relative" { span class="flex flex-row gap-2 relative" {
span class="scale-x-[.65] scale-y-[.5] absolute opacity-[.3]"{ span class="scale-x-[.65] scale-y-[.5] absolute opacity-[.3]"{
@@ -19,7 +21,7 @@ pub fn quote(quote: &Quote) -> Markup {
} }
@if show_author { @if show_author {
p class="text-sm italic ml-3 flex flex-row gap-1.5 text-neutral-400" { p class="text-sm italic ml-3 flex flex-row gap-1.5 text-neutral-400" {
"" (line.attribution.name) "" (line.attribution.iter().map(|a| a.name.clone()).collect::<Vec<_>>().join(", "))
} }
} }
} }

View File

@@ -153,7 +153,12 @@ pub async fn form(
for nid in form.authors { for nid in form.authors {
authors.push(Name::get_by_id(&mut *tx, nid).await.unwrap()); authors.push(Name::get_by_id(&mut *tx, nid).await.unwrap());
} }
let lines = form.lines.into_iter().zip(authors).collect(); let lines = form
.lines
.into_iter()
.zip(authors)
.map(|(l, a)| (l, vec![a]))
.collect();
let timestamp = match NaiveDateTime::parse_from_str(&form.time, "%Y-%m-%dT%H:%M") { let timestamp = match NaiveDateTime::parse_from_str(&form.time, "%Y-%m-%dT%H:%M") {
Ok(ts) => ts, Ok(ts) => ts,