//! Database models for the files table. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use utoipa::ToSchema; use uuid::Uuid; /// TranscriptEntry stored in JSONB - matches frontend TranscriptEntry #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TranscriptEntry { pub id: String, pub speaker: String, pub start: f32, pub end: f32, pub text: String, pub is_final: bool, } /// Chart type for visualization elements #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum ChartType { Line, Bar, Pie, Area, } /// Body element types for structured file content #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(tag = "type", rename_all = "camelCase")] pub enum BodyElement { /// Heading element (h1-h6) Heading { level: u8, text: String }, /// Paragraph text Paragraph { text: String }, /// Chart visualization Chart { #[serde(rename = "chartType")] chart_type: ChartType, title: Option, data: serde_json::Value, config: Option, }, /// Image element (deferred for MVP) Image { src: String, alt: Option, caption: Option, }, } /// File record from the database. #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct File { pub id: Uuid, pub owner_id: Uuid, pub name: String, pub description: Option, #[sqlx(json)] pub transcript: Vec, pub location: Option, /// AI-generated summary of the transcript pub summary: Option, /// Structured body content (headings, paragraphs, charts) #[sqlx(json)] pub body: Vec, pub created_at: DateTime, pub updated_at: DateTime, } /// Request payload for creating a new file. #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateFileRequest { /// Name of the file (auto-generated if not provided) pub name: Option, /// Optional description pub description: Option, /// Transcript entries pub transcript: Vec, /// Storage location (e.g., s3://bucket/path) - not used yet pub location: Option, } /// Request payload for updating an existing file. #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct UpdateFileRequest { /// New name (optional) pub name: Option, /// New description (optional) pub description: Option, /// New transcript (optional) pub transcript: Option>, /// AI-generated summary (optional) pub summary: Option, /// Structured body content (optional) pub body: Option>, } /// Response for file list endpoint. #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileListResponse { pub files: Vec, pub total: i64, } /// Summary of a file for list views (excludes full transcript). #[derive(Debug, Clone, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileSummary { pub id: Uuid, pub name: String, pub description: Option, pub transcript_count: usize, /// Duration derived from last transcript end time pub duration: Option, pub created_at: DateTime, pub updated_at: DateTime, } impl From for FileSummary { fn from(file: File) -> Self { let duration = file .transcript .iter() .map(|t| t.end) .fold(0.0_f32, f32::max); Self { id: file.id, name: file.name, description: file.description, transcript_count: file.transcript.len(), duration: if duration > 0.0 { Some(duration) } else { None }, created_at: file.created_at, updated_at: file.updated_at, } } }