Skip to main content

parley/services/
review_service.rs

1use std::{
2    path::PathBuf,
3    time::{SystemTime, UNIX_EPOCH},
4};
5
6use anyhow::{Context, Result, anyhow};
7
8use crate::{
9    domain::{
10        config::AppConfig,
11        review::{Author, CommentStatus, DiffSide, NewLineComment, ReviewSession, ReviewState},
12    },
13    persistence::store::Store,
14};
15
16#[derive(Debug, Clone)]
17pub struct ReviewService {
18    store: Store,
19}
20
21#[derive(Debug, Clone)]
22pub struct AddCommentInput {
23    pub file_path: String,
24    pub old_line: Option<u32>,
25    pub new_line: Option<u32>,
26    pub side: DiffSide,
27    pub body: String,
28    pub author: Author,
29}
30
31#[derive(Debug, Clone)]
32pub struct AddReplyInput {
33    pub comment_id: u64,
34    pub author: Author,
35    pub body: String,
36}
37
38impl ReviewService {
39    pub fn new(store: Store) -> Self {
40        Self { store }
41    }
42
43    pub async fn create_review(&self, name: &str) -> Result<ReviewSession> {
44        let session = ReviewSession::new(name.to_string(), now_ms()?);
45        self.store
46            .create_review(&session)
47            .await
48            .with_context(|| format!("failed to create review {name}"))?;
49        Ok(session)
50    }
51
52    pub async fn load_review(&self, name: &str) -> Result<ReviewSession> {
53        self.store
54            .load_review(name)
55            .await
56            .with_context(|| format!("failed to load review {name}"))
57    }
58
59    pub async fn load_or_create_review(&self, name: &str) -> Result<ReviewSession> {
60        match self.store.load_review(name).await {
61            Ok(review) => Ok(review),
62            Err(_) => self.create_review(name).await,
63        }
64    }
65
66    pub async fn list_reviews(&self) -> Result<Vec<String>> {
67        self.store
68            .list_reviews()
69            .await
70            .context("failed to list reviews")
71    }
72
73    pub async fn load_config(&self) -> Result<AppConfig> {
74        self.store
75            .load_config()
76            .await
77            .context("failed to load parler config")
78    }
79
80    pub async fn save_config(&self, config: &AppConfig) -> Result<()> {
81        self.store
82            .save_config(config)
83            .await
84            .context("failed to save parler config")
85    }
86
87    pub fn review_log_path(&self, review_name: &str) -> Result<PathBuf> {
88        self.store
89            .review_log_path(review_name)
90            .with_context(|| format!("failed to resolve log path for review {review_name}"))
91    }
92
93    pub async fn set_state(&self, name: &str, next: ReviewState) -> Result<ReviewSession> {
94        let mut session = self.load_review(name).await?;
95        session
96            .set_state(next, now_ms()?)
97            .map_err(|error| anyhow!(error))?;
98        self.store
99            .save_review(&session)
100            .await
101            .context("failed to save state change")?;
102        Ok(session)
103    }
104
105    pub async fn set_state_force(&self, name: &str, next: ReviewState) -> Result<ReviewSession> {
106        let mut session = self.load_review(name).await?;
107        session
108            .set_state_force(next, now_ms()?)
109            .map_err(|error| anyhow!(error))?;
110        self.store
111            .save_review(&session)
112            .await
113            .context("failed to save forced state change")?;
114        Ok(session)
115    }
116
117    pub async fn add_comment(&self, name: &str, input: AddCommentInput) -> Result<ReviewSession> {
118        let mut session = self.load_review(name).await?;
119        session.add_comment(
120            NewLineComment {
121                file_path: input.file_path,
122                old_line: input.old_line,
123                new_line: input.new_line,
124                side: input.side,
125                body: input.body,
126                author: input.author,
127            },
128            now_ms()?,
129        );
130        self.store
131            .save_review(&session)
132            .await
133            .context("failed to persist new comment")?;
134        Ok(session)
135    }
136
137    pub async fn add_reply(&self, name: &str, input: AddReplyInput) -> Result<ReviewSession> {
138        let mut session = self.load_review(name).await?;
139        session
140            .add_reply(input.comment_id, input.author, input.body, now_ms()?)
141            .map_err(|error| anyhow!(error))?;
142        self.store
143            .save_review(&session)
144            .await
145            .context("failed to persist new reply")?;
146        Ok(session)
147    }
148
149    pub async fn mark_addressed(
150        &self,
151        name: &str,
152        comment_id: u64,
153        actor: Author,
154    ) -> Result<ReviewSession> {
155        self.set_comment_status(name, comment_id, CommentStatus::Addressed, actor)
156            .await
157    }
158
159    pub async fn mark_open(
160        &self,
161        name: &str,
162        comment_id: u64,
163        actor: Author,
164    ) -> Result<ReviewSession> {
165        self.set_comment_status(name, comment_id, CommentStatus::Open, actor)
166            .await
167    }
168
169    pub async fn force_mark_addressed(&self, name: &str, comment_id: u64) -> Result<ReviewSession> {
170        let mut session = self.load_review(name).await?;
171        session
172            .set_comment_status_force(comment_id, CommentStatus::Addressed, now_ms()?)
173            .map_err(|error| anyhow!(error))?;
174        self.store
175            .save_review(&session)
176            .await
177            .context("failed to persist forced comment status")?;
178        Ok(session)
179    }
180
181    async fn set_comment_status(
182        &self,
183        name: &str,
184        comment_id: u64,
185        status: CommentStatus,
186        actor: Author,
187    ) -> Result<ReviewSession> {
188        let mut session = self.load_review(name).await?;
189        session
190            .set_comment_status(comment_id, status, actor, now_ms()?)
191            .map_err(|error| anyhow!(error))?;
192        self.store
193            .save_review(&session)
194            .await
195            .context("failed to persist comment status")?;
196        Ok(session)
197    }
198}
199
200fn now_ms() -> Result<u64> {
201    let elapsed = SystemTime::now()
202        .duration_since(UNIX_EPOCH)
203        .context("system clock is before unix epoch")?;
204    Ok(elapsed.as_millis() as u64)
205}