implement identity remove action

This commit is contained in:
yggverse 2024-11-29 05:44:40 +02:00
parent 00afa70d5d
commit 29083f50d9
8 changed files with 241 additions and 18 deletions

View File

@ -86,7 +86,7 @@ impl Gemini {
.gemini .gemini
.auth .auth
.database .database
.records(None) .records_scope(None)
.unwrap() .unwrap()
.iter() .iter()
.filter(|this| this.profile_identity_gemini_id == identity.id) .filter(|this| this.profile_identity_gemini_id == identity.id)
@ -100,7 +100,7 @@ impl Gemini {
.gemini .gemini
.auth .auth
.database .database
.records(Some(auth_url.as_str())) .records_scope(Some(auth_url.as_str()))
.unwrap() .unwrap()
.iter() .iter()
.filter(|this| this.profile_identity_gemini_id == identity.id) .filter(|this| this.profile_identity_gemini_id == identity.id)
@ -162,7 +162,8 @@ impl Gemini {
} }
// Remove all identity auths for `auth_uri` // Remove all identity auths for `auth_uri`
None => { None => {
if let Err(reason) = profile.identity.gemini.auth.remove(auth_url.as_str()) if let Err(reason) =
profile.identity.gemini.auth.remove_scope(auth_url.as_str())
{ {
todo!("{}", reason.to_string()) todo!("{}", reason.to_string())
}; };

View File

@ -1,8 +1,10 @@
mod drop;
mod file; mod file;
pub mod list; pub mod list;
mod name; mod name;
mod save; mod save;
use drop::Drop;
use file::File; use file::File;
use list::{item::value::Value, List}; use list::{item::value::Value, List};
use name::Name; use name::Name;
@ -15,6 +17,7 @@ use std::rc::Rc;
pub struct Form { pub struct Form {
// pub action: Rc<Action>, // pub action: Rc<Action>,
// pub drop: Rc<Drop>,
pub file: Rc<File>, pub file: Rc<File>,
pub list: Rc<List>, pub list: Rc<List>,
pub name: Rc<Name>, pub name: Rc<Name>,
@ -28,6 +31,7 @@ impl Form {
/// Create new `Self` /// Create new `Self`
pub fn new(profile: Rc<Profile>, action: Rc<Action>) -> Self { pub fn new(profile: Rc<Profile>, action: Rc<Action>) -> Self {
// Init components // Init components
let drop = Rc::new(Drop::new(profile.clone(), action.clone()));
let file = Rc::new(File::new(action.clone())); let file = Rc::new(File::new(action.clone()));
let list = Rc::new(List::new()); let list = Rc::new(List::new());
let name = Rc::new(Name::new(action.clone())); let name = Rc::new(Name::new(action.clone()));
@ -39,10 +43,12 @@ impl Form {
gobject.append(&list.gobject); gobject.append(&list.gobject);
gobject.append(&name.gobject); gobject.append(&name.gobject);
gobject.append(&file.gobject); gobject.append(&file.gobject);
gobject.append(&drop.gobject);
gobject.append(&save.gobject); gobject.append(&save.gobject);
// Connect events // Connect events
list.on_select({ list.on_select({
let drop = drop.clone();
let file = file.clone(); let file = file.clone();
let name = name.clone(); let name = name.clone();
let save = save.clone(); let save = save.clone();
@ -54,12 +60,14 @@ impl Form {
// Change file choose button visibility // Change file choose button visibility
file.update(matches!(item, Value::ImportPem)); file.update(matches!(item, Value::ImportPem));
// Change export button visibility by update it holder value // Change other components visibility by update it holder value
match item { match item {
Value::ProfileIdentityGeminiId(value) => { Value::ProfileIdentityGeminiId(value) => {
drop.update(Some(value));
save.update(Some(value)); save.update(Some(value));
} }
_ => { _ => {
drop.update(None);
save.update(None); save.update(None);
} }
} }
@ -72,11 +80,12 @@ impl Form {
// Return activated `Self` // Return activated `Self`
Self { Self {
// action, // action,
gobject, // drop,
file, file,
list, list,
name, name,
// save, // save,
gobject,
} }
} }

View File

@ -0,0 +1,128 @@
use super::Action;
use crate::profile::Profile;
use adw::{
prelude::{AdwDialogExt, AlertDialogExt, AlertDialogExtManual},
AlertDialog, ResponseAppearance,
};
use gtk::{
prelude::{ButtonExt, WidgetExt},
Button,
};
use std::{cell::RefCell, rc::Rc};
// Defaults
const LABEL: &str = "Delete";
const TOOLTIP_TEXT: &str = "Drop selected identity from database";
const MARGIN: i32 = 8;
const HEADING: &str = "Delete";
const BODY: &str = "Permanently delete selected identity from database?";
const RESPONSE_CANCEL: (&str, &str) = ("cancel", "Cancel");
const RESPONSE_CONFIRM: (&str, &str) = ("confirm", "Confirm");
pub struct Drop {
profile_identity_gemini_id: Rc<RefCell<Option<i64>>>,
pub gobject: Button,
}
impl Drop {
// Constructors
/// Create new `Self`
pub fn new(profile: Rc<Profile>, action: Rc<Action>) -> Self {
// Init selected option holder
let profile_identity_gemini_id = Rc::new(RefCell::new(None::<i64>));
// Init `GObject`
let gobject = Button::builder()
.label(LABEL)
.margin_top(MARGIN)
.tooltip_text(TOOLTIP_TEXT)
.visible(false)
.build();
// Init events
gobject.connect_clicked({
let action = action.clone();
let gobject = gobject.clone();
let profile_identity_gemini_id = profile_identity_gemini_id.clone();
move |_| {
// Get selected identity from holder
match profile_identity_gemini_id.borrow().as_ref() {
Some(profile_identity_gemini_id) => {
// Init main `GObject`
let dialog = AlertDialog::builder()
.heading(HEADING)
.body(BODY)
.close_response(RESPONSE_CANCEL.0)
.default_response(RESPONSE_CANCEL.0)
.build();
// Set response variants
dialog.add_responses(&[RESPONSE_CANCEL, RESPONSE_CONFIRM]);
// Decorate default response preset
dialog.set_response_appearance(
RESPONSE_CONFIRM.0,
ResponseAppearance::Suggested,
);
dialog.set_response_appearance(
RESPONSE_CANCEL.0,
ResponseAppearance::Destructive,
);
// Connect confirmation event
dialog.connect_response(Some(RESPONSE_CONFIRM.0), {
let action = action.clone();
let profile = profile.clone();
let gobject = gobject.clone();
let profile_identity_gemini_id = profile_identity_gemini_id.clone();
move |_, _| {
match profile.identity.gemini.delete(profile_identity_gemini_id) {
Ok(_) => {
gobject.set_css_classes(&["success"]);
gobject.set_label("Identity successfully deleted")
}
Err(e) => {
gobject.set_css_classes(&["error"]);
gobject.set_label(&e.to_string())
}
}
action.update.activate()
}
});
// Show dialog
dialog.present(Some(&gobject))
}
None => todo!(), // unexpected
}
}
});
// Return activated `Self`
Self {
profile_identity_gemini_id,
gobject,
}
}
// Actions
/// Update `profile_identity_gemini_id` holder,
/// toggle visibility depending on given value
pub fn update(&self, profile_identity_gemini_id: Option<i64>) {
self.gobject.set_visible(match profile_identity_gemini_id {
Some(value) => {
self.profile_identity_gemini_id.replace(Some(value));
true
}
None => {
self.profile_identity_gemini_id.replace(None);
false
}
})
}
}

View File

@ -68,6 +68,20 @@ impl Gemini {
} }
} }
/// Delete record from database including children dependencies, update memory index
pub fn delete(&self, profile_identity_gemini_id: i64) -> Result<(), Error> {
match self.auth.remove_ref(profile_identity_gemini_id) {
Ok(_) => match self.database.delete(profile_identity_gemini_id) {
Ok(_) => {
self.index()?;
Ok(())
}
Err(reason) => Err(Error::Database(reason)),
},
Err(e) => Err(Error::Auth(e)),
}
}
/// Generate new certificate and insert record to DB, update memory index /// Generate new certificate and insert record to DB, update memory index
/// * return new `profile_identity_gemini_id` on success /// * return new `profile_identity_gemini_id` on success
pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result<i64, Error> { pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result<i64, Error> {

View File

@ -43,7 +43,7 @@ impl Auth {
/// * return last insert `profile_identity_gemini_auth_id` on success /// * return last insert `profile_identity_gemini_auth_id` on success
pub fn apply(&self, profile_identity_gemini_id: i64, scope: &str) -> Result<i64, Error> { pub fn apply(&self, profile_identity_gemini_id: i64, scope: &str) -> Result<i64, Error> {
// Cleanup records match `scope` (unauthorize) // Cleanup records match `scope` (unauthorize)
self.remove(scope)?; self.remove_scope(scope)?;
// Create new record (auth) // Create new record (auth)
let profile_identity_gemini_auth_id = let profile_identity_gemini_auth_id =
@ -60,8 +60,24 @@ impl Auth {
} }
/// Remove all records match request (unauthorize) /// Remove all records match request (unauthorize)
pub fn remove(&self, scope: &str) -> Result<(), Error> { pub fn remove_scope(&self, scope: &str) -> Result<(), Error> {
match self.database.records(Some(scope)) { match self.database.records_scope(Some(scope)) {
Ok(records) => {
for record in records {
if let Err(reason) = self.database.delete(record.id) {
return Err(Error::Database(reason));
}
}
}
Err(reason) => return Err(Error::Database(reason)),
}
self.index()?;
Ok(())
}
/// Remove all records match `profile_identity_gemini_id` foreign reference key
pub fn remove_ref(&self, profile_identity_gemini_id: i64) -> Result<(), Error> {
match self.database.records_ref(profile_identity_gemini_id) {
Ok(records) => { Ok(records) => {
for record in records { for record in records {
if let Err(reason) = self.database.delete(record.id) { if let Err(reason) = self.database.delete(record.id) {
@ -83,7 +99,7 @@ impl Auth {
} }
// Build new index // Build new index
match self.database.records(None) { match self.database.records_scope(None) {
Ok(records) => { Ok(records) => {
for record in records { for record in records {
if let Err(reason) = self if let Err(reason) = self

View File

@ -60,10 +60,17 @@ impl Database {
// Getters // Getters
/// Get records from database match current `profile_id` optionally filtered by `scope` /// Get records from database match current `profile_id` optionally filtered by `scope`
pub fn records(&self, scope: Option<&str>) -> Result<Vec<Table>, Error> { pub fn records_scope(&self, scope: Option<&str>) -> Result<Vec<Table>, Error> {
let readable = self.connection.read().unwrap(); // @TODO let readable = self.connection.read().unwrap(); // @TODO
let tx = readable.unchecked_transaction()?; let tx = readable.unchecked_transaction()?;
select(&tx, scope) select_scope(&tx, scope)
}
/// Get records from database match current `profile_id` optionally filtered by `scope`
pub fn records_ref(&self, profile_identity_gemini_id: i64) -> Result<Vec<Table>, Error> {
let readable = self.connection.read().unwrap(); // @TODO
let tx = readable.unchecked_transaction()?;
select_ref(&tx, profile_identity_gemini_id)
} }
} }
@ -105,7 +112,7 @@ pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
) )
} }
pub fn select(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>, Error> { pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`profile_identity_gemini_id`, `profile_identity_gemini_id`,
@ -133,6 +140,34 @@ pub fn select(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>, Error
Ok(records) Ok(records)
} }
pub fn select_ref(tx: &Transaction, profile_identity_gemini_id: i64) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare(
"SELECT `id`,
`profile_identity_gemini_id`,
`scope`
FROM `profile_identity_gemini_auth`
WHERE `profile_identity_gemini_id` = ?",
)?;
let result = stmt.query_map([profile_identity_gemini_id], |row| {
Ok(Table {
id: row.get(0)?,
profile_identity_gemini_id: row.get(1)?,
scope: row.get(2)?,
})
})?;
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 { pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid() tx.last_insert_rowid()
} }

View File

@ -45,6 +45,22 @@ impl Database {
} }
} }
/// Delete record with given `id` from database
pub fn delete(&self, id: i64) -> Result<(), Error> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?;
// Create new record
delete(&tx, id)?;
// Done
match tx.commit() {
Ok(_) => Ok(()),
Err(reason) => Err(reason),
}
}
/// Get single record match `id` /// Get single record match `id`
pub fn record(&self, id: i64) -> Result<Option<Table>, Error> { pub fn record(&self, id: i64) -> Result<Option<Table>, Error> {
let readable = self.connection.read().unwrap(); let readable = self.connection.read().unwrap();
@ -94,6 +110,10 @@ pub fn insert(tx: &Transaction, profile_identity_id: i64, pem: &str) -> Result<u
) )
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `profile_identity_gemini` WHERE `id` = ?", [id])
}
pub fn select(tx: &Transaction, profile_identity_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, profile_identity_id: i64) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,

View File

@ -11,14 +11,14 @@ pub enum Error {
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result { fn fmt(&self, f: &mut Formatter) -> Result {
match self { match self {
Self::Auth(reason) => write!(f, "Could not create auth: {reason}"), Self::Auth(e) => write!(f, "Could not create auth: {e}"),
Self::Certificate(reason) => { Self::Certificate(e) => {
write!(f, "Could not create certificate: {reason}") write!(f, "Could not create certificate: {e}")
} }
Self::Database(reason) => { Self::Database(e) => {
write!(f, "Database error: {reason}") write!(f, "Database error: {e}")
} }
Self::Memory(reason) => write!(f, "Memory error: {reason}"), Self::Memory(e) => write!(f, "Memory error: {e}"),
} }
} }
} }