parley/services/
review_service.rs1use 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}