From 7842e4eb3e99669a30d920048f6f156ffebd72f5 Mon Sep 17 00:00:00 2001 From: fluo10 Date: Mon, 15 Sep 2025 19:42:48 +0900 Subject: [PATCH] Update local data and migration --- core/src/data/local/authorization/mod.rs | 91 ------------------- core/src/data/local/authorization/request.rs | 8 -- core/src/data/local/authorization/response.rs | 12 --- .../local/authorization_request/common.rs | 0 .../data/local/authorization_request/mod.rs | 13 +++ .../local/authorization_request/received.rs | 80 ++++++++++++++++ .../data/local/authorization_request/sent.rs | 67 ++++++++++++++ core/src/data/local/migration/v1.rs | 21 +++++ core/src/data/local/mod.rs | 27 +++--- core/src/data/local/peer.rs | 80 ++++++++++++++++ 10 files changed, 272 insertions(+), 127 deletions(-) delete mode 100644 core/src/data/local/authorization/mod.rs delete mode 100644 core/src/data/local/authorization/request.rs delete mode 100644 core/src/data/local/authorization/response.rs create mode 100644 core/src/data/local/authorization_request/common.rs create mode 100644 core/src/data/local/authorization_request/mod.rs create mode 100644 core/src/data/local/authorization_request/received.rs create mode 100644 core/src/data/local/authorization_request/sent.rs create mode 100644 core/src/data/local/peer.rs diff --git a/core/src/data/local/authorization/mod.rs b/core/src/data/local/authorization/mod.rs deleted file mode 100644 index 79a14ff..0000000 --- a/core/src/data/local/authorization/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Structs about authorization. - -mod request; -mod response; - -use std::os::unix::raw::time_t; - -use chrono::{DateTime, Local, NaiveDateTime}; -use iroh::NodeId; -pub use request::*; -pub use response::*; -use rusqlite::{params, types::FromSqlError, Connection}; -use uuid::Uuid; - -use crate::data::local::RusqliteRecord; - -/// On going authorization -pub struct Authorization { - request_id: Uuid, - node_id: NodeId, - node_info: Option, - passcode: Option, - created_at: DateTime, - updated_at: DateTime, -} -static TABLE_NAME: &str = "authorization"; -static DEFAULT_COLUMNS: [&str;5] = [ - "request_id", - "node_id", - "created_at", - "updated_at" -]; - -impl Authorization { - pub fn new_sent(node_id: NodeId, passcode: String) -> Self { - let timestamp = Local::now(); - Self { - node_id: node_id, - passcode: passcode, - created_at: timestamp.clone(), - updated_at: timestamp - } - } - pub fn new_received(node_id:) - pub fn get_by_node_id(node_id: NodeId, connection: &Connection) -> Result { - connection.query_row( - "SELECT node_id, passcode, created_at, updated_at FROM authorizaation WHRE node_id=(?1)", - params![node_id.as_bytes()], - Self::from_row - ) - } - - -} -impl RusqliteRecord for Authorization { - fn from_row(row: &rusqlite::Row<'_>) -> Result { - let created_at: NaiveDateTime = row.get(2)?; - let updated_at: NaiveDateTime = row.get(3)?; - let node_id: Vec = row.get(0)?; - Ok(Self { - node_id: NodeId::from_bytes(node_id[..32].try_into().or_else(|e| { - Err(rusqlite::types::FromSqlError::InvalidBlobSize { - expected_size: 32, - blob_size: node_id.len() - }) - })?).or(Err(FromSqlError::InvalidType))?, - passcode: row.get(1)?, - created_at: DateTime::from(created_at.and_utc()), - updated_at: DateTime::from(updated_at.and_utc()), - }) - } - fn insert(&self, connection: &rusqlite::Connection) -> Result<(), rusqlite::Error> { - connection.execute( - "INSERT INTO authorization (node_id, passcode, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)", - (&self.node_id.as_bytes(), &self.passcode, &self.created_at.naive_utc(), &self.updated_at.naive_utc()), - )?; - Ok(()) - } - fn get_all(connection: &rusqlite::Connection) -> Result, rusqlite::Error> { - let mut stmt = connection.prepare(&(String::from("SELECT ") + &DEFAULT_COLUMNS.join(", ") + " FROM " + TABLE_NAME))?; - let rows = stmt.query_map( - [], - Self::from_row - )?; - let mut result= Vec::new(); - for row in rows { - result.push(row?); - } - Ok(result) - } -} \ No newline at end of file diff --git a/core/src/data/local/authorization/request.rs b/core/src/data/local/authorization/request.rs deleted file mode 100644 index 91216c1..0000000 --- a/core/src/data/local/authorization/request.rs +++ /dev/null @@ -1,8 +0,0 @@ -use iroh::NodeId; - -/// Request of node authentication. -#[derive(Debug, Clone)] -pub struct AuthorizationRequest { - sender_id: NodeId, - sender_info: String, -} diff --git a/core/src/data/local/authorization/response.rs b/core/src/data/local/authorization/response.rs deleted file mode 100644 index d0aa70b..0000000 --- a/core/src/data/local/authorization/response.rs +++ /dev/null @@ -1,12 +0,0 @@ -use iroh::NodeId; - -/// Response of node authentication. -#[derive(Debug, Clone)] -pub struct AuthorizationResponse { - sender_id: NodeId, - passcode: String, -} - - - - diff --git a/core/src/data/local/authorization_request/common.rs b/core/src/data/local/authorization_request/common.rs new file mode 100644 index 0000000..e69de29 diff --git a/core/src/data/local/authorization_request/mod.rs b/core/src/data/local/authorization_request/mod.rs new file mode 100644 index 0000000..eba1521 --- /dev/null +++ b/core/src/data/local/authorization_request/mod.rs @@ -0,0 +1,13 @@ +//! Structs about authorization. + +mod sent; +mod received; + +use std::os::unix::raw::time_t; + +use chrono::{DateTime, Local, NaiveDateTime}; +use iroh::NodeId; +pub use sent::*; +pub use received::*; +use rusqlite::{params, types::FromSqlError, Connection}; +use uuid::Uuid; diff --git a/core/src/data/local/authorization_request/received.rs b/core/src/data/local/authorization_request/received.rs new file mode 100644 index 0000000..25ef920 --- /dev/null +++ b/core/src/data/local/authorization_request/received.rs @@ -0,0 +1,80 @@ +use caretta_id::{DoubleId, SingleId}; +use chrono::{DateTime, Local, NaiveDateTime}; +use iroh::{NodeId, PublicKey}; + +use crate::{data::local::LocalModel, global::LOCAL_DATABASE_CONNECTION}; + +/// Response of node authentication. +#[derive(Debug, Clone)] +pub struct ReceivedAuthorizationRequest { + request_id: SingleId, + public_key: PublicKey, + node_info: String, + created_at: DateTime, + responded_at: Option>, +} + + + +impl ReceivedAuthorizationRequest { + pub fn get_by_request_id(request_id: SingleId) -> Result { + todo!() + } + pub fn get_by_public_key(public_key: PublicKey) -> Result { + todo!() + } + pub fn get_by_local_peer_id(local_peer_id: DoubleId) -> Result { + todo!() + } +} + +impl LocalModel for ReceivedAuthorizationRequest { + const TABLE_NAME: &str = "received_authorization_request"; + const DEFAULT_COLUMNS: &[&str] = &[ + "request_id", + "public_key", + "node_info", + "created_at", + "responded_at", + ]; + fn from_default_row(row: &rusqlite::Row<'_>) -> Result { + let created_at: NaiveDateTime = row.get(3)?; + let responded_at: Option = row.get(4)?; + Ok(Self { + request_id: row.get(0)?, + public_key: PublicKey::from_bytes(&row.get(1)?).map_err(|e| rusqlite::types::FromSqlError::Other(Box::new(e)))?, + node_info: row.get(2)?, + created_at: DateTime::from(created_at.and_utc()), + responded_at: responded_at.map(|x| DateTime::from(x.and_utc())), + }) + } + fn insert(&self) -> Result<(), rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + + connection.execute( + &("INSERT INTO ".to_string() + Self::TABLE_NAME + " (" + &Self::DEFAULT_COLUMNS.join(", ") + ") VALUES (?1, ?2, ?3, ?4, ?5)"), + ( + &self.request_id, + &self.public_key.as_bytes(), + &self.node_info, + &self.created_at.naive_utc(), + &self.responded_at.map(|x| x.naive_utc()), + ) + )?; + Ok(()) + } + fn get_all() -> Result, rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + + let mut stmt = connection.prepare(&(String::from("SELECT ") + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; + let rows = stmt.query_map( + [], + Self::from_default_row + )?; + let mut result= Vec::new(); + for row in rows { + result.push(row?); + } + Ok(result) + } +} \ No newline at end of file diff --git a/core/src/data/local/authorization_request/sent.rs b/core/src/data/local/authorization_request/sent.rs new file mode 100644 index 0000000..e705693 --- /dev/null +++ b/core/src/data/local/authorization_request/sent.rs @@ -0,0 +1,67 @@ +use caretta_id::SingleId; +use chrono::{DateTime, Local, NaiveDateTime}; +use iroh::{NodeId, PublicKey}; +use rusqlite::types::FromSqlError; + +use crate::{data::local::LocalModel, global::LOCAL_DATABASE_CONNECTION}; + +/// Request of node authentication. +#[derive(Debug, Clone)] +pub struct SentAuthorizationRequest { + request_id: SingleId, + public_key: PublicKey, + passcode: String, + created_at: DateTime, + sent_at: Option>, +} + +impl LocalModel for SentAuthorizationRequest { + + const TABLE_NAME: &str = "sent_authorization"; + const DEFAULT_COLUMNS: &[&str] = &[ + "request_id", + "public_key", + "passcode", + "created_at", + "sent_at" + ]; + fn from_default_row(row: &rusqlite::Row<'_>) -> Result { + let created_at: NaiveDateTime = row.get(2)?; + let sent_at: Option = row.get(3)?; + Ok(Self { + request_id: row.get(0)?, + public_key: PublicKey::from_bytes(&row.get(1)?).map_err(|e| FromSqlError::Other(Box::new(e)))?, + passcode: row.get(2)?, + created_at: DateTime::from(created_at.and_utc()), + sent_at: sent_at.map(|x| DateTime::from(x.and_utc())), + }) + } + fn insert(&self) -> Result<(), rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + connection.execute( + &(String::from("INSERT INTO ") + Self::TABLE_NAME + " (" + &Self::DEFAULT_COLUMNS.join(", ") + ") VALUES (?1, ?2, ?3, ?4, ?5)"), + ( + &self.request_id, + &self.public_key.as_bytes(), + &self.passcode, + &self.created_at.naive_utc(), + &self.sent_at.map(|x| x.naive_utc()) + ), + )?; + Ok(()) + } + fn get_all() -> Result, rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + + let mut stmt = connection.prepare(&(String::from("SELECT ") + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; + let rows = stmt.query_map( + [], + Self::from_default_row + )?; + let mut result= Vec::new(); + for row in rows { + result.push(row?); + } + Ok(result) + } +} \ No newline at end of file diff --git a/core/src/data/local/migration/v1.rs b/core/src/data/local/migration/v1.rs index 07c7f12..f525d36 100644 --- a/core/src/data/local/migration/v1.rs +++ b/core/src/data/local/migration/v1.rs @@ -4,6 +4,27 @@ pub fn migrate(con: &mut Connection) -> Result<(), Error>{ let tx = con.transaction()?; tx.execute_batch( "BEGIN; + CREATE TABLE peer ( + id INTEGER PRIMARY KEY, + local_id INTEGER NOT NULL UNIQUE, + public_key BLOB UNIQUE NOT NULL, + ); + CREATE TABLE received_authorization_request ( + id INTEGER PRIMARY KEY, + request_id INTEGER NOT NULL UNIQUE, + public_key BLOB NOT NULL UNIQUE, + node_info TEXT, + created_at TEXT NOT NULL, + responded_at TEXT + ); + CREATE TABLE sent_authorization_request ( + id INTEGER PRIMARY KEY, + request_id INTEGER NOT NULL UNIQUE, + public_key. BLOB NOT NULL UNIQUE, + passcode TEXT NOT NULL, + created_at TEXT NOT NULL, + sent_at TEXT + ); CREATE TABLE authorized_peer ( id INTEGER PRIMARY KEY, node_id BLOB NOT NULL UNIQUE, diff --git a/core/src/data/local/mod.rs b/core/src/data/local/mod.rs index 5485a0e..e7ae821 100644 --- a/core/src/data/local/mod.rs +++ b/core/src/data/local/mod.rs @@ -1,4 +1,5 @@ -mod authorization; +mod authorization_request; +mod peer; pub mod migration; use std::{cell::OnceCell, iter::Map, path::Path, sync::{LazyLock, OnceLock}}; @@ -8,20 +9,14 @@ use rusqlite::{ffi::Error, Connection, MappedRows, Row}; use crate::{config::StorageConfig, global::{CONFIG, LOCAL_DATABASE_CONNECTION}}; -pub use authorization::*; +pub use authorization_request::*; -pub trait RusqliteRecord: Sized { - fn insert(&self, connection: &Connection) -> Result<(), rusqlite::Error>; - fn from_row(row: &Row<'_>) -> Result; - fn get_all(connection: &Connection) -> Result, rusqlite::Error>; -} - -pub trait LocalRecord : RusqliteRecord{ - fn insert_global(&self) -> Result<(), rusqlite::Error> { - self.insert(&LOCAL_DATABASE_CONNECTION.get_unchecked()) - } - fn get_all_global() -> Result, rusqlite::Error> { - let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - Self::get_all(&connection) - } +/// Model trait for local database data. +/// use LOCAL_DATABASE_CONNECTION for database connection. +pub trait LocalModel: Sized { + const TABLE_NAME: &str; + const DEFAULT_COLUMNS: &[&str]; + fn insert(&self) -> Result<(), rusqlite::Error>; + fn from_default_row(row: &Row<'_>) -> Result; + fn get_all() -> Result, rusqlite::Error>; } \ No newline at end of file diff --git a/core/src/data/local/peer.rs b/core/src/data/local/peer.rs new file mode 100644 index 0000000..8285e48 --- /dev/null +++ b/core/src/data/local/peer.rs @@ -0,0 +1,80 @@ +//! Structs about cached peer. + +use std::os::unix::raw::time_t; + +use caretta_id::DoubleId; +use chrono::{DateTime, Local, NaiveDateTime}; +use iroh::{NodeId, PublicKey}; +use rusqlite::{params, types::FromSqlError, Connection}; +use uuid::Uuid; + +use crate::{data::local::LocalModel, global::LOCAL_DATABASE_CONNECTION}; + +/// Peer information cached in local database. +/// +/// - Currently this only contain local id and public key (=node id) of iroh. +/// - This is a junction table enable to use caretta-id to specify items in the UI, especially on the CLI. +/// - Actual peer information is managed by iroh endpoint and not contained in this model. +/// - Once a peer is authorized, it is assigned a global (=synced) ID as authorized_peer so essentially this local id targets unauthorized peers. +/// +pub struct Peer { + pub local_id: DoubleId, + pub public_key: PublicKey, +} + +impl Peer { + pub fn get_by_local_id(local_id: DoubleId) -> Result, rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + Ok(Some(connection.query_row( + &("SELECT ".to_string() + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME + " WHERE local_id=(?1)"), + params![local_id], + Self::from_default_row + )?)) + } + pub fn get_by_public_key(public_key: PublicKey) -> Result, rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + Ok(Some(connection.query_row( + &("SELECT ".to_string() + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME + " WHERE public_key=(?1)"), + params![public_key.as_bytes()], + Self::from_default_row + )?)) + } +} + +impl LocalModel for Peer { + const TABLE_NAME: &str = "peer"; + const DEFAULT_COLUMNS: &[&str] = &[ + "local_id", + "public_key" + ]; + + fn from_default_row(row: &rusqlite::Row<'_>) -> Result { + + Ok(Self { + local_id: row.get(0)?, + public_key: PublicKey::from_bytes(&row.get(1)?).map_err(|e| FromSqlError::Other(Box::new(e)))? + }) + } + fn insert(&self) -> Result<(), rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + + connection.execute( + &("INSERT INTO ".to_owned() + Self::TABLE_NAME + " (" + &Self::DEFAULT_COLUMNS.join(", ") + ") VALUES (?1, ?2, ?3, ?4)"), + (&self.local_id, &self.public_key.as_bytes()), + )?; + Ok(()) + } + fn get_all() -> Result, rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + let mut stmt = connection.prepare(&("SELECT ".to_string() + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; + let rows = stmt.query_map( + [], + Self::from_default_row + )?; + let mut result= Vec::new(); + for row in rows { + result.push(row?); + } + Ok(result) + } +} \ No newline at end of file