pinto/service/export.rs
1//! Read-only complete-board snapshots for machine-readable export.
2
3use super::{apply_effective_points, hierarchical, lock_board, open_board};
4use crate::backlog::{BacklogItem, Status};
5use crate::error::{Error, Result};
6use crate::service::dod::read_common_dod;
7use crate::sprint::Sprint;
8use crate::storage::{BacklogItemRepository, SprintRepository};
9use std::path::Path;
10
11/// A complete read-only snapshot of all board data exposed by `export --json`.
12///
13/// The configuration is represented as JSON because the typed configuration module is an
14/// implementation detail of the library. It is the effective, validated configuration loaded by
15/// pinto, including defaults for omitted settings.
16#[derive(Debug, Clone, PartialEq)]
17pub struct BoardSnapshot {
18 /// Active PBIs in the same hierarchical priority order as `list --json`.
19 pub items: Vec<BacklogItem>,
20 /// Sprints in the same creation order as `sprint list --json`.
21 pub sprints: Vec<Sprint>,
22 /// Effective validated board configuration.
23 pub config: serde_json::Value,
24 /// Common Definition of Done, or `None` when it is unset or empty.
25 pub dod: Option<String>,
26}
27
28/// Load one read-only snapshot containing the board PBIs, Sprints, configuration, and common DoD.
29///
30/// Configuration and all repositories are opened once, so the export uses one validated backend
31/// selection. The board write lock is acquired before configuration and storage are opened and is
32/// held until the complete snapshot has been assembled. This gives automation one consistent board
33/// view while ordinary read commands remain non-blocking.
34pub async fn export_snapshot(project_dir: &Path) -> Result<BoardSnapshot> {
35 let _lock = lock_board(project_dir).await?;
36 let (board_dir, repo, config) = open_board(project_dir).await?;
37 let (mut items, sprints, dod) = tokio::try_join!(
38 BacklogItemRepository::list(&repo),
39 SprintRepository::list(&repo),
40 read_common_dod(&board_dir),
41 )?;
42
43 apply_effective_points(
44 &mut items,
45 config.points.aggregate_children,
46 &Status::new(&config.done_column),
47 );
48 let items = hierarchical(items);
49 let config = serde_json::to_value(&config).map_err(|error| Error::Parse {
50 path: board_dir.join("config.toml"),
51 message: error.to_string(),
52 })?;
53
54 Ok(BoardSnapshot {
55 items,
56 sprints,
57 config,
58 dod,
59 })
60}