Skip to content
Draft
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f37bbb6
Create BrpClient, query and response structs
stefanvi Jun 22, 2026
b99fca5
Add correct path to brp client
stefanvi Jun 22, 2026
4a60acf
Brp deserializing (WIP)
stefanvi Jun 22, 2026
6e66433
Add verify function to brp client
stefanvi Jun 22, 2026
b7803a2
Add brp mock to docker compose
stefanvi Jun 25, 2026
ab2a922
Translate struct name and fields to English
stefanvi Jun 25, 2026
ea3b837
improve handling of international addresses
stefanvi Jun 25, 2026
dca72e9
Use structstruck for nested BRP struct definitions
stefanvi Jul 2, 2026
5c879a5
Remove first name from BrpPersonRaw
stefanvi Jul 3, 2026
99bf14d
Add basic brp check when csb importing political group
stefanvi Jul 3, 2026
d24618c
Move BrpValidation event to CsbEvent enum
stefanvi Jul 3, 2026
40edbe8
Create BrpField enum (WIP: address implementation broken)
stefanvi Jul 3, 2026
0afddb0
Fix address verification
stefanvi Jul 6, 2026
f01b70b
Refactor brp module
stefanvi Jul 6, 2026
3e34520
Run BRP import on tokio task
stefanvi Jul 6, 2026
fdf2917
Make brp verify function return a vec of Omissions
stefanvi Jul 6, 2026
3b9e896
Add test to check if ommissions are added when a person is not succes…
stefanvi Jul 9, 2026
d9669f9
Add more tracing to do_brp_verification
stefanvi Jul 9, 2026
994fef2
Fix clippy issues
stefanvi Jul 27, 2026
a975389
Merge branch 'main' into create-brp-client
stefanvi Jul 27, 2026
1be31e6
Refactor brp files into its own directory and delete orphaned files
stefanvi Jul 27, 2026
c332fc5
Add test for brp verification upon csb import
stefanvi Jul 27, 2026
594d49c
Add timeout to brp_veriy and several other small improvements
stefanvi Jul 27, 2026
5f87f99
Add BrpValidationInProgress event to CsbStore
stefanvi Jul 27, 2026
b0e90ac
Make omission test more explicit and remove typo
stefanvi Jul 29, 2026
06f11b3
Add BrpClient to the AppState
stefanvi Jul 29, 2026
2edd27b
Skip persons that have already been validated against the brp
stefanvi Jul 29, 2026
3387452
Add candidate lists to omission category for brp validation
stefanvi Jul 30, 2026
e791d4e
Add new_for_test constructor for BrpClient
stefanvi Jul 30, 2026
fcfed8a
Convert brp_validation_status from a boolean into an enum
stefanvi Jul 30, 2026
69000a8
Run BRP checks in a separate background task, that monitors completion
stefanvi Jul 31, 2026
04ec323
Use 'korteStraatnaam' instead of 'officieleStraatnaam'
stefanvi Jul 31, 2026
22036d3
Improve code structure
stefanvi Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ aes-gcm = "0.11.0" # 118M downloads (RustCrypto)
hkdf = "0.13.0" # 198M downloads (RustCrypto)
postcard = { version = "1.1.3", default-features = false, features = ["alloc"] } # 42M downloads (James Munns)
secrecy = "0.10.3" # 126M downloads (Tony Arcieri)
structstruck = "0.5.1" # 212K downloads (Julius Michaelis)
urlencoding = "2.1.3" # 213M downloads (Kornel)
zeroize = "1.9.0" # 359M downloads (RustCrypto)

Expand Down Expand Up @@ -154,6 +155,7 @@ sqlx = { workspace = true, optional = true }
subtle = { workspace = true }
textris-pdf = { workspace = true }
tokio = { workspace = true }
structstruck = { workspace = true }
tokio-util = { workspace = true }
tower-http = { workspace = true }
tracing = { workspace = true }
Expand Down
9 changes: 9 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ services:
TZ: Europe/Amsterdam
ports: ['127.0.0.1:5432:5432']
networks: [default]

personen-mock:
container_name: brp-personen-mock
image: ghcr.io/brp-api/personen-mock:2.7.0-latest
environment:
- ASPNETCORE_ENVIRONMENT=Release
- ASPNETCORE_URLS=http://+:5010
ports:
- "5010:5010"
1 change: 1 addition & 0 deletions locales/en/audit_log.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ documents:
empty: No events have been recorded yet.
event:
add_candidate_to_list: Added candidate to list
brp_validation: Validated candidate against the BRP
create_candidate_list: Created list of candidates
create_name_authorisation: Created statutory name and authorised agent
create_empty: Created empty political group
Expand Down
1 change: 1 addition & 0 deletions locales/nl/audit_log.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ documents:
empty: Er zijn nog geen gebeurtenissen vastgelegd.
event:
add_candidate_to_list: Kandidaat aan lijst toegevoegd
brp_validation: Kandidaat gevalideerd tegen de BRP
create_candidate_list: Kandidatenlijst aangemaakt
create_name_authorisation: Statutaire naam en gemachtigde aangemaakt
create_empty: Lege politieke groepering aangemaakt
Expand Down
59 changes: 56 additions & 3 deletions src/csb/import/pages/import.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use askama::Template;
use axum::{
extract::State,
Expand All @@ -6,15 +8,20 @@ use axum::{
use serde::Deserialize;

use crate::{
AppError, AppState, Context, CsbContext, CsbEvent, Form, HtmlTemplate, Locale, PgStoreData,
StreamId,
AppError, AppState, Context, CsbContext, CsbEvent, CsbStoreData, Form, HtmlTemplate, Locale,
PgStoreData, StreamId,
csb::examination::{CsbExaminationOverviewPath, CsbPoliticalGroupPath},
filters, redirect_success, trans,
filters, redirect_success,
store::Store,
structs::brp::BrpClient,
trans,
utils::parse_hash_prefix,
};

use super::{CsbCreateEmptyPath, CsbImportPath};

const BRP_COURTESY_TIMEOUT: Duration = Duration::from_secs(1);

#[derive(Template)]
#[template(path = "csb/import/pages/import.html")]
struct CsbImportTemplate {
Expand Down Expand Up @@ -121,6 +128,10 @@ async fn do_import(
})
.await?;

// TODO: get from env at higher level probably
let brp_client = BrpClient::new("http://localhost:5010", "", "haalcentraal/api/brp/personen");
Comment thread
stefanvi marked this conversation as resolved.
Outdated
do_brp_verification(&csb_store, &brp_client).await?;

Ok(redirect_success(CsbPoliticalGroupPath {
stream_id: csb_store.stream_id,
}))
Expand All @@ -141,6 +152,48 @@ pub async fn create_empty(
}))
}

pub async fn do_brp_verification(
store: &Store<CsbStoreData>,
brp_client: &BrpClient,
) -> Result<(), AppError> {
let store = store.clone();
let brp_client = brp_client.clone();

tokio::task::spawn(async move {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should await this task somewhere. If the task panics, any remaining candidates will silently be unverified.

store.data.write().brp_verification_in_progress = true;
Comment thread
stefanvi marked this conversation as resolved.
Outdated

let mut ticker = tokio::time::interval(BRP_COURTESY_TIMEOUT);
for person in store.get_persons() {
tracing::info!("Checking person {} against the brp", person.id);
ticker.tick().await;

match brp_client.verify(&person).await {
Ok(omissions) => {
for omission in omissions {
if let Err(err) = omission.create(&store).await {
tracing::error!(
"failed to record BRP omission for {}: {err}",
person.id
);
}
}
}
Err(err) => {
tracing::error!("BRP verification failed for {}: {err}", person.id);
}
}
}

tracing::info!(
"Finished checking candidates on list {:?}",
store.data.read().imported_data.political_group
);
store.data.write().brp_verification_in_progress = false;
Comment thread
stefanvi marked this conversation as resolved.
Outdated
});

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
9 changes: 9 additions & 0 deletions src/csb/store_csb/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};

use crate::{
Event, PgEvent, PgStoreData, StreamId,
persons::PersonId,
structs::csb::{Correction, Omission, OmissionId},
trans,
utils::format_hash,
Expand Down Expand Up @@ -40,6 +41,10 @@ pub enum CsbEvent {
omission_id: OmissionId,
},
UpdateCorrection(Correction),
BrpValidation {
person: PersonId,
valid: bool,
},
}

impl Event for CsbEvent {
Expand All @@ -53,6 +58,7 @@ impl Event for CsbEvent {
| CsbEvent::UpdateOmission(_)
| CsbEvent::DeleteOmission { .. } => "omission",
CsbEvent::UpdateCorrection(_) => "correction",
CsbEvent::BrpValidation { .. } => "brp_validation",
}
}

Expand All @@ -66,6 +72,7 @@ impl Event for CsbEvent {
CsbEvent::UpdateOmission(_) => "update_omission",
CsbEvent::DeleteOmission { .. } => "delete_omission",
CsbEvent::UpdateCorrection(_) => "update_correction",
CsbEvent::BrpValidation { .. } => "brep_validation",
Comment thread
stefanvi marked this conversation as resolved.
Outdated
}
}

Expand All @@ -81,6 +88,7 @@ impl Event for CsbEvent {
CsbEvent::UpdateCorrection { .. } => {
trans!("audit_log.event.update_correction", locale)
}
CsbEvent::BrpValidation { .. } => trans!("audit_log.event.brp_validation", locale),
}
}

Expand All @@ -102,6 +110,7 @@ impl Event for CsbEvent {
CsbEvent::CreateOmission(o) | CsbEvent::UpdateOmission(o) => o.description.clone(),
CsbEvent::DeleteOmission { omission_id } => omission_id.to_string(),
CsbEvent::UpdateCorrection(_) => String::new(),
CsbEvent::BrpValidation { person, .. } => person.to_string(),
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/csb/store_csb/getters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ impl CsbStore {
.collect()
}

pub fn get_persons(&self) -> Vec<Person> {
let data = self.data.read();

data.imported_data.persons.values().cloned().collect()
}

/// Return the single stored omission. Test-only helper for asserting on
/// omissions whose category has no dedicated getter (e.g. candidate lists).
#[cfg(test)]
Expand Down
5 changes: 5 additions & 0 deletions src/csb/store_csb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct CsbStoreData {
pub(crate) omissions: HashMap<OmissionId, Omission>,
pub(crate) csb_corrected_persons: HashMap<PersonId, Person>,
pub(crate) csb_corrected_display_name: Option<DisplayName>,
pub(crate) brp_validations: HashMap<PersonId, bool>,
pub(crate) brp_verification_in_progress: bool,
}

impl StoreData for CsbStoreData {
Expand Down Expand Up @@ -108,6 +110,9 @@ impl StoreData for CsbStoreData {
correction.apply(person);
}
},
CsbEvent::BrpValidation { person, valid } => {
self.brp_validations.insert(person, valid);
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/csb/store_main/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ mod event;
mod extractor;

pub use event::CsbMainEvent;
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::{
Scope, StreamId,
persons::PersonId,
store::{StoreData, StoreEvent},
};

Expand All @@ -20,6 +22,7 @@ pub const CSB_MAIN_STREAM_ID: StreamId = StreamId(uuid::Uuid::from_u128(
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CsbMainStoreData {
pub(crate) events: Vec<StoreEvent<CsbMainEvent>>,
pub(crate) brp_verifications: HashMap<PersonId, bool>,
}

impl StoreData for CsbMainStoreData {
Expand Down
Loading