From 67250af445b339d9b62c5bf96cb5eefeaadd1d13 Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Wed, 30 Sep 2026 08:18:30 +0200 Subject: [PATCH] build: the plan's contact may be a group, which then answers for every page Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt --- plans/tomtervel.yaml | 4 +++- src/build.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/plans/tomtervel.yaml b/plans/tomtervel.yaml index 3400188..d1db17d 100644 --- a/plans/tomtervel.yaml +++ b/plans/tomtervel.yaml @@ -12,7 +12,9 @@ # igjen. business: Tomter Vel language: no -contact: { name: Styret i Tomter Vel, email: post@tomtervel.no } +# Styret svarer for sidene som gruppe: ingen konto for innboksen, og +# hvert styremedlem kan foreslå endringer på dem. +contact: { name: Styret i Tomter Vel, email: post@tomtervel.no, group: styret } # Avsenderen for all e-post fra siden (gdo på kasse leverer); det # kasses postfix kan sende for i dag. mail: { from: "Tomter Vel ", reply_to: post@tomtervel.no } diff --git a/src/build.rs b/src/build.rs index 62b253b..fc1f1b1 100644 --- a/src/build.rs +++ b/src/build.rs @@ -156,6 +156,11 @@ pub struct Door { pub struct Contact { pub name: String, pub email: String, + /// The group the contact is: a board's inbox, not a person. Then + /// portal makes no account for the address, and every member of + /// the group may suggest changes to the pages it answers for. + #[serde(default)] + pub group: Option, } /// Who the site's mail comes from: portal sends nothing without it, @@ -343,6 +348,13 @@ fn is_id(s: &str) -> bool { impl Plan { pub fn validate(&self) -> anyhow::Result<()> { + // A contact that is a group has to be one with a desk, or + // nobody could sign in as it. + if let Some(group) = &self.contact.group { + if !groups(self).contains(group) { + bail!("contact: group {group:?} has no desk: it must handle, watch or move something"); + } + } let mut doors = BTreeSet::new(); for door in &self.doors { if !is_id(&door.id) || !doors.insert(&door.id) { @@ -1591,7 +1603,10 @@ fn form_alternative(plan: &Plan, need: &PlanNeed, w: &Value, action: &str) -> Va /// Every file of the content repo, from the plan and a set of words. pub fn render(plan: &Plan, words: &BTreeMap) -> anyhow::Result> { let mut files: BTreeMap = BTreeMap::new(); - let responsible = json!({ "name": plan.contact.name, "contact": plan.contact.email }); + let mut responsible = json!({ "name": plan.contact.name, "contact": plan.contact.email }); + if let Some(group) = &plan.contact.group { + responsible["group"] = json!(group); + } let mut site = json!({ "title": plan.business, "lang": plan.language }); if let Some(mail) = &plan.mail { @@ -2909,6 +2924,18 @@ mod invite_tests { const BOARD: &str = "business: Vel\ncontact: { name: S, email: s@v.example }\nmail: { from: v@v.example }\nlanguage: no\nneeds:\n - id: notice\n who: en nabo som har noe å låne ut\n wants: et oppslag naboene ser\n lands_in: oppslag\n handled_by: redaktor\n stages: [open, tatt_ned]\n done_when: [tatt_ned]\n moves_by: { tatt_ned: [submitter, redaktor] }\n mails: { open: s@v.example }\n watched_by: [public]\n public_states: [open]\n max_fields: 3\n collects:\n - { name: email, type: email }\n - { name: hva, type: textarea }\n - id: answer\n who: en nabo som kan låne ut det\n wants: at den som la ut oppslaget får vite det\n about: notice\n lands_in: svar\n handled_by: redaktor\n stages: [open, lest]\n done_when: [lest]\n mails: { open: owner }\n max_fields: 1\n collects:\n - { name: email, type: email }\n"; + #[test] + fn a_contact_that_is_a_group_answers_for_every_page_as_that_group() { + let plan: Plan = serde_yaml::from_str(&BOARD.replace("contact: { name: S, email: s@v.example }", "contact: { name: S, email: s@v.example, group: redaktor }")).unwrap(); + plan.validate().unwrap(); + let files = render(&plan, &fallback_words(&plan)).unwrap(); + let front: Value = serde_yaml::from_str(&files["questions/index.yaml"]).unwrap(); + assert_eq!(front["responsible"]["group"], "redaktor"); + assert_eq!(front["responsible"]["contact"], "s@v.example"); + let nobody: Plan = serde_yaml::from_str(&BOARD.replace("contact: { name: S, email: s@v.example }", "contact: { name: S, email: s@v.example, group: nobody }")).unwrap(); + assert!(nobody.validate().unwrap_err().to_string().contains("no desk")); + } + #[test] fn a_need_about_another_is_answered_from_the_boards_own_rows() { let plan: Plan = serde_yaml::from_str(BOARD).unwrap();