mockforge_collab/
models.rs

1//! Core data models for collaboration
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7/// User role in a workspace
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
9#[sqlx(type_name = "user_role", rename_all = "lowercase")]
10#[serde(rename_all = "lowercase")]
11pub enum UserRole {
12    /// Full access including workspace management
13    Admin,
14    /// Can create and edit mocks
15    Editor,
16    /// Read-only access
17    Viewer,
18}
19
20impl UserRole {
21    /// Check if this role can perform admin actions
22    pub fn is_admin(&self) -> bool {
23        matches!(self, UserRole::Admin)
24    }
25
26    /// Check if this role can edit
27    pub fn can_edit(&self) -> bool {
28        matches!(self, UserRole::Admin | UserRole::Editor)
29    }
30
31    /// Check if this role can view
32    pub fn can_view(&self) -> bool {
33        true // All roles can view
34    }
35}
36
37/// User account
38#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
39pub struct User {
40    /// Unique user ID
41    pub id: Uuid,
42    /// Username (unique)
43    pub username: String,
44    /// Email address (unique)
45    pub email: String,
46    /// Password hash (not serialized)
47    #[serde(skip_serializing)]
48    pub password_hash: String,
49    /// Display name
50    pub display_name: Option<String>,
51    /// Avatar URL
52    pub avatar_url: Option<String>,
53    /// Account created timestamp
54    pub created_at: DateTime<Utc>,
55    /// Last updated timestamp
56    pub updated_at: DateTime<Utc>,
57    /// Whether the account is active
58    pub is_active: bool,
59}
60
61impl User {
62    /// Create a new user (for insertion)
63    pub fn new(username: String, email: String, password_hash: String) -> Self {
64        let now = Utc::now();
65        Self {
66            id: Uuid::new_v4(),
67            username,
68            email,
69            password_hash,
70            display_name: None,
71            avatar_url: None,
72            created_at: now,
73            updated_at: now,
74            is_active: true,
75        }
76    }
77}
78
79/// Team workspace for collaboration
80#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
81pub struct TeamWorkspace {
82    /// Unique workspace ID
83    pub id: Uuid,
84    /// Workspace name
85    pub name: String,
86    /// Description
87    pub description: Option<String>,
88    /// Owner user ID
89    pub owner_id: Uuid,
90    /// Workspace configuration (JSON)
91    pub config: serde_json::Value,
92    /// Current version number
93    pub version: i64,
94    /// Created timestamp
95    pub created_at: DateTime<Utc>,
96    /// Last updated timestamp
97    pub updated_at: DateTime<Utc>,
98    /// Whether the workspace is archived
99    pub is_archived: bool,
100}
101
102impl TeamWorkspace {
103    /// Create a new workspace
104    pub fn new(name: String, owner_id: Uuid) -> Self {
105        let now = Utc::now();
106        Self {
107            id: Uuid::new_v4(),
108            name,
109            description: None,
110            owner_id,
111            config: serde_json::json!({}),
112            version: 1,
113            created_at: now,
114            updated_at: now,
115            is_archived: false,
116        }
117    }
118}
119
120/// Workspace membership
121#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
122pub struct WorkspaceMember {
123    /// Unique membership ID
124    pub id: Uuid,
125    /// Workspace ID
126    pub workspace_id: Uuid,
127    /// User ID
128    pub user_id: Uuid,
129    /// Role in this workspace
130    pub role: UserRole,
131    /// When the user joined
132    pub joined_at: DateTime<Utc>,
133    /// Last activity timestamp
134    pub last_activity: DateTime<Utc>,
135}
136
137impl WorkspaceMember {
138    /// Create a new workspace member
139    pub fn new(workspace_id: Uuid, user_id: Uuid, role: UserRole) -> Self {
140        let now = Utc::now();
141        Self {
142            id: Uuid::new_v4(),
143            workspace_id,
144            user_id,
145            role,
146            joined_at: now,
147            last_activity: now,
148        }
149    }
150}
151
152/// Workspace invitation
153#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
154pub struct WorkspaceInvitation {
155    /// Unique invitation ID
156    pub id: Uuid,
157    /// Workspace ID
158    pub workspace_id: Uuid,
159    /// Email address to invite
160    pub email: String,
161    /// Role to assign
162    pub role: UserRole,
163    /// User who sent the invitation
164    pub invited_by: Uuid,
165    /// Invitation token
166    pub token: String,
167    /// Expiration timestamp
168    pub expires_at: DateTime<Utc>,
169    /// Created timestamp
170    pub created_at: DateTime<Utc>,
171    /// Whether the invitation was accepted
172    pub accepted: bool,
173}
174
175/// Active user session in a workspace
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ActiveSession {
178    /// User ID
179    pub user_id: Uuid,
180    /// Workspace ID
181    pub workspace_id: Uuid,
182    /// Session ID
183    pub session_id: Uuid,
184    /// When the session started
185    pub connected_at: DateTime<Utc>,
186    /// Last activity timestamp
187    pub last_activity: DateTime<Utc>,
188    /// Current cursor position (for presence)
189    pub cursor: Option<CursorPosition>,
190}
191
192/// Cursor position for presence awareness
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct CursorPosition {
195    /// File or resource being edited
196    pub resource: String,
197    /// Line number (if applicable)
198    pub line: Option<u32>,
199    /// Column number (if applicable)
200    pub column: Option<u32>,
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_user_role_permissions() {
209        assert!(UserRole::Admin.is_admin());
210        assert!(UserRole::Admin.can_edit());
211        assert!(UserRole::Admin.can_view());
212
213        assert!(!UserRole::Editor.is_admin());
214        assert!(UserRole::Editor.can_edit());
215        assert!(UserRole::Editor.can_view());
216
217        assert!(!UserRole::Viewer.is_admin());
218        assert!(!UserRole::Viewer.can_edit());
219        assert!(UserRole::Viewer.can_view());
220    }
221
222    #[test]
223    fn test_user_creation() {
224        let user = User::new(
225            "testuser".to_string(),
226            "test@example.com".to_string(),
227            "hashed_password".to_string(),
228        );
229
230        assert_eq!(user.username, "testuser");
231        assert_eq!(user.email, "test@example.com");
232        assert!(user.is_active);
233    }
234
235    #[test]
236    fn test_workspace_creation() {
237        let owner_id = Uuid::new_v4();
238        let workspace = TeamWorkspace::new("Test Workspace".to_string(), owner_id);
239
240        assert_eq!(workspace.name, "Test Workspace");
241        assert_eq!(workspace.owner_id, owner_id);
242        assert_eq!(workspace.version, 1);
243        assert!(!workspace.is_archived);
244    }
245
246    #[test]
247    fn test_workspace_member_creation() {
248        let workspace_id = Uuid::new_v4();
249        let user_id = Uuid::new_v4();
250        let member = WorkspaceMember::new(workspace_id, user_id, UserRole::Editor);
251
252        assert_eq!(member.workspace_id, workspace_id);
253        assert_eq!(member.user_id, user_id);
254        assert_eq!(member.role, UserRole::Editor);
255    }
256}