vs-store 0.1.10

SQLite-backed durable state for vibesurfer.
Documentation
//! `pages` table CRUD.

use rusqlite::params;

use super::{epoch_secs, Store};
use crate::error::{Result, StoreError};
use crate::types::Page;

impl Store {
    pub fn create_page(&mut self, id: &str, session_id: &str, url: &str) -> Result<Page> {
        let now = epoch_secs();
        self.conn().execute(
            "INSERT INTO pages(id, session_id, url, title, last_token, last_dom_hash,
                               last_seen_at, closed_at)
             VALUES (?1, ?2, ?3, NULL, NULL, NULL, ?4, NULL)",
            params![id, session_id, url, now],
        )?;
        Ok(Page {
            id: id.to_string(),
            session_id: session_id.to_string(),
            url: url.to_string(),
            title: None,
            last_token: None,
            last_dom_hash: None,
            last_seen_at: now,
            closed_at: None,
        })
    }

    pub fn close_page(&mut self, id: &str) -> Result<()> {
        let now = epoch_secs();
        let n = self.conn().execute(
            "UPDATE pages SET closed_at=?2 WHERE id=?1 AND closed_at IS NULL",
            params![id, now],
        )?;
        if n == 0 {
            return Err(StoreError::NotFound {
                kind: "page",
                id: id.to_string(),
            });
        }
        Ok(())
    }

    pub fn update_page_token(
        &mut self,
        id: &str,
        token: &str,
        dom_hash: &str,
        title: Option<&str>,
    ) -> Result<()> {
        let now = epoch_secs();
        let n = self.conn().execute(
            "UPDATE pages
                SET last_token=?2, last_dom_hash=?3, last_seen_at=?4,
                    title=COALESCE(?5, title)
              WHERE id=?1",
            params![id, token, dom_hash, now, title],
        )?;
        if n == 0 {
            return Err(StoreError::NotFound {
                kind: "page",
                id: id.to_string(),
            });
        }
        Ok(())
    }

    pub fn get_page(&self, id: &str) -> Result<Option<Page>> {
        let mut stmt = self.conn().prepare("SELECT * FROM pages WHERE id=?1")?;
        let mut rows = stmt.query([id])?;
        if let Some(row) = rows.next()? {
            Ok(Some(Page::from_row(row)?))
        } else {
            Ok(None)
        }
    }

    pub fn list_pages(&self, session_id: &str) -> Result<Vec<Page>> {
        let mut stmt = self
            .conn()
            .prepare("SELECT * FROM pages WHERE session_id=?1 ORDER BY last_seen_at DESC")?;
        let rows = stmt.query_map([session_id], Page::from_row)?;
        Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
    }
}