use serde::{Deserialize,Serialize}; use sqlx::Row; use futures::TryStreamExt; use anyhow::{anyhow,Context, Result}; use std::fmt; use std::num::ParseIntError; use crate::{ users::{ entries::{Entries,Entry}, UserStore, }, OPENID_USERS_TABLENAME, tools::str_date_from_timestamp, }; const DISPLAY_SEPARATOR: &str = "="; #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize,Default)] pub struct OpenidUser { pub id: i64, pub application: String, pub appkey: String, pub name: String, pub userid: i64, pub description: String, pub created: String, pub lastaccess: String, } fn default_empty() -> String { "".to_string() } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct OpenidUserData { #[serde(default = "default_empty")] pub id: String, #[serde(default = "default_empty")] pub name: String, #[serde(default = "default_empty")] pub application: String, #[serde(default = "default_empty")] pub appkey: String, #[serde(default = "default_empty")] pub userid: String, #[serde(default = "default_empty")] pub description: String, } impl fmt::Display for OpenidUser { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Write strictly the first element into the supplied output // stream: `f`. Returns `fmt::Result` which indicates whether the // operation succeeded or failed. Note that `write!` uses syntax which // is very similar to `println!`. let sep = DISPLAY_SEPARATOR; let content = format!( "ID{} {} Name{} {} Application{} {} AppKey {} {} UserId{} {} Description{} {} Created{} {} Last access{} {}", sep, self.id, sep, self.name, sep, self.application, sep, self.appkey, sep, self.userid, sep, self.description, sep, self.created, sep, self.lastaccess, ); write!(f, "{}", content) } } impl Entry for OpenidUser { fn from_line(line: &str) -> Result { let parts: Vec<&str> = line.split(":").map(|part| part.trim()).collect(); Ok(OpenidUser { id: parts[0].to_string().parse::().unwrap_or_default(), name: parts[1].to_string(), application: parts[2].to_string(), appkey: parts[3].to_string(), description: parts[4].to_string(), userid: parts[5].to_string().parse::().unwrap_or_default(), created: parts[6].to_string(), lastaccess: parts[7].to_string(), }) } } impl OpenidUser { pub async fn add(self, store: &UserStore) -> Result { match store { UserStore::Sql(pool) => { let query_result = sqlx::query( format!("INSERT INTO {} ( name, application, appkey, userid, description, created, lastaccess ) VALUES ( ?, ?, ?, ?, ?, ?, ?)", OPENID_USERS_TABLENAME).as_str() ) .bind(self.name) .bind(self.application) .bind(self.appkey) .bind(self.userid) .bind(self.description) .bind(self.created) .bind(self.lastaccess) .execute(pool).await?; Ok(query_result.last_insert_id().unwrap_or_default()) }, UserStore::File(file_path) => { // use itertools::Itertools; // let entries: Vec = concat(vec![ // Entries::new(Path::new(&file_path)).map(|user: OpenidUser|{ // user.line_format() // }).collect(), // vec![ self.line_format()] // ]); // Entries::::write(Path::new(&file_path), &entries); let all: Vec = Entries::new(&file_path).collect(); let id = if all.len() > 0 { all[all.len()-1].id + 1 } else { 1 }; let mut new_user = self.to_owned(); new_user.id = id; let entries: Entries = Entries::new(&file_path); match entries.append(new_user.line_format()) { Ok(_) => Ok(id), Err(e) => { println!("Error add item to file: {}",e); Err(anyhow!("No data added")).context("OpenidUser add") } } }, } } #[allow(dead_code)] pub async fn select(field: &str, value: &str, human: bool, store: &UserStore) -> Result { match store { UserStore::Sql(pool) => { let query_str = format!("SELECT * FROM {} WHERE '{}' = ?", OPENID_USERS_TABLENAME, field); let mut stream = sqlx::query( &query_str ) .bind(value) //.map(|row: PgRow| { // map the row into a user-defined domain type //}) .fetch(pool); if let Some(row) = stream.try_next().await? { let created = if human { let created: String = row.try_get("created")?; str_date_from_timestamp(&created) } else { row.try_get("created")? }; let lastaccess = if human { let lastaccess: String = row.try_get("lastaccess")?; str_date_from_timestamp(&lastaccess) } else { row.try_get("lastaccess")? }; Ok(Self{ id: row.try_get("id")?, name: row.try_get("name")?, application: row.try_get("application")?, appkey: row.try_get("appkey")?, userid: row.try_get("userid")?, description: row.try_get("description")?, created, lastaccess, }) } else { Err(anyhow!("No data found")).context("OpenidUser select") } }, UserStore::File(file_path) => { if let Some(user) = Entries::::new(&file_path).find(|it| match field { "id" => it.id == value.parse::().unwrap_or_default(), "name" => it.name == value, "application" => it.application == value, "appkey" => it.appkey == value, "userid" => it.userid == value.parse::().unwrap_or_default(), _ => false, } ) { Ok(user) } else { Err(anyhow!("No data found")).context("OpenidUser select") } }, } } pub async fn app_select(app: &str , field: &str, value: &str, human: bool, store: &UserStore) -> Result { match store { UserStore::Sql(pool) => { let query_str = format!("SELECT * FROM {} WHERE application = '{}' AND {} = ?", OPENID_USERS_TABLENAME, app, field); let mut stream = sqlx::query( &query_str ) .bind(value) //.map(|row: PgRow| { // map the row into a user-defined domain type //}) .fetch(pool); if let Some(row) = stream.try_next().await? { let created = if human { let created: String = row.try_get("created")?; str_date_from_timestamp(&created) } else { row.try_get("created")? }; let lastaccess = if human { let lastaccess: String = row.try_get("lastaccess")?; str_date_from_timestamp(&lastaccess) } else { row.try_get("lastaccess")? }; Ok(Self{ id: row.try_get("id")?, name: row.try_get("name")?, application: row.try_get("application")?, appkey: row.try_get("appkey")?, userid: row.try_get("userid")?, description: row.try_get("description")?, created, lastaccess, }) } else { Err(anyhow!("No data found")).context("OpenidUser select") } }, UserStore::File(file_path) => { if let Some(user) = Entries::::new(&file_path).find(|it| match field { "id" => it.id == value.parse::().unwrap_or_default(), "name" => it.name == value, "application" => it.application == app, "appkey" => it.application == value, "userid" => it.userid == value.parse::().unwrap_or_default(), _ => false, } ) { Ok(user) } else { Err(anyhow!("No data found")).context("OpenidUser select") } }, } } pub async fn delete(id: i64, store: &UserStore) -> Result { match store { UserStore::Sql(pool) => { let query_str = format!("DELETE FROM {} WHERE id = ?", OPENID_USERS_TABLENAME); let query_result = sqlx::query( &query_str ) .bind(id) .execute(pool).await?; Ok(query_result.rows_affected() > 0) }, UserStore::File(file_path) => { let new_entries: Vec = Entries::::new(&file_path).filter(|it| it.id != id).map(|user| user.line_format() ).collect(); let entries = Entries::::new(&file_path); match entries.write(&new_entries) { Ok(_) => Ok(true), Err(e) => { println!("Error data delete '{}': {}", id, e); Err(anyhow!("No data delete").context("OpenidUser delete")) } } }, } } pub async fn update(self, store: &UserStore) -> anyhow::Result { match store { UserStore::Sql(pool) => { let query_str = format!("UPDATE {} SET name = ?, application = ?, appkey = ?, userid = ?, description = ?, created = ?, lastaccess = ? WHERE id = ? ", OPENID_USERS_TABLENAME ); let query_result = sqlx::query( &query_str ) .bind(self.name) .bind(self.application) .bind(self.appkey) .bind(self.userid) .bind(self.description) .bind(self.created) .bind(self.lastaccess) .bind(self.id) .execute(pool).await?; Ok(query_result.rows_affected() > 0) }, UserStore::File(file_path) => { let new_entries: Vec = Entries::new(&file_path).map(|user: OpenidUser|{ if user.id == self.id { self.to_owned().line_format() } else { user.line_format() } }).collect(); let entries = Entries::::new(&file_path); match entries.write(&new_entries) { Ok(_) => Ok(true), Err(e) => { println!("Error data update '{}': {}", self.id, e); Err(anyhow!("No data updated").context("OpenidUser update")) } } }, } } pub fn show(&self, sep: &str) { let content = if sep.is_empty() { format!( "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}", self.id, self.name, self.application, self.appkey, self.userid, self.description, self.created, self.lastaccess, ) } else { format!("{}",self).replace(DISPLAY_SEPARATOR, sep) }; println!("{}\n_____________________________",content); } pub async fn users_list( query_str: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result> { let mut usrs: Vec = Vec::new(); match store { UserStore::Sql(pool) => { let mut stream = sqlx::query( &query_str ) //.map(|row: PgRow| { // map the row into a user-defined domain type //}) .fetch(pool); while let Some(row) = stream.try_next().await? { let created = if human { let created: String = row.try_get("created")?; str_date_from_timestamp(&created) } else { row.try_get("created")? }; let lastaccess = if human { let lastaccess: String = row.try_get("lastaccess")?; str_date_from_timestamp(&lastaccess) } else { row.try_get("lastaccess")? }; let user = Self{ id: row.try_get("id")?, name: row.try_get("name")?, application: row.try_get("application")?, appkey: row.try_get("appkey")?, userid: row.try_get("userid")?, description: row.try_get("description")?, created, lastaccess, }; if show { user.show(sep); } usrs.push(user); } Ok(usrs) }, UserStore::File(file_path) => { let all: Vec = Entries::new(&file_path).collect(); if show { for user in all.to_owned() { if show { user.show(sep); } } } Ok(all) }, // UserStore::None => Err(anyhow!("No store set")).context("OpenidUsers list"), } } #[allow(dead_code)] pub async fn list(store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result> { OpenidUser::users_list( &format!("SELECT * FROM {}", OPENID_USERS_TABLENAME), store, human, show, sep ).await } pub async fn list_selection(field: &str, value: &str, store: &UserStore, human: bool, show: bool, sep: &str) -> anyhow::Result> { OpenidUser::users_list( &format!("SELECT * FROM {} WHERE {} = '{}'", OPENID_USERS_TABLENAME, &field, &value), store, human, show, sep ).await } #[allow(dead_code)] pub async fn count(store: &UserStore) -> anyhow::Result { match store { UserStore::Sql(pool) => { let query_str = format!("SELECT count(*) as total FROM {}", OPENID_USERS_TABLENAME); let row = sqlx::query( &query_str ) .fetch_one(pool).await?; let total: i64 = row.try_get("total")?; Ok(total) }, UserStore::File(file_path) => { let all: Vec = Entries::new(&file_path).collect(); Ok(all.len() as i64) }, } } fn line_format(self) -> String { format!( "{}:{}:{}:{}:{}:{}:{}:{}", self.id, self.name, self.application, self.appkey, self.description, self.userid, self.created, self.lastaccess, ) } /* pub fn from_data(&mut self, user_data: OpenidUserData) { if !user_data.name.is_empty() { self.name = user_data.name.to_owned(); } if !user_data.application.is_empty() { self.application = user_data.application.to_owned(); } if !user_data.description.is_empty() { self.description = user_data.description.to_owned(); } } pub fn from_user(&mut self, new_user: OpenidUser) { if !new_user.name.is_empty() { self.name = new_user.name.to_owned(); } if !new_user.application.is_empty() { self.application = new_user.application.to_owned(); } if !new_user.description.is_empty() { self.description = new_user.description.to_owned(); } } pub fn session_data(&self) -> String { format!("{}|{}|{}|{}|{}", &self.id, &self.name, &self.application, &self.appkey, &self.userid ) } */ pub async fn sync_ids(userid: &str, open_ids: &str, store: &UserStore) { let openid_sel = OpenidUser::list_selection( "userid", userid, store, true,false, "|" ).await.unwrap_or_else(|e| { println!("Error list selection {}: {}", &userid, e); Vec::new() }); let new_open_ids: Vec = open_ids.split(",").map(|s| s.to_string()).collect(); for itm in openid_sel { if new_open_ids.len() == 0 || ! new_open_ids.contains(&itm.appkey) { let _res = OpenidUser::delete(itm.id, &store).await.unwrap_or_else(|e| { println!("Error delete {}: {}",&itm.appkey,e); false }); } } } }