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    #[must_use]
23    pub const fn is_admin(&self) -> bool {
24        matches!(self, Self::Admin)
25    }
26
27    /// Check if this role can edit
28    #[must_use]
29    pub const fn can_edit(&self) -> bool {
30        matches!(self, Self::Admin | Self::Editor)
31    }
32
33    /// Check if this role can view
34    #[must_use]
35    pub const fn can_view(&self) -> bool {
36        true // All roles can view
37    }
38}
39
40/// User account
41#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
42pub struct User {
43    /// Unique user ID
44    pub id: Uuid,
45    /// Username (unique)
46    pub username: String,
47    /// Email address (unique)
48    pub email: String,
49    /// Password hash (not serialized)
50    #[serde(skip_serializing)]
51    pub password_hash: String,
52    /// Display name
53    pub display_name: Option<String>,
54    /// Avatar URL
55    pub avatar_url: Option<String>,
56    /// Account created timestamp
57    pub created_at: DateTime<Utc>,
58    /// Last updated timestamp
59    pub updated_at: DateTime<Utc>,
60    /// Whether the account is active
61    pub is_active: bool,
62}
63
64impl User {
65    /// Create a new user (for insertion)
66    #[must_use]
67    pub fn new(username: String, email: String, password_hash: String) -> Self {
68        let now = Utc::now();
69        Self {
70            id: Uuid::new_v4(),
71            username,
72            email,
73            password_hash,
74            display_name: None,
75            avatar_url: None,
76            created_at: now,
77            updated_at: now,
78            is_active: true,
79        }
80    }
81}
82
83/// Team workspace for collaboration
84#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
85pub struct TeamWorkspace {
86    /// Unique workspace ID
87    pub id: Uuid,
88    /// Workspace name
89    pub name: String,
90    /// Description
91    pub description: Option<String>,
92    /// Owner user ID
93    pub owner_id: Uuid,
94    /// Workspace configuration (JSON)
95    pub config: serde_json::Value,
96    /// Current version number
97    pub version: i64,
98    /// Created timestamp
99    pub created_at: DateTime<Utc>,
100    /// Last updated timestamp
101    pub updated_at: DateTime<Utc>,
102    /// Whether the workspace is archived
103    pub is_archived: bool,
104}
105
106impl TeamWorkspace {
107    /// Create a new workspace
108    #[must_use]
109    pub fn new(name: String, owner_id: Uuid) -> Self {
110        let now = Utc::now();
111        Self {
112            id: Uuid::new_v4(),
113            name,
114            description: None,
115            owner_id,
116            config: serde_json::json!({}),
117            version: 1,
118            created_at: now,
119            updated_at: now,
120            is_archived: false,
121        }
122    }
123}
124
125/// Workspace membership
126#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
127pub struct WorkspaceMember {
128    /// Unique membership ID
129    pub id: Uuid,
130    /// Workspace ID
131    pub workspace_id: Uuid,
132    /// User ID
133    pub user_id: Uuid,
134    /// Role in this workspace
135    pub role: UserRole,
136    /// When the user joined
137    pub joined_at: DateTime<Utc>,
138    /// Last activity timestamp
139    pub last_activity: DateTime<Utc>,
140}
141
142impl WorkspaceMember {
143    /// Create a new workspace member
144    #[must_use]
145    pub fn new(workspace_id: Uuid, user_id: Uuid, role: UserRole) -> Self {
146        let now = Utc::now();
147        Self {
148            id: Uuid::new_v4(),
149            workspace_id,
150            user_id,
151            role,
152            joined_at: now,
153            last_activity: now,
154        }
155    }
156}
157
158/// Workspace invitation
159#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
160pub struct WorkspaceInvitation {
161    /// Unique invitation ID
162    pub id: Uuid,
163    /// Workspace ID
164    pub workspace_id: Uuid,
165    /// Email address to invite
166    pub email: String,
167    /// Role to assign
168    pub role: UserRole,
169    /// User who sent the invitation
170    pub invited_by: Uuid,
171    /// Invitation token
172    pub token: String,
173    /// Expiration timestamp
174    pub expires_at: DateTime<Utc>,
175    /// Created timestamp
176    pub created_at: DateTime<Utc>,
177    /// Whether the invitation was accepted
178    pub accepted: bool,
179}
180
181/// Active user session in a workspace
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ActiveSession {
184    /// User ID
185    pub user_id: Uuid,
186    /// Workspace ID
187    pub workspace_id: Uuid,
188    /// Session ID
189    pub session_id: Uuid,
190    /// When the session started
191    pub connected_at: DateTime<Utc>,
192    /// Last activity timestamp
193    pub last_activity: DateTime<Utc>,
194    /// Current cursor position (for presence)
195    pub cursor: Option<CursorPosition>,
196}
197
198/// Cursor position for presence awareness
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct CursorPosition {
201    /// File or resource being edited
202    pub resource: String,
203    /// Line number (if applicable)
204    pub line: Option<u32>,
205    /// Column number (if applicable)
206    pub column: Option<u32>,
207}
208
209/// Workspace fork relationship
210#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
211pub struct WorkspaceFork {
212    /// Unique fork ID
213    pub id: Uuid,
214    /// Source workspace ID (the original)
215    pub source_workspace_id: Uuid,
216    /// Forked workspace ID (the copy)
217    pub forked_workspace_id: Uuid,
218    /// When the fork was created
219    pub forked_at: DateTime<Utc>,
220    /// User who created the fork
221    pub forked_by: Uuid,
222    /// Commit ID at which fork was created (fork point)
223    pub fork_point_commit_id: Option<Uuid>,
224}
225
226impl WorkspaceFork {
227    /// Create a new fork record
228    #[must_use]
229    pub fn new(
230        source_workspace_id: Uuid,
231        forked_workspace_id: Uuid,
232        forked_by: Uuid,
233        fork_point_commit_id: Option<Uuid>,
234    ) -> Self {
235        Self {
236            id: Uuid::new_v4(),
237            source_workspace_id,
238            forked_workspace_id,
239            forked_by,
240            fork_point_commit_id,
241            forked_at: Utc::now(),
242        }
243    }
244}
245
246/// Merge status
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
248#[sqlx(type_name = "merge_status", rename_all = "lowercase")]
249#[serde(rename_all = "lowercase")]
250pub enum MergeStatus {
251    /// Merge is pending
252    Pending,
253    /// Merge is in progress
254    InProgress,
255    /// Merge completed successfully
256    Completed,
257    /// Merge has conflicts that need resolution
258    Conflict,
259    /// Merge was cancelled
260    Cancelled,
261}
262
263/// Workspace merge operation
264#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
265pub struct WorkspaceMerge {
266    /// Unique merge ID
267    pub id: Uuid,
268    /// Source workspace ID (being merged FROM)
269    pub source_workspace_id: Uuid,
270    /// Target workspace ID (being merged INTO)
271    pub target_workspace_id: Uuid,
272    /// Common ancestor commit ID
273    pub base_commit_id: Uuid,
274    /// Latest commit from source workspace
275    pub source_commit_id: Uuid,
276    /// Latest commit from target workspace
277    pub target_commit_id: Uuid,
278    /// Resulting merge commit ID (None if not completed)
279    pub merge_commit_id: Option<Uuid>,
280    /// Merge status
281    pub status: MergeStatus,
282    /// Conflict data (JSON array of conflicts)
283    pub conflict_data: Option<serde_json::Value>,
284    /// User who performed the merge
285    pub merged_by: Option<Uuid>,
286    /// When the merge was completed
287    pub merged_at: Option<DateTime<Utc>>,
288    /// When the merge was created
289    pub created_at: DateTime<Utc>,
290}
291
292impl WorkspaceMerge {
293    /// Create a new merge operation
294    #[must_use]
295    pub fn new(
296        source_workspace_id: Uuid,
297        target_workspace_id: Uuid,
298        base_commit_id: Uuid,
299        source_commit_id: Uuid,
300        target_commit_id: Uuid,
301    ) -> Self {
302        Self {
303            id: Uuid::new_v4(),
304            source_workspace_id,
305            target_workspace_id,
306            base_commit_id,
307            source_commit_id,
308            target_commit_id,
309            merge_commit_id: None,
310            status: MergeStatus::Pending,
311            conflict_data: None,
312            merged_by: None,
313            merged_at: None,
314            created_at: Utc::now(),
315        }
316    }
317}
318
319/// Conflict in a merge
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct MergeConflict {
322    /// Path to the conflicting field
323    pub path: String,
324    /// Base value (common ancestor)
325    pub base_value: Option<serde_json::Value>,
326    /// Source value (from workspace being merged)
327    pub source_value: Option<serde_json::Value>,
328    /// Target value (from current workspace)
329    pub target_value: Option<serde_json::Value>,
330    /// Conflict type
331    pub conflict_type: ConflictType,
332}
333
334/// Type of conflict
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
336#[serde(rename_all = "lowercase")]
337pub enum ConflictType {
338    /// Both sides modified the same field
339    Modified,
340    /// Field was deleted in one side, modified in the other
341    DeletedModified,
342    /// Field was added in both sides with different values
343    BothAdded,
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    // ==================== UserRole Tests ====================
351
352    #[test]
353    fn test_user_role_permissions() {
354        assert!(UserRole::Admin.is_admin());
355        assert!(UserRole::Admin.can_edit());
356        assert!(UserRole::Admin.can_view());
357
358        assert!(!UserRole::Editor.is_admin());
359        assert!(UserRole::Editor.can_edit());
360        assert!(UserRole::Editor.can_view());
361
362        assert!(!UserRole::Viewer.is_admin());
363        assert!(!UserRole::Viewer.can_edit());
364        assert!(UserRole::Viewer.can_view());
365    }
366
367    #[test]
368    fn test_user_role_equality() {
369        assert_eq!(UserRole::Admin, UserRole::Admin);
370        assert_ne!(UserRole::Admin, UserRole::Editor);
371        assert_ne!(UserRole::Editor, UserRole::Viewer);
372    }
373
374    #[test]
375    fn test_user_role_clone() {
376        let role = UserRole::Editor;
377        let cloned = role;
378        assert_eq!(cloned, UserRole::Editor);
379    }
380
381    #[test]
382    fn test_user_role_serialization() {
383        let role = UserRole::Admin;
384        let json = serde_json::to_string(&role).unwrap();
385        assert_eq!(json, r#""admin""#);
386    }
387
388    #[test]
389    fn test_user_role_deserialization() {
390        let role: UserRole = serde_json::from_str(r#""viewer""#).unwrap();
391        assert_eq!(role, UserRole::Viewer);
392    }
393
394    // ==================== User Tests ====================
395
396    #[test]
397    fn test_user_creation() {
398        let user = User::new(
399            "testuser".to_string(),
400            "test@example.com".to_string(),
401            "hashed_password".to_string(),
402        );
403
404        assert_eq!(user.username, "testuser");
405        assert_eq!(user.email, "test@example.com");
406        assert!(user.is_active);
407    }
408
409    #[test]
410    fn test_user_new_defaults() {
411        let user = User::new("user".to_string(), "user@test.com".to_string(), "hash".to_string());
412
413        assert!(user.display_name.is_none());
414        assert!(user.avatar_url.is_none());
415        assert!(user.is_active);
416    }
417
418    #[test]
419    fn test_user_has_unique_id() {
420        let user1 = User::new("u1".to_string(), "u1@test.com".to_string(), "h1".to_string());
421        let user2 = User::new("u2".to_string(), "u2@test.com".to_string(), "h2".to_string());
422
423        assert_ne!(user1.id, user2.id);
424    }
425
426    #[test]
427    fn test_user_serialization_skips_password() {
428        let user = User::new(
429            "testuser".to_string(),
430            "test@example.com".to_string(),
431            "secret_hash".to_string(),
432        );
433
434        let json = serde_json::to_string(&user).unwrap();
435        assert!(!json.contains("secret_hash"));
436        assert!(!json.contains("password_hash"));
437    }
438
439    // ==================== TeamWorkspace Tests ====================
440
441    #[test]
442    fn test_workspace_creation() {
443        let owner_id = Uuid::new_v4();
444        let workspace = TeamWorkspace::new("Test Workspace".to_string(), owner_id);
445
446        assert_eq!(workspace.name, "Test Workspace");
447        assert_eq!(workspace.owner_id, owner_id);
448        assert_eq!(workspace.version, 1);
449        assert!(!workspace.is_archived);
450    }
451
452    #[test]
453    fn test_workspace_defaults() {
454        let owner_id = Uuid::new_v4();
455        let workspace = TeamWorkspace::new("Test".to_string(), owner_id);
456
457        assert!(workspace.description.is_none());
458        assert!(!workspace.is_archived);
459        assert_eq!(workspace.config, serde_json::json!({}));
460    }
461
462    #[test]
463    fn test_workspace_has_unique_id() {
464        let owner_id = Uuid::new_v4();
465        let ws1 = TeamWorkspace::new("WS1".to_string(), owner_id);
466        let ws2 = TeamWorkspace::new("WS2".to_string(), owner_id);
467
468        assert_ne!(ws1.id, ws2.id);
469    }
470
471    // ==================== WorkspaceMember Tests ====================
472
473    #[test]
474    fn test_workspace_member_creation() {
475        let workspace_id = Uuid::new_v4();
476        let user_id = Uuid::new_v4();
477        let member = WorkspaceMember::new(workspace_id, user_id, UserRole::Editor);
478
479        assert_eq!(member.workspace_id, workspace_id);
480        assert_eq!(member.user_id, user_id);
481        assert_eq!(member.role, UserRole::Editor);
482    }
483
484    #[test]
485    fn test_workspace_member_admin() {
486        let workspace_id = Uuid::new_v4();
487        let user_id = Uuid::new_v4();
488        let member = WorkspaceMember::new(workspace_id, user_id, UserRole::Admin);
489
490        assert!(member.role.is_admin());
491        assert!(member.role.can_edit());
492    }
493
494    #[test]
495    fn test_workspace_member_viewer() {
496        let workspace_id = Uuid::new_v4();
497        let user_id = Uuid::new_v4();
498        let member = WorkspaceMember::new(workspace_id, user_id, UserRole::Viewer);
499
500        assert!(!member.role.is_admin());
501        assert!(!member.role.can_edit());
502        assert!(member.role.can_view());
503    }
504
505    // ==================== WorkspaceFork Tests ====================
506
507    #[test]
508    fn test_workspace_fork_creation() {
509        let source_id = Uuid::new_v4();
510        let forked_id = Uuid::new_v4();
511        let user_id = Uuid::new_v4();
512        let commit_id = Uuid::new_v4();
513
514        let fork = WorkspaceFork::new(source_id, forked_id, user_id, Some(commit_id));
515
516        assert_eq!(fork.source_workspace_id, source_id);
517        assert_eq!(fork.forked_workspace_id, forked_id);
518        assert_eq!(fork.forked_by, user_id);
519        assert_eq!(fork.fork_point_commit_id, Some(commit_id));
520    }
521
522    #[test]
523    fn test_workspace_fork_without_commit() {
524        let source_id = Uuid::new_v4();
525        let forked_id = Uuid::new_v4();
526        let user_id = Uuid::new_v4();
527
528        let fork = WorkspaceFork::new(source_id, forked_id, user_id, None);
529
530        assert!(fork.fork_point_commit_id.is_none());
531    }
532
533    #[test]
534    fn test_workspace_fork_has_unique_id() {
535        let fork1 = WorkspaceFork::new(Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4(), None);
536        let fork2 = WorkspaceFork::new(Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4(), None);
537
538        assert_ne!(fork1.id, fork2.id);
539    }
540
541    // ==================== MergeStatus Tests ====================
542
543    #[test]
544    fn test_merge_status_equality() {
545        assert_eq!(MergeStatus::Pending, MergeStatus::Pending);
546        assert_ne!(MergeStatus::Pending, MergeStatus::Completed);
547    }
548
549    #[test]
550    fn test_merge_status_serialization() {
551        let status = MergeStatus::InProgress;
552        let json = serde_json::to_string(&status).unwrap();
553        assert_eq!(json, r#""inprogress""#);
554    }
555
556    #[test]
557    fn test_merge_status_deserialization() {
558        let status: MergeStatus = serde_json::from_str(r#""conflict""#).unwrap();
559        assert_eq!(status, MergeStatus::Conflict);
560    }
561
562    #[test]
563    fn test_merge_status_all_variants() {
564        let variants = vec![
565            MergeStatus::Pending,
566            MergeStatus::InProgress,
567            MergeStatus::Completed,
568            MergeStatus::Conflict,
569            MergeStatus::Cancelled,
570        ];
571
572        for status in variants {
573            let json = serde_json::to_string(&status).unwrap();
574            let deserialized: MergeStatus = serde_json::from_str(&json).unwrap();
575            assert_eq!(status, deserialized);
576        }
577    }
578
579    // ==================== WorkspaceMerge Tests ====================
580
581    #[test]
582    fn test_workspace_merge_creation() {
583        let source_id = Uuid::new_v4();
584        let target_id = Uuid::new_v4();
585        let base_id = Uuid::new_v4();
586        let source_commit = Uuid::new_v4();
587        let target_commit = Uuid::new_v4();
588
589        let merge =
590            WorkspaceMerge::new(source_id, target_id, base_id, source_commit, target_commit);
591
592        assert_eq!(merge.source_workspace_id, source_id);
593        assert_eq!(merge.target_workspace_id, target_id);
594        assert_eq!(merge.base_commit_id, base_id);
595        assert_eq!(merge.status, MergeStatus::Pending);
596        assert!(merge.merge_commit_id.is_none());
597        assert!(merge.merged_by.is_none());
598        assert!(merge.merged_at.is_none());
599    }
600
601    #[test]
602    fn test_workspace_merge_default_status() {
603        let merge = WorkspaceMerge::new(
604            Uuid::new_v4(),
605            Uuid::new_v4(),
606            Uuid::new_v4(),
607            Uuid::new_v4(),
608            Uuid::new_v4(),
609        );
610
611        assert_eq!(merge.status, MergeStatus::Pending);
612        assert!(merge.conflict_data.is_none());
613    }
614
615    // ==================== ConflictType Tests ====================
616
617    #[test]
618    fn test_conflict_type_equality() {
619        assert_eq!(ConflictType::Modified, ConflictType::Modified);
620        assert_ne!(ConflictType::Modified, ConflictType::BothAdded);
621    }
622
623    #[test]
624    fn test_conflict_type_serialization() {
625        let conflict = ConflictType::DeletedModified;
626        let json = serde_json::to_string(&conflict).unwrap();
627        assert_eq!(json, r#""deletedmodified""#);
628    }
629
630    #[test]
631    fn test_conflict_type_all_variants() {
632        let variants = vec![
633            ConflictType::Modified,
634            ConflictType::DeletedModified,
635            ConflictType::BothAdded,
636        ];
637
638        for conflict_type in variants {
639            let json = serde_json::to_string(&conflict_type).unwrap();
640            let deserialized: ConflictType = serde_json::from_str(&json).unwrap();
641            assert_eq!(conflict_type, deserialized);
642        }
643    }
644
645    // ==================== MergeConflict Tests ====================
646
647    #[test]
648    fn test_merge_conflict_creation() {
649        let conflict = MergeConflict {
650            path: "/routes/users".to_string(),
651            base_value: Some(serde_json::json!({"method": "GET"})),
652            source_value: Some(serde_json::json!({"method": "POST"})),
653            target_value: Some(serde_json::json!({"method": "PUT"})),
654            conflict_type: ConflictType::Modified,
655        };
656
657        assert_eq!(conflict.path, "/routes/users");
658        assert_eq!(conflict.conflict_type, ConflictType::Modified);
659    }
660
661    #[test]
662    fn test_merge_conflict_with_none_values() {
663        let conflict = MergeConflict {
664            path: "/routes/new".to_string(),
665            base_value: None,
666            source_value: Some(serde_json::json!({"method": "GET"})),
667            target_value: Some(serde_json::json!({"method": "POST"})),
668            conflict_type: ConflictType::BothAdded,
669        };
670
671        assert!(conflict.base_value.is_none());
672        assert!(conflict.source_value.is_some());
673    }
674
675    // ==================== CursorPosition Tests ====================
676
677    #[test]
678    fn test_cursor_position_creation() {
679        let cursor = CursorPosition {
680            resource: "routes.yaml".to_string(),
681            line: Some(42),
682            column: Some(10),
683        };
684
685        assert_eq!(cursor.resource, "routes.yaml");
686        assert_eq!(cursor.line, Some(42));
687        assert_eq!(cursor.column, Some(10));
688    }
689
690    #[test]
691    fn test_cursor_position_without_line_column() {
692        let cursor = CursorPosition {
693            resource: "config.json".to_string(),
694            line: None,
695            column: None,
696        };
697
698        assert!(cursor.line.is_none());
699        assert!(cursor.column.is_none());
700    }
701
702    #[test]
703    fn test_cursor_position_serialization() {
704        let cursor = CursorPosition {
705            resource: "test.yaml".to_string(),
706            line: Some(1),
707            column: Some(5),
708        };
709
710        let json = serde_json::to_string(&cursor).unwrap();
711        let deserialized: CursorPosition = serde_json::from_str(&json).unwrap();
712
713        assert_eq!(deserialized.resource, "test.yaml");
714        assert_eq!(deserialized.line, Some(1));
715    }
716
717    // ==================== ActiveSession Tests ====================
718
719    #[test]
720    fn test_active_session_creation() {
721        let user_id = Uuid::new_v4();
722        let workspace_id = Uuid::new_v4();
723        let session_id = Uuid::new_v4();
724        let now = Utc::now();
725
726        let session = ActiveSession {
727            user_id,
728            workspace_id,
729            session_id,
730            connected_at: now,
731            last_activity: now,
732            cursor: None,
733        };
734
735        assert_eq!(session.user_id, user_id);
736        assert_eq!(session.workspace_id, workspace_id);
737        assert!(session.cursor.is_none());
738    }
739
740    #[test]
741    fn test_active_session_with_cursor() {
742        let user_id = Uuid::new_v4();
743        let workspace_id = Uuid::new_v4();
744        let session_id = Uuid::new_v4();
745        let now = Utc::now();
746
747        let cursor = CursorPosition {
748            resource: "routes.yaml".to_string(),
749            line: Some(10),
750            column: Some(5),
751        };
752
753        let session = ActiveSession {
754            user_id,
755            workspace_id,
756            session_id,
757            connected_at: now,
758            last_activity: now,
759            cursor: Some(cursor),
760        };
761
762        assert!(session.cursor.is_some());
763        assert_eq!(session.cursor.as_ref().unwrap().resource, "routes.yaml");
764    }
765
766    #[test]
767    fn test_active_session_serialization() {
768        let session = ActiveSession {
769            user_id: Uuid::new_v4(),
770            workspace_id: Uuid::new_v4(),
771            session_id: Uuid::new_v4(),
772            connected_at: Utc::now(),
773            last_activity: Utc::now(),
774            cursor: None,
775        };
776
777        let json = serde_json::to_string(&session).unwrap();
778        let deserialized: ActiveSession = serde_json::from_str(&json).unwrap();
779
780        assert_eq!(deserialized.user_id, session.user_id);
781    }
782}