1#![warn(missing_docs)]
44#![allow(clippy::cast_precision_loss)]
45#![allow(clippy::cast_possible_truncation)]
46#![allow(clippy::cast_sign_loss)]
47#![allow(clippy::unused_async)]
48#![allow(clippy::struct_excessive_bools)]
49#![allow(clippy::similar_names)]
50#![allow(clippy::match_same_arms)]
51#![allow(clippy::unused_self)]
52#![allow(clippy::missing_errors_doc)]
53#![allow(clippy::format_push_string)]
54#![allow(clippy::match_like_matches_macro)]
55
56use chrono::{DateTime, Utc};
57use serde::{Deserialize, Serialize};
58use std::collections::HashMap;
59use uuid::Uuid;
60
61pub mod annotation;
62pub mod annotation_export;
63pub mod annotations;
64pub mod approval;
65pub mod approval_workflow;
66pub mod batch_ops;
67pub mod change;
68pub mod comment;
69pub mod comment_thread;
70pub mod compare;
71pub mod comparison_mode;
72pub mod deadline;
73pub mod delivery;
74pub mod drawing;
75pub mod export;
76pub mod feedback_round;
77pub mod marker;
78pub mod notify;
79pub mod offline_review;
80pub mod realtime;
81pub mod realtime_delta;
82pub mod report;
83pub mod review_api;
84pub mod review_automation;
85pub mod review_checklist;
86pub mod review_comparator;
87pub mod review_diff;
88pub mod review_export;
89pub mod review_history;
90pub mod review_link;
91pub mod review_metrics;
92pub mod review_notification_rule;
93pub mod review_permission;
94pub mod review_playlist;
95pub mod review_priority;
96pub mod review_session;
97pub mod review_snapshot;
98pub mod review_status;
99pub mod review_tag;
100pub mod review_template;
101pub mod session;
102pub mod status;
103pub mod task;
104pub mod timeline_note;
105pub mod version;
106pub mod version_compare;
107pub mod version_lazy;
108
109pub mod error;
111
112pub use compare::{
113 apply_compare_filter, CompareFilter, CompareLayout, CompareResult, CompareVersion, DiffStats,
114 MediaComparator, WipeAngle,
115};
116pub use error::{ReviewError, ReviewResult};
117pub use session::ReviewSession;
118pub use timeline_note::{NoteType, TimeRange, TimelineNote, TimelineNoteCollection};
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
122pub struct SessionId(Uuid);
123
124impl SessionId {
125 #[must_use]
127 pub fn new() -> Self {
128 Self(Uuid::new_v4())
129 }
130
131 #[must_use]
133 pub fn as_uuid(&self) -> &Uuid {
134 &self.0
135 }
136}
137
138impl Default for SessionId {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl std::fmt::Display for SessionId {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 write!(f, "{}", self.0)
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
152pub struct CommentId(Uuid);
153
154impl CommentId {
155 #[must_use]
157 pub fn new() -> Self {
158 Self(Uuid::new_v4())
159 }
160
161 #[must_use]
163 pub fn as_uuid(&self) -> &Uuid {
164 &self.0
165 }
166}
167
168impl Default for CommentId {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl std::fmt::Display for CommentId {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 write!(f, "{}", self.0)
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
182pub struct DrawingId(Uuid);
183
184impl DrawingId {
185 #[must_use]
187 pub fn new() -> Self {
188 Self(Uuid::new_v4())
189 }
190
191 #[must_use]
193 pub fn as_uuid(&self) -> &Uuid {
194 &self.0
195 }
196}
197
198impl Default for DrawingId {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204impl std::fmt::Display for DrawingId {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 write!(f, "{}", self.0)
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
212pub struct TaskId(Uuid);
213
214impl TaskId {
215 #[must_use]
217 pub fn new() -> Self {
218 Self(Uuid::new_v4())
219 }
220
221 #[must_use]
223 pub fn as_uuid(&self) -> &Uuid {
224 &self.0
225 }
226}
227
228impl Default for TaskId {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234impl std::fmt::Display for TaskId {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 write!(f, "{}", self.0)
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
242pub struct VersionId(Uuid);
243
244impl VersionId {
245 #[must_use]
247 pub fn new() -> Self {
248 Self(Uuid::new_v4())
249 }
250
251 #[must_use]
253 pub fn as_uuid(&self) -> &Uuid {
254 &self.0
255 }
256}
257
258impl Default for VersionId {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl std::fmt::Display for VersionId {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 write!(f, "{}", self.0)
267 }
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct User {
273 pub id: String,
275 pub name: String,
277 pub email: String,
279 pub role: UserRole,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
285pub enum UserRole {
286 Owner,
288 Approver,
290 Reviewer,
292 Observer,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
298pub enum AnnotationType {
299 General,
301 Issue,
303 Suggestion,
305 Question,
307 Approval,
309 Rejection,
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
315pub enum WorkflowType {
316 Simple,
318 MultiStage,
320 Parallel,
322 Sequential,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct SessionConfig {
329 pub title: String,
331 pub content_id: String,
333 pub workflow_type: WorkflowType,
335 pub description: Option<String>,
337 pub deadline: Option<DateTime<Utc>>,
339 pub metadata: HashMap<String, String>,
341}
342
343impl SessionConfig {
344 #[must_use]
346 pub fn builder() -> SessionConfigBuilder {
347 SessionConfigBuilder::default()
348 }
349}
350
351#[derive(Default)]
353pub struct SessionConfigBuilder {
354 title: Option<String>,
355 content_id: Option<String>,
356 workflow_type: Option<WorkflowType>,
357 description: Option<String>,
358 deadline: Option<DateTime<Utc>>,
359 metadata: HashMap<String, String>,
360}
361
362impl SessionConfigBuilder {
363 #[must_use]
365 pub fn title(mut self, title: impl Into<String>) -> Self {
366 self.title = Some(title.into());
367 self
368 }
369
370 #[must_use]
372 pub fn content_id(mut self, id: impl Into<String>) -> Self {
373 self.content_id = Some(id.into());
374 self
375 }
376
377 #[must_use]
379 pub fn workflow_type(mut self, workflow: WorkflowType) -> Self {
380 self.workflow_type = Some(workflow);
381 self
382 }
383
384 #[must_use]
386 pub fn description(mut self, desc: impl Into<String>) -> Self {
387 self.description = Some(desc.into());
388 self
389 }
390
391 #[must_use]
393 pub fn deadline(mut self, deadline: DateTime<Utc>) -> Self {
394 self.deadline = Some(deadline);
395 self
396 }
397
398 #[must_use]
400 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
401 self.metadata.insert(key.into(), value.into());
402 self
403 }
404
405 pub fn build(self) -> crate::error::ReviewResult<SessionConfig> {
412 let title = self
413 .title
414 .filter(|t| !t.is_empty())
415 .ok_or_else(|| crate::error::ReviewError::InvalidConfig("title is required".into()))?;
416 let content_id = self.content_id.filter(|c| !c.is_empty()).ok_or_else(|| {
417 crate::error::ReviewError::InvalidConfig("content_id is required".into())
418 })?;
419 Ok(SessionConfig {
420 title,
421 content_id,
422 workflow_type: self.workflow_type.unwrap_or(WorkflowType::Simple),
423 description: self.description,
424 deadline: self.deadline,
425 metadata: self.metadata,
426 })
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433
434 #[test]
435 fn test_session_id_creation() {
436 let id1 = SessionId::new();
437 let id2 = SessionId::new();
438 assert_ne!(id1, id2);
439 }
440
441 #[test]
442 fn test_comment_id_creation() {
443 let id1 = CommentId::new();
444 let id2 = CommentId::new();
445 assert_ne!(id1, id2);
446 }
447
448 #[test]
449 fn test_session_config_builder() {
450 let config = SessionConfig::builder()
451 .title("Test Session")
452 .content_id("video-123")
453 .workflow_type(WorkflowType::Simple)
454 .description("Test description")
455 .metadata("key", "value")
456 .build()
457 .expect("valid config");
458
459 assert_eq!(config.title, "Test Session");
460 assert_eq!(config.content_id, "video-123");
461 assert_eq!(config.workflow_type, WorkflowType::Simple);
462 assert_eq!(config.description, Some("Test description".to_string()));
463 assert_eq!(config.metadata.get("key"), Some(&"value".to_string()));
464 }
465
466 #[test]
467 fn test_session_config_builder_missing_title_errors() {
468 let result = SessionConfig::builder().content_id("video-123").build();
469 assert!(result.is_err());
470 }
471
472 #[test]
473 fn test_session_config_builder_missing_content_id_errors() {
474 let result = SessionConfig::builder().title("My Review").build();
475 assert!(result.is_err());
476 }
477
478 #[test]
479 fn test_user_role_equality() {
480 assert_eq!(UserRole::Owner, UserRole::Owner);
481 assert_ne!(UserRole::Owner, UserRole::Reviewer);
482 }
483
484 #[test]
485 fn test_annotation_type_equality() {
486 assert_eq!(AnnotationType::Issue, AnnotationType::Issue);
487 assert_ne!(AnnotationType::Issue, AnnotationType::Suggestion);
488 }
489}