Update local data and migration

This commit is contained in:
fluo10 2025-09-15 19:42:48 +09:00
parent dd43b89086
commit 73c59c97d0
10 changed files with 272 additions and 127 deletions

View file

@ -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<String>,
passcode: Option<String>,
created_at: DateTime<Local>,
updated_at: DateTime<Local>,
}
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<Self, rusqlite::Error> {
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<Self, rusqlite::Error> {
let created_at: NaiveDateTime = row.get(2)?;
let updated_at: NaiveDateTime = row.get(3)?;
let node_id: Vec<u8> = 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<Vec<Self>, 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)
}
}

View file

@ -1,8 +0,0 @@
use iroh::NodeId;
/// Request of node authentication.
#[derive(Debug, Clone)]
pub struct AuthorizationRequest {
sender_id: NodeId,
sender_info: String,
}

View file

@ -1,12 +0,0 @@
use iroh::NodeId;
/// Response of node authentication.
#[derive(Debug, Clone)]
pub struct AuthorizationResponse {
sender_id: NodeId,
passcode: String,
}

View file

@ -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;

View file

@ -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<Local>,
responded_at: Option<DateTime<Local>>,
}
impl ReceivedAuthorizationRequest {
pub fn get_by_request_id(request_id: SingleId) -> Result<Self, rusqlite::Error> {
todo!()
}
pub fn get_by_public_key(public_key: PublicKey) -> Result<Self, rusqlite::Error> {
todo!()
}
pub fn get_by_local_peer_id(local_peer_id: DoubleId) -> Result<Self, rusqlite::Error> {
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<Self, rusqlite::Error> {
let created_at: NaiveDateTime = row.get(3)?;
let responded_at: Option<NaiveDateTime> = 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<Vec<Self>, 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)
}
}

View file

@ -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<Local>,
sent_at: Option<DateTime<Local>>,
}
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<Self, rusqlite::Error> {
let created_at: NaiveDateTime = row.get(2)?;
let sent_at: Option<NaiveDateTime> = 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<Vec<Self>, 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)
}
}

View file

@ -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,

View file

@ -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<Self, rusqlite::Error>;
fn get_all(connection: &Connection) -> Result<Vec<Self>, 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<Vec<Self>, 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<Self, rusqlite::Error>;
fn get_all() -> Result<Vec<Self>, rusqlite::Error>;
}

View file

@ -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<Option<Self>, 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<Option<Self>, 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<Self, rusqlite::Error> {
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<Vec<Self>, 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)
}
}