summaryrefslogblamecommitdiff
path: root/makima/src/db/models.rs
blob: 135ae75790f76bb45b4024bd37326954adb9e31d (plain) (tree)



















                                                                      

































                                                         










                                                     




                                                              



























                                                                



                                          









































                                                                         
//! 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<String>,
        data: serde_json::Value,
        config: Option<serde_json::Value>,
    },
    /// Image element (deferred for MVP)
    Image {
        src: String,
        alt: Option<String>,
        caption: Option<String>,
    },
}

/// 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<String>,
    #[sqlx(json)]
    pub transcript: Vec<TranscriptEntry>,
    pub location: Option<String>,
    /// AI-generated summary of the transcript
    pub summary: Option<String>,
    /// Structured body content (headings, paragraphs, charts)
    #[sqlx(json)]
    pub body: Vec<BodyElement>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// 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<String>,
    /// Optional description
    pub description: Option<String>,
    /// Transcript entries
    pub transcript: Vec<TranscriptEntry>,
    /// Storage location (e.g., s3://bucket/path) - not used yet
    pub location: Option<String>,
}

/// Request payload for updating an existing file.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateFileRequest {
    /// New name (optional)
    pub name: Option<String>,
    /// New description (optional)
    pub description: Option<String>,
    /// New transcript (optional)
    pub transcript: Option<Vec<TranscriptEntry>>,
    /// AI-generated summary (optional)
    pub summary: Option<String>,
    /// Structured body content (optional)
    pub body: Option<Vec<BodyElement>>,
}

/// Response for file list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileListResponse {
    pub files: Vec<FileSummary>,
    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<String>,
    pub transcript_count: usize,
    /// Duration derived from last transcript end time
    pub duration: Option<f32>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl From<File> 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,
        }
    }
}