use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Course { pub id: Uuid, pub organization_id: Uuid, pub title: String, pub description: Option, pub instructor_id: Uuid, pub pacing_mode: String, // "self_paced" or "instructor_led" pub start_date: Option>, pub end_date: Option>, pub passing_percentage: i32, pub certificate_template: Option, pub price: f64, pub currency: String, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Module { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub title: String, pub position: i32, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Lesson { pub id: Uuid, pub organization_id: Uuid, pub module_id: Uuid, pub title: String, pub content_type: String, pub content_url: Option, pub summary: Option, pub transcription: Option, pub metadata: Option, pub grading_category_id: Option, pub is_graded: bool, pub max_attempts: Option, pub allow_retry: bool, pub position: i32, pub due_date: Option>, pub important_date_type: Option, // "exam", "assignment", "milestone", etc. pub transcription_status: Option, pub is_previewable: bool, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct GradingCategory { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub name: String, pub weight: i32, // 0-100 pub drop_count: i32, pub tipo_nota_id: Option, // Maps to idTipoNota in external MySQL system pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct UserGrade { pub id: Uuid, pub user_id: Uuid, pub course_id: Uuid, pub lesson_id: Uuid, pub score: f32, // 0.0 to 1.0 pub attempts_count: i32, pub metadata: Option, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LessonInteraction { pub id: Uuid, pub organization_id: Uuid, pub user_id: Uuid, pub lesson_id: Uuid, pub video_timestamp: Option, pub event_type: String, pub metadata: Option, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct HeatmapPoint { pub second: i32, pub count: i64, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Notification { pub id: Uuid, pub organization_id: Uuid, pub user_id: Uuid, pub title: String, pub message: String, pub notification_type: String, pub is_read: bool, pub link_url: Option, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct AuditLogResponse { pub id: Uuid, pub user_id: Uuid, pub user_full_name: Option, pub action: String, pub entity_type: String, pub entity_id: Uuid, pub changes: serde_json::Value, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct Enrollment { pub id: Uuid, pub user_id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub external_id: Option, // idDetalleContrato from the external system pub enrolled_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct UserBookmark { pub id: Uuid, pub organization_id: Uuid, pub user_id: Uuid, pub course_id: Uuid, pub lesson_id: Uuid, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct CourseInstructor { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub user_id: Uuid, pub role: String, // "primary", "instructor", "assistant" pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LtiRegistration { pub id: Uuid, pub organization_id: Uuid, pub issuer: String, pub client_id: String, pub deployment_id: String, pub auth_token_url: String, pub auth_login_url: String, pub jwks_url: String, pub platform_name: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LtiResourceLink { pub id: Uuid, pub organization_id: Uuid, pub resource_link_id: String, pub course_id: Uuid, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiLaunchClaims { #[serde(rename = "iss")] pub issuer: String, #[serde(rename = "sub")] pub subject: String, #[serde(rename = "aud")] pub audience: serde_json::Value, // Can be string or array #[serde(rename = "exp")] pub expires_at: i64, #[serde(rename = "iat")] pub issued_at: i64, pub nonce: String, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/message_type")] pub message_type: String, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/version")] pub version: String, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/deployment_id")] pub deployment_id: String, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/resource_link")] pub resource_link: Option, #[serde(rename = "https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings")] pub deep_linking_settings: Option, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/context")] pub context: Option, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/roles")] pub roles: Vec, pub name: Option, pub email: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiResourceLinkClaim { pub id: String, pub title: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiContextClaim { pub id: String, pub label: Option, pub title: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiDeepLinkingSettings { pub deep_link_return_url: String, pub accept_types: Vec, pub accept_presentation_document_targets: Vec, pub accept_media_types: Option, pub accept_multiple: Option, pub accept_copy_advice: Option, pub auto_create: Option, pub title: Option, pub text: Option, pub data: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiDeepLinkingResponseClaims { #[serde(rename = "iss")] pub issuer: String, #[serde(rename = "sub")] pub subject: String, #[serde(rename = "aud")] pub audience: String, #[serde(rename = "exp")] pub expires_at: i64, #[serde(rename = "iat")] pub issued_at: i64, pub nonce: String, #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/message_type")] pub message_type: String, // "LtiDeepLinkingResponse" #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/version")] pub version: String, // "1.3.0" #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/deployment_id")] pub deployment_id: String, #[serde(rename = "https://purl.imsglobal.org/spec/lti-dl/claim/content_items")] pub content_items: Vec, #[serde(rename = "https://purl.imsglobal.org/spec/lti-dl/claim/data")] pub data: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiDeepLinkingContentItem { #[serde(rename = "type")] pub item_type: String, // "ltiResourceLink" pub title: Option, pub text: Option, pub url: Option, pub icon: Option, pub thumbnail: Option, #[serde(flatten)] pub extra: serde_json::Map, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct LtiImage { pub url: String, pub width: Option, pub height: Option, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Asset { pub id: Uuid, pub organization_id: Uuid, pub uploaded_by: Option, pub course_id: Option, pub filename: String, pub storage_path: String, pub mimetype: String, pub size_bytes: i64, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Transaction { pub id: Uuid, pub organization_id: Uuid, pub user_id: Uuid, pub course_id: Uuid, pub amount: f64, pub currency: String, pub status: String, // "pending", "success", "failure" pub provider_reference: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct User { pub id: Uuid, pub organization_id: Uuid, pub email: String, pub password_hash: String, pub full_name: String, pub role: String, // admin, instructor, student pub xp: i32, pub level: i32, pub avatar_url: Option, pub bio: Option, pub language: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct UserResponse { pub id: Uuid, pub email: String, pub full_name: String, pub role: String, pub organization_id: Uuid, pub xp: i32, pub level: i32, pub avatar_url: Option, pub bio: Option, pub language: Option, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Organization { pub id: Uuid, pub name: String, pub domain: Option, pub logo_url: Option, pub primary_color: Option, pub secondary_color: Option, pub certificate_template: Option, pub platform_name: Option, pub favicon_url: Option, pub logo_variant: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize)] pub struct AuthResponse { pub user: UserResponse, pub token: String, } #[derive(Debug, Serialize, Deserialize)] pub struct PublishedCourse { pub course: Course, pub organization: Organization, pub grading_categories: Vec, pub modules: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub instructors: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub dependencies: Option>, } #[derive(Debug, Serialize, Deserialize)] pub struct PublishedModule { pub module: Module, pub lessons: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CourseAnalytics { pub course_id: Uuid, pub total_enrollments: i64, pub average_score: f32, // 0.0-1.0 pub lessons: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LessonAnalytics { pub lesson_id: Uuid, pub lesson_title: String, pub average_score: f32, // 0.0-1.0 pub submission_count: i64, } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct CohortData { pub period: String, pub count: i64, pub completion_rate: f32, } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct RetentionData { pub lesson_id: Uuid, pub lesson_title: String, pub student_count: i64, pub completion_rate: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AdvancedAnalytics { pub cohorts: Vec, pub retention: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct DailyProgress { pub date: String, pub count: i64, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct AnalyticsFilter { pub cohort_id: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Recommendation { pub title: String, pub description: String, pub lesson_id: Option, pub priority: String, // "high", "medium", "low" pub reason: String, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RecommendationResponse { pub recommendations: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProgressStats { pub total_lessons: i64, pub completed_lessons: i64, pub progress_percentage: f32, pub daily_completions: Vec, pub estimated_completion_date: Option>, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Webhook { pub id: Uuid, pub organization_id: Uuid, pub url: String, pub events: Vec, pub secret: Option, pub is_active: bool, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct OrganizationSSOConfig { pub organization_id: Uuid, pub issuer_url: String, pub client_id: String, pub client_secret: String, pub enabled: bool, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct AuditLog { pub id: Uuid, pub organization_id: Option, pub user_id: Option, pub action: String, pub entity_type: String, pub entity_id: Uuid, pub event_type: String, pub old_data: Option, pub new_data: Option, pub ip_address: Option, pub user_agent: Option, pub changes: Option, pub created_at: DateTime, } // Discussion Forums Models #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct DiscussionThread { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub lesson_id: Option, pub author_id: Uuid, pub title: String, pub content: String, pub is_pinned: bool, pub is_locked: bool, pub view_count: i32, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct DiscussionPost { pub id: Uuid, pub organization_id: Uuid, pub thread_id: Uuid, pub parent_post_id: Option, pub author_id: Uuid, pub content: String, pub upvotes: i32, pub is_endorsed: bool, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct DiscussionVote { pub id: Uuid, pub organization_id: Uuid, pub post_id: Uuid, pub user_id: Uuid, pub vote_type: String, // 'upvote' or 'downvote' pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct DiscussionSubscription { pub id: Uuid, pub organization_id: Uuid, pub thread_id: Uuid, pub user_id: Uuid, pub created_at: DateTime, } // Response DTOs for Discussion APIs #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct ThreadWithAuthor { // Thread fields pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub lesson_id: Option, pub author_id: Uuid, pub title: String, pub content: String, pub is_pinned: bool, pub is_locked: bool, pub view_count: i32, pub created_at: DateTime, pub updated_at: DateTime, // Author info pub author_name: String, pub author_avatar: Option, // Aggregated data pub post_count: i64, pub has_endorsed_answer: bool, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct PostWithAuthor { // Post fields pub id: Uuid, pub organization_id: Uuid, pub thread_id: Uuid, pub parent_post_id: Option, pub author_id: Uuid, pub content: String, pub upvotes: i32, pub is_endorsed: bool, pub created_at: DateTime, pub updated_at: DateTime, // Author info pub author_name: String, pub author_avatar: Option, // User interaction pub user_vote: Option, // 'upvote', 'downvote', or null // Nested replies (not from DB, populated manually) #[sqlx(skip)] pub replies: Vec, } // Course Announcements #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct CourseAnnouncement { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub author_id: Uuid, pub title: String, pub content: String, pub is_pinned: bool, pub created_at: DateTime, pub updated_at: DateTime, #[sqlx(skip)] pub cohort_ids: Option>, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct AnnouncementWithAuthor { // Announcement fields pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub author_id: Uuid, pub title: String, pub content: String, pub is_pinned: bool, pub created_at: DateTime, pub updated_at: DateTime, // Author info pub author_name: String, pub author_avatar: Option, #[sqlx(skip)] pub cohort_ids: Option>, } // Student Notes #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct StudentNote { pub id: Uuid, pub user_id: Uuid, pub lesson_id: Uuid, pub content: String, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Deserialize)] pub struct SaveNotePayload { pub content: String, } // Cohorts & Groups #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Cohort { pub id: Uuid, pub organization_id: Uuid, pub name: String, pub description: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct UserCohort { pub id: Uuid, pub cohort_id: Uuid, pub user_id: Uuid, pub assigned_at: DateTime, } #[derive(Debug, Deserialize)] pub struct CreateCohortPayload { pub name: String, pub description: Option, } #[derive(Debug, Deserialize)] pub struct AddMemberPayload { pub user_id: Uuid, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct StudentGradeReport { pub user_id: Uuid, pub full_name: String, pub email: String, pub progress: f32, pub average_score: Option, pub last_active_at: Option>, } // Peer Assessment #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct CourseSubmission { pub id: Uuid, pub user_id: Uuid, pub course_id: Uuid, pub lesson_id: Uuid, pub content: String, pub submitted_at: DateTime, pub updated_at: DateTime, pub organization_id: Uuid, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct PeerReview { pub id: Uuid, pub submission_id: Uuid, pub reviewer_id: Uuid, pub score: i32, pub feedback: String, pub created_at: DateTime, pub updated_at: DateTime, pub organization_id: Uuid, } #[derive(Debug, Deserialize)] pub struct SubmitAssignmentPayload { pub content: String, } #[derive(Debug, Deserialize)] pub struct SubmitPeerReviewPayload { pub submission_id: Uuid, pub score: i32, pub feedback: String, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct SubmissionWithReviews { pub id: Uuid, pub user_id: Uuid, pub full_name: String, pub email: String, pub submitted_at: DateTime, pub review_count: i64, pub average_score: Option, } // Content Libraries #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LibraryBlock { pub id: Uuid, pub organization_id: Uuid, pub created_by: Uuid, pub name: String, pub description: Option, pub block_type: String, pub block_data: serde_json::Value, pub tags: Option>, pub usage_count: i32, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize)] pub struct CreateLibraryBlockPayload { pub name: String, pub description: Option, pub block_type: String, pub block_data: serde_json::Value, pub tags: Option>, } #[derive(Debug, Serialize, Deserialize)] pub struct UpdateLibraryBlockPayload { pub name: Option, pub description: Option, pub tags: Option>, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LibraryTemplate { pub id: Uuid, pub organization_id: Uuid, pub created_by: Uuid, pub name: String, pub description: Option, pub lesson_data: serde_json::Value, pub tags: Option>, pub usage_count: i32, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::Type, Clone, Copy, PartialEq)] #[sqlx(type_name = "dropout_risk_level", rename_all = "lowercase")] pub enum DropoutRiskLevel { Low, Medium, High, Critical, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct DropoutRisk { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub user_id: Uuid, pub risk_level: DropoutRiskLevel, pub score: f32, // 0.0 to 1.0 (Higher means higher risk) pub reasons: Option, // e.g., ["low_grades", "inactivity"] pub last_calculated_at: DateTime, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DropoutRiskReason { pub metric: String, pub value: f32, pub description: String, } #[cfg(test)] mod tests { use super::*; use serde_json::json; #[test] fn test_published_course_serialization() { let lesson_id = Uuid::new_v4(); let module_id = Uuid::new_v4(); let course_id = Uuid::new_v4(); let lesson = Lesson { id: lesson_id, organization_id: course_id, // Use course_id as proxy for org_id in test module_id, title: "Test Lesson".to_string(), content_type: "activity".to_string(), content_url: None, summary: None, transcription: None, metadata: Some(json!({ "blocks": [ { "id": "b1", "type": "fill-in-the-blanks", "content": "The capital of France is [[Paris]]." }, { "id": "b2", "type": "matching", "pairs": [{"left": "Term", "right": "Definition"}] } ] })), grading_category_id: None, is_graded: false, max_attempts: None, allow_retry: true, position: 1, due_date: None, important_date_type: None, transcription_status: None, created_at: Utc::now(), }; let pub_module = PublishedModule { module: Module { id: module_id, organization_id: course_id, course_id, title: "Test Module".to_string(), position: 1, created_at: Utc::now(), }, lessons: vec![lesson], }; let pub_course = PublishedCourse { course: Course { id: course_id, organization_id: Uuid::new_v4(), title: "Test Course".to_string(), description: None, instructor_id: Uuid::new_v4(), pacing_mode: "self_paced".to_string(), start_date: None, end_date: None, passing_percentage: 70, certificate_template: None, price: 0.0, currency: "USD".to_string(), created_at: Utc::now(), updated_at: Utc::now(), }, organization: Organization { id: Uuid::new_v4(), name: "Test Org".to_string(), domain: None, logo_url: None, primary_color: None, secondary_color: None, certificate_template: None, platform_name: None, favicon_url: None, created_at: Utc::now(), updated_at: Utc::now(), }, grading_categories: vec![], modules: vec![pub_module], instructors: None, }; let course_with_price = Course { id: course_id, organization_id: Uuid::new_v4(), title: "Test Course".to_string(), description: None, instructor_id: Uuid::new_v4(), pacing_mode: "self_paced".to_string(), start_date: None, end_date: None, passing_percentage: 70, certificate_template: None, price: 29.99, currency: "USD".to_string(), created_at: Utc::now(), updated_at: Utc::now(), }; assert_eq!(course_with_price.price, 29.99); let serialized = serde_json::to_string(&pub_course).unwrap(); let deserialized: PublishedCourse = serde_json::from_str(&serialized).unwrap(); assert_eq!(pub_course.course.title, deserialized.course.title); assert_eq!(pub_course.modules.len(), deserialized.modules.len()); assert_eq!(deserialized.modules[0].lessons[0].title, "Test Lesson"); } } // ==================== Advanced Grading / Rubrics ==================== #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Rubric { pub id: Uuid, pub organization_id: Uuid, pub course_id: Option, pub created_by: Uuid, pub name: String, pub description: Option, pub total_points: i32, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct RubricCriterion { pub id: Uuid, pub rubric_id: Uuid, pub name: String, pub description: Option, pub max_points: i32, pub position: i32, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct RubricLevel { pub id: Uuid, pub criterion_id: Uuid, pub name: String, pub description: Option, pub points: i32, pub position: i32, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LessonRubric { pub id: Uuid, pub lesson_id: Uuid, pub rubric_id: Uuid, pub is_active: bool, pub assigned_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct RubricAssessment { pub id: Uuid, pub lesson_id: Uuid, pub rubric_id: Uuid, pub user_id: Uuid, pub graded_by: Option, pub submission_id: Option, pub total_score: f32, pub max_score: i32, pub feedback: Option, pub status: String, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct AssessmentScore { pub id: Uuid, pub assessment_id: Uuid, pub criterion_id: Uuid, pub level_id: Option, pub points: f32, pub feedback: Option, pub created_at: DateTime, } // ==================== Learning Sequences / Dependencies ==================== #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct LessonDependency { pub id: Uuid, pub organization_id: Uuid, pub lesson_id: Uuid, pub prerequisite_lesson_id: Uuid, pub min_score_percentage: Option, pub created_at: DateTime, } // ==================== Live Learning (Meetings) ==================== #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Meeting { pub id: Uuid, pub organization_id: Uuid, pub course_id: Uuid, pub title: String, pub description: Option, pub provider: String, // "jitsi" | "bbb" pub meeting_id: String, // Room name or external ID pub start_at: DateTime, pub duration_minutes: i32, pub join_url: Option, pub is_active: bool, pub created_at: DateTime, pub updated_at: DateTime, } // ==================== Student portfolio & Badges ==================== #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct Badge { pub id: Uuid, pub organization_id: Uuid, pub name: String, pub description: String, pub icon_url: String, pub criteria: serde_json::Value, pub created_at: DateTime, } #[derive(Debug, Serialize, Deserialize, sqlx::FromRow, Clone)] pub struct UserBadge { pub id: Uuid, pub user_id: Uuid, pub badge_id: Uuid, pub awarded_at: DateTime, pub evidence_url: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PublicProfile { pub user_id: Uuid, pub full_name: String, pub avatar_url: Option, pub bio: Option, pub badges: Vec, pub level: i32, pub xp: i32, pub completed_courses_count: i64, }