oximedia_clips/group/
folder.rs1use crate::clip::ClipId;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct FolderId(Uuid);
12
13impl FolderId {
14 #[must_use]
16 pub fn new() -> Self {
17 Self(Uuid::new_v4())
18 }
19
20 #[must_use]
22 pub const fn from_uuid(uuid: Uuid) -> Self {
23 Self(uuid)
24 }
25
26 #[must_use]
28 pub const fn as_uuid(&self) -> &Uuid {
29 &self.0
30 }
31}
32
33impl Default for FolderId {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl std::fmt::Display for FolderId {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}", self.0)
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Folder {
48 pub id: FolderId,
50
51 pub name: String,
53
54 pub parent_id: Option<FolderId>,
56
57 pub description: Option<String>,
59
60 clip_ids: HashSet<ClipId>,
62
63 child_folder_ids: HashSet<FolderId>,
65
66 pub created_at: DateTime<Utc>,
68
69 pub modified_at: DateTime<Utc>,
71}
72
73impl Folder {
74 #[must_use]
76 pub fn new(name: impl Into<String>) -> Self {
77 let now = Utc::now();
78 Self {
79 id: FolderId::new(),
80 name: name.into(),
81 parent_id: None,
82 description: None,
83 clip_ids: HashSet::new(),
84 child_folder_ids: HashSet::new(),
85 created_at: now,
86 modified_at: now,
87 }
88 }
89
90 #[must_use]
92 pub fn new_child(name: impl Into<String>, parent_id: FolderId) -> Self {
93 let mut folder = Self::new(name);
94 folder.parent_id = Some(parent_id);
95 folder
96 }
97
98 pub fn add_clip(&mut self, clip_id: ClipId) -> bool {
100 if self.clip_ids.insert(clip_id) {
101 self.modified_at = Utc::now();
102 true
103 } else {
104 false
105 }
106 }
107
108 pub fn remove_clip(&mut self, clip_id: &ClipId) -> bool {
110 if self.clip_ids.remove(clip_id) {
111 self.modified_at = Utc::now();
112 true
113 } else {
114 false
115 }
116 }
117
118 pub fn add_child_folder(&mut self, folder_id: FolderId) -> bool {
120 if self.child_folder_ids.insert(folder_id) {
121 self.modified_at = Utc::now();
122 true
123 } else {
124 false
125 }
126 }
127
128 pub fn remove_child_folder(&mut self, folder_id: &FolderId) -> bool {
130 if self.child_folder_ids.remove(folder_id) {
131 self.modified_at = Utc::now();
132 true
133 } else {
134 false
135 }
136 }
137
138 #[must_use]
140 pub fn clips(&self) -> Vec<ClipId> {
141 self.clip_ids.iter().copied().collect()
142 }
143
144 #[must_use]
146 pub fn child_folders(&self) -> Vec<FolderId> {
147 self.child_folder_ids.iter().copied().collect()
148 }
149
150 #[must_use]
152 pub fn clip_count(&self) -> usize {
153 self.clip_ids.len()
154 }
155
156 #[must_use]
158 pub fn child_count(&self) -> usize {
159 self.child_folder_ids.len()
160 }
161
162 #[must_use]
164 pub const fn is_root(&self) -> bool {
165 self.parent_id.is_none()
166 }
167
168 pub fn set_description(&mut self, description: impl Into<String>) {
170 self.description = Some(description.into());
171 self.modified_at = Utc::now();
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_folder_creation() {
181 let folder = Folder::new("Root Folder");
182 assert_eq!(folder.name, "Root Folder");
183 assert!(folder.is_root());
184 }
185
186 #[test]
187 fn test_child_folder() {
188 let parent = Folder::new("Parent");
189 let child = Folder::new_child("Child", parent.id);
190 assert_eq!(child.parent_id, Some(parent.id));
191 assert!(!child.is_root());
192 }
193
194 #[test]
195 fn test_folder_clips() {
196 let mut folder = Folder::new("Test");
197 let clip = ClipId::new();
198
199 assert!(folder.add_clip(clip));
200 assert_eq!(folder.clip_count(), 1);
201
202 assert!(folder.remove_clip(&clip));
203 assert_eq!(folder.clip_count(), 0);
204 }
205
206 #[test]
207 fn test_child_folders() {
208 let mut parent = Folder::new("Parent");
209 let child = Folder::new_child("Child", parent.id);
210
211 assert!(parent.add_child_folder(child.id));
212 assert_eq!(parent.child_count(), 1);
213 }
214}