diff --git a/src/profile/identity.rs b/src/profile/identity.rs index 9d28e74a..0430d34f 100644 --- a/src/profile/identity.rs +++ b/src/profile/identity.rs @@ -1,11 +1,16 @@ +mod database; mod gemini; + +use database::Database; use gemini::Gemini; +use gtk::glib::DateTime; use sqlite::{Connection, Transaction}; use std::{rc::Rc, sync::RwLock}; /// Authorization wrapper for different protocols pub struct Identity { + // database: Rc, gemini: Rc, } @@ -14,8 +19,21 @@ impl Identity { /// Create new `Self` pub fn new(connection: Rc>, profile_id: Rc) -> Self { + // Init identity database + let database = Rc::new(Database::new(connection.clone())); + + // Get active identity set for profile or create new one + let profile_identity_id = Rc::new(match database.active() { + Some(identity) => identity.id, + None => match database.add(profile_id, true, DateTime::now_local().unwrap(), None) { + Ok(id) => id, + Err(_) => todo!(), + }, + }); + Self { - gemini: Rc::new(Gemini::new(connection, profile_id)), + // database, + gemini: Rc::new(Gemini::new(connection, profile_identity_id)), } } @@ -42,7 +60,9 @@ impl Identity { pub fn migrate(tx: &Transaction) -> Result<(), String> { // Migrate self components - // nothing yet.. + if let Err(e) = database::init(tx) { + return Err(e.to_string()); + } // Delegate migration to childs gemini::migrate(tx)?; diff --git a/src/profile/identity/database.rs b/src/profile/identity/database.rs new file mode 100644 index 00000000..4423aada --- /dev/null +++ b/src/profile/identity/database.rs @@ -0,0 +1,164 @@ +use gtk::glib::DateTime; +use sqlite::{Connection, Error, Transaction}; +use std::{rc::Rc, sync::RwLock}; + +pub struct Table { + pub id: i64, + pub profile_id: i64, + pub is_active: bool, + pub time: DateTime, + pub name: Option, +} + +pub struct Database { + pub connection: Rc>, +} + +impl Database { + // Constructors + + /// Create new `Self` + pub fn new(connection: Rc>) -> Self { + Self { connection } + } + + // Getters + + /// Get all records + pub fn records(&self) -> Vec { + let readable = self.connection.read().unwrap(); + let tx = readable.unchecked_transaction().unwrap(); + select(&tx).unwrap() + } + + /// Get active identity record if exist + pub fn active(&self) -> Option
{ + self.records().into_iter().find(|record| record.is_active) + } + + // Setters + + /// Create new record in `Self` database connected + pub fn add( + &self, + profile_id: Rc, + is_active: bool, + time: DateTime, + name: Option, + ) -> Result { + // Begin new transaction + let mut writable = self.connection.write().unwrap(); + let tx = writable.transaction().unwrap(); + + // New record has active status + if is_active { + // Deactivate other records as only one profile should be active + for record in select(&tx).unwrap() { + let _ = update( + &tx, + record.profile_id, + record.id, + false, + record.time, + record.name, + ); + } + } + + // Create new record + insert(&tx, profile_id, is_active, time, name).unwrap(); + + // Hold insert ID for result + let id = last_insert_id(&tx); + + // Done + match tx.commit() { + Ok(_) => Ok(id), + Err(_) => Err(()), // @TODO + } + } +} + +// Low-level DB API + +pub fn init(tx: &Transaction) -> Result { + tx.execute( + "CREATE TABLE IF NOT EXISTS `profile_identity` + ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `profile_id` INTEGER NOT NULL, + `is_active` INTEGER NOT NULL, + `time` INTEGER NOT NULL, + `name` VARCHAR(255), + + FOREIGN KEY (`profile_id`) REFERENCES `profile`(`id`) + )", + [], + ) +} + +pub fn insert( + tx: &Transaction, + profile_id: Rc, + is_active: bool, + time: DateTime, + name: Option, +) -> Result { + tx.execute( + "INSERT INTO `profile_identity` ( + `profile_id`, + `is_active`, + `time`, + `name` + ) VALUES (?, ?, ?, ?)", + (profile_id, is_active, time.to_unix(), name), + ) +} + +pub fn update( + tx: &Transaction, + id: i64, + profile_id: i64, + is_active: bool, + time: DateTime, + name: Option, +) -> Result { + tx.execute( + "UPDATE `profile_identity` + SET `profile_id` = ?, + `is_active` = ?, + `time` = ?, + `name` = ? + WHERE + `id` = ?", + (profile_id, is_active, time.to_unix(), name, id), + ) +} + +pub fn select(tx: &Transaction) -> Result, Error> { + let mut stmt = tx.prepare( + "SELECT `id`, `profile_id`, `is_active`, `time`, `name` FROM `profile_identity`", + )?; + let result = stmt.query_map([], |row| { + Ok(Table { + id: row.get(0)?, + profile_id: row.get(1)?, + is_active: row.get(2)?, + time: DateTime::from_unix_local(row.get(3)?).unwrap(), + name: row.get(4)?, + }) + })?; + + let mut records = Vec::new(); + + for record in result { + let table = record?; + records.push(table); + } + + Ok(records) +} + +pub fn last_insert_id(tx: &Transaction) -> i64 { + tx.last_insert_rowid() +} diff --git a/src/profile/identity/gemini.rs b/src/profile/identity/gemini.rs index 758e7405..39577c15 100644 --- a/src/profile/identity/gemini.rs +++ b/src/profile/identity/gemini.rs @@ -19,10 +19,10 @@ impl Gemini { // Constructors /// Create new `Self` - pub fn new(connection: Rc>, profile_id: Rc) -> Self { + pub fn new(connection: Rc>, profile_identity_id: Rc) -> Self { Self { auth: Rc::new(Auth::new(connection.clone())), - database: Rc::new(Database::new(connection, profile_id)), + database: Rc::new(Database::new(connection, profile_identity_id)), } } diff --git a/src/profile/identity/gemini/database.rs b/src/profile/identity/gemini/database.rs index 5bcc1f85..f579e956 100644 --- a/src/profile/identity/gemini/database.rs +++ b/src/profile/identity/gemini/database.rs @@ -3,32 +3,32 @@ use std::{rc::Rc, sync::RwLock}; pub struct Table { pub id: i64, - //pub profile_id: i64, + //pub profile_identity_id: i64, pub pem: String, } /// Storage for Gemini auth certificates pub struct Database { connection: Rc>, - profile_id: Rc, // multi-profile relationship + profile_identity_id: Rc, // multi-profile relationship } impl Database { // Constructors /// Create new `Self` - pub fn new(connection: Rc>, profile_id: Rc) -> Self { + pub fn new(connection: Rc>, profile_identity_id: Rc) -> Self { Self { connection, - profile_id, + profile_identity_id, } } - /// Get all records match current `profile_id` + /// Get all records match current `profile_identity_id` pub fn records(&self) -> Result, Error> { let readable = self.connection.read().unwrap(); // @TODO let tx = readable.unchecked_transaction()?; - select(&tx, *self.profile_id) + select(&tx, *self.profile_identity_id) } } @@ -38,11 +38,11 @@ pub fn init(tx: &Transaction) -> Result { tx.execute( "CREATE TABLE IF NOT EXISTS `profile_identity_gemini` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `profile_id` INTEGER NOT NULL, - `pem` TEXT NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `profile_identity_id` INTEGER NOT NULL, + `pem` TEXT NOT NULL, - FOREIGN KEY (`profile_id`) REFERENCES `profile`(`id`) + FOREIGN KEY (`profile_identity_id`) REFERENCES `profile_identity`(`id`) )", [], ) @@ -50,13 +50,17 @@ pub fn init(tx: &Transaction) -> Result { pub fn select(tx: &Transaction, profile_id: i64) -> Result, Error> { let mut stmt = tx.prepare( - "SELECT `id`, `profile_id`, `pem` FROM `profile_identity_gemini` WHERE `profile_id` = ?", + "SELECT `id`, + `profile_identity_id`, + `pem` + + FROM `profile_identity_gemini` WHERE `profile_identity_id` = ?", )?; let result = stmt.query_map([profile_id], |row| { Ok(Table { id: row.get(0)?, - //profile_id: row.get(1)?, + //profile_identity_id: row.get(1)?, pem: row.get(2)?, }) })?;