Yoda 0.11.5

Browser for Gemini Protocol
mod auth;
mod certificate;
mod database;
mod item;
mod memory;

use anyhow::{Result, bail};
use auth::Auth;
use database::Database;
use gtk::glib::DateTime;
use item::Item;
use memory::Memory;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use sqlite::Transaction;

/// Authorization wrapper for Gemini protocol
///
/// https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates
pub struct Identity {
    pub auth: Auth,
    pub database: Database,
    pub memory: Memory,
} // @TODO remove pub access

impl Identity {
    // Constructors

    /// Create new `Self`
    pub fn build(
        database_pool: &Pool<SqliteConnectionManager>,
        profile_identity_id: i64,
    ) -> Result<Self> {
        // Init components
        let auth = Auth::build(database_pool)?;
        let database = Database::build(database_pool, profile_identity_id);
        let memory = Memory::new();

        // Init `Self`
        let this = Self {
            auth,
            database,
            memory,
        };

        // Build initial index
        Self::index(&this)?;

        Ok(this)
    }

    // Actions

    /// Add new record to database, update memory index
    /// * return new `profile_identity_id` on success
    pub fn add(&self, pem: &str) -> Result<i64> {
        let profile_identity_id = self.database.add(pem)?;
        self.index()?;
        Ok(profile_identity_id)
    }

    /// Delete record from database including children dependencies, update memory index
    pub fn delete(&self, profile_identity_id: i64) -> Result<()> {
        self.auth.remove_ref(profile_identity_id)?;
        self.database.delete(profile_identity_id)?;
        self.index()?;
        Ok(())
    }

    /// Generate new certificate and insert record to DB, update memory index
    /// * return new `profile_identity_id` on success
    pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result<i64> {
        // Generate new certificate
        match certificate::generate(
            match time {
                Some(value) => value,
                None => (
                    DateTime::now_local()?,
                    DateTime::from_local(9999, 12, 31, 23, 59, 59.9)?, // max @TODO
                ),
            },
            name,
        ) {
            Ok(pem) => self.add(&pem),
            Err(e) => bail!("Could not create certificate: {e}"),
        }
    }

    /// Create new `Memory` index from `Database` for `Self`
    pub fn index(&self) -> Result<()> {
        // Clear previous records
        self.memory.clear()?;
        for record in self.database.records()? {
            self.memory.add(record.id, record.pem)?;
        }
        Ok(())
    }

    /// Get `Identity` match `request`
    /// * [Client certificates specification](https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates)
    /// * this function work with memory cache (not database)
    pub fn get(&self, request: &str) -> Option<Item> {
        if let Some(auth) = self.auth.get(request) {
            match self.memory.get(auth.profile_identity_id) {
                Ok(pem) => {
                    return Some(Item {
                        // scope: auth.scope,
                        pem,
                    });
                }
                Err(e) => todo!("{e}"),
            }
        }
        None
    }
}

// Tools

pub fn migrate(tx: &Transaction) -> Result<()> {
    // Migrate self components
    database::init(tx)?;

    // Delegate migration to childs
    auth::migrate(tx)?;

    // Success
    Ok(())
}