Refactored autofill component as prep for ffi layer
This commit is contained in:
Родитель
dc1430537c
Коммит
418b38a95e
|
@ -38,3 +38,10 @@
|
|||
|
||||
- We no longer discard the final path component from self-hosted sync tokenserver URLs.
|
||||
([#3694](https://github.com/mozilla/application-services/pull/3694))
|
||||
|
||||
|
||||
## Autofill
|
||||
|
||||
### What's Changed
|
||||
|
||||
- We added the `touch_address` and `touch_credit_card` store functions and refactored the component.([#3691](https://github.com/mozilla/application-services/pull/3691))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Work around the fact that `sqlcipher` might get enabled by a cargo feature
|
||||
//! another crate in the workspace needs, without setting up nss. (This is a
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod addresses;
|
||||
pub mod credit_cards;
|
|
@ -1,118 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use crate::db::models::address::{Address, InternalAddress, NewAddressFields};
|
||||
use crate::db::schema::ADDRESS_COMMON_COLS;
|
||||
use crate::error::*;
|
||||
use crate::schema::ADDRESS_COMMON_COLS;
|
||||
|
||||
use rusqlite::{Connection, Row, NO_PARAMS};
|
||||
use serde::Serialize;
|
||||
use serde_derive::*;
|
||||
use rusqlite::{Connection, NO_PARAMS};
|
||||
use sync_guid::Guid;
|
||||
use types::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct NewAddressFields {
|
||||
pub given_name: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub additional_name: String,
|
||||
|
||||
pub family_name: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub organization: String,
|
||||
|
||||
pub street_address: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_level3: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_level2: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_level1: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub postal_code: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub country: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub tel: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct Address {
|
||||
pub guid: Guid,
|
||||
|
||||
pub fields: NewAddressFields,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeCreated")]
|
||||
pub time_created: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastUsed")]
|
||||
pub time_last_used: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastModified")]
|
||||
pub time_last_modified: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timesUsed")]
|
||||
pub times_used: i64,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "changeCounter")]
|
||||
pub(crate) sync_change_counter: i64,
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub fn from_row(row: &Row<'_>) -> Result<Address, rusqlite::Error> {
|
||||
let address_fields = NewAddressFields {
|
||||
given_name: row.get("given_name")?,
|
||||
additional_name: row.get("additional_name")?,
|
||||
family_name: row.get("family_name")?,
|
||||
organization: row.get("organization")?,
|
||||
street_address: row.get("street_address")?,
|
||||
address_level3: row.get("address_level3")?,
|
||||
address_level2: row.get("address_level2")?,
|
||||
address_level1: row.get("address_level1")?,
|
||||
postal_code: row.get("postal_code")?,
|
||||
country: row.get("country")?,
|
||||
tel: row.get("tel")?,
|
||||
email: row.get("email")?,
|
||||
};
|
||||
|
||||
Ok(Address {
|
||||
guid: Guid::from_string(row.get("guid")?),
|
||||
fields: address_fields,
|
||||
time_created: row.get("time_created")?,
|
||||
time_last_used: row.get("time_last_used")?,
|
||||
time_last_modified: row.get("time_last_modified")?,
|
||||
times_used: row.get("times_used")?,
|
||||
sync_change_counter: row.get("sync_change_counter")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add_address(conn: &mut Connection, new_address: NewAddressFields) -> Result<Address> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn add_address(conn: &Connection, new_address: NewAddressFields) -> Result<InternalAddress> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
|
||||
let address = Address {
|
||||
let address = InternalAddress {
|
||||
guid: Guid::random(),
|
||||
fields: new_address,
|
||||
time_created: Timestamp::now(),
|
||||
time_last_used: Timestamp { 0: 0 },
|
||||
time_last_used: Some(Timestamp::now()),
|
||||
time_last_modified: Timestamp::now(),
|
||||
times_used: 0,
|
||||
sync_change_counter: 1,
|
||||
|
@ -171,8 +78,8 @@ pub fn add_address(conn: &mut Connection, new_address: NewAddressFields) -> Resu
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_address(conn: &mut Connection, guid: &Guid) -> Result<Address> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn get_address(conn: &Connection, guid: String) -> Result<InternalAddress> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let sql = format!(
|
||||
"SELECT
|
||||
{common_cols}
|
||||
|
@ -181,15 +88,17 @@ pub fn get_address(conn: &mut Connection, guid: &Guid) -> Result<Address> {
|
|||
common_cols = ADDRESS_COMMON_COLS
|
||||
);
|
||||
|
||||
let address = tx.query_row(&sql, &[guid.as_str()], |row| Ok(Address::from_row(row)?))?;
|
||||
let address = tx.query_row(&sql, &[guid.as_str()], |row| {
|
||||
Ok(InternalAddress::from_row(row)?)
|
||||
})?;
|
||||
|
||||
tx.commit()?;
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_all_addresses(conn: &mut Connection) -> Result<Vec<Address>> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn get_all_addresses(conn: &Connection) -> Result<Vec<InternalAddress>> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let mut addresses = Vec::new();
|
||||
let sql = format!(
|
||||
"SELECT
|
||||
|
@ -200,7 +109,8 @@ pub fn get_all_addresses(conn: &mut Connection) -> Result<Vec<Address>> {
|
|||
|
||||
{
|
||||
let mut stmt = tx.prepare(&sql)?;
|
||||
let addresses_iter = stmt.query_map(NO_PARAMS, |row| Ok(Address::from_row(row)?))?;
|
||||
let addresses_iter =
|
||||
stmt.query_map(NO_PARAMS, |row| Ok(InternalAddress::from_row(row)?))?;
|
||||
|
||||
for address_result in addresses_iter {
|
||||
addresses.push(address_result.expect("Should unwrap address"));
|
||||
|
@ -212,8 +122,8 @@ pub fn get_all_addresses(conn: &mut Connection) -> Result<Vec<Address>> {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_address(conn: &mut Connection, address: Address) -> Result<()> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn update_address(conn: &Connection, address: &Address) -> Result<()> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
tx.execute_named(
|
||||
"UPDATE addresses_data
|
||||
SET given_name = :given_name,
|
||||
|
@ -231,18 +141,18 @@ pub fn update_address(conn: &mut Connection, address: Address) -> Result<()> {
|
|||
sync_change_counter = sync_change_counter + 1
|
||||
WHERE guid = :guid",
|
||||
rusqlite::named_params! {
|
||||
":given_name": address.fields.given_name,
|
||||
":additional_name": address.fields.additional_name,
|
||||
":family_name": address.fields.family_name,
|
||||
":organization": address.fields.organization,
|
||||
":street_address": address.fields.street_address,
|
||||
":address_level3": address.fields.address_level3,
|
||||
":address_level2": address.fields.address_level2,
|
||||
":address_level1": address.fields.address_level1,
|
||||
":postal_code": address.fields.postal_code,
|
||||
":country": address.fields.country,
|
||||
":tel": address.fields.tel,
|
||||
":email": address.fields.email,
|
||||
":given_name": address.given_name,
|
||||
":additional_name": address.additional_name,
|
||||
":family_name": address.family_name,
|
||||
":organization": address.organization,
|
||||
":street_address": address.street_address,
|
||||
":address_level3": address.address_level3,
|
||||
":address_level2": address.address_level2,
|
||||
":address_level1": address.address_level1,
|
||||
":postal_code": address.postal_code,
|
||||
":country": address.country,
|
||||
":tel": address.tel,
|
||||
":email": address.email,
|
||||
":guid": address.guid,
|
||||
},
|
||||
)?;
|
||||
|
@ -251,8 +161,8 @@ pub fn update_address(conn: &mut Connection, address: Address) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_address(conn: &mut Connection, guid: &Guid) -> Result<bool> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn delete_address(conn: &Connection, guid: String) -> Result<bool> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
|
||||
// check that guid exists
|
||||
let exists = tx.query_row(
|
||||
|
@ -298,6 +208,26 @@ pub fn delete_address(conn: &mut Connection, guid: &Guid) -> Result<bool> {
|
|||
Ok(exists)
|
||||
}
|
||||
|
||||
pub fn touch(conn: &Connection, guid: String) -> Result<()> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let now_ms = Timestamp::now();
|
||||
|
||||
tx.execute_named(
|
||||
"UPDATE addresses_data
|
||||
SET time_last_used = :time_last_used,
|
||||
times_used = times_used + 1,
|
||||
sync_change_counter = sync_change_counter + 1
|
||||
WHERE guid = :guid",
|
||||
rusqlite::named_params! {
|
||||
":time_last_used": now_ms,
|
||||
":guid": guid.as_str(),
|
||||
},
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -305,10 +235,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_address_create_and_read() {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_address = add_address(
|
||||
&mut db,
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "jane".to_string(),
|
||||
family_name: "doe".to_string(),
|
||||
|
@ -328,7 +258,7 @@ mod tests {
|
|||
assert_eq!(1, saved_address.sync_change_counter);
|
||||
|
||||
// get created address
|
||||
let retrieved_address = get_address(&mut db, &saved_address.guid)
|
||||
let retrieved_address = get_address(&db, saved_address.guid.to_string())
|
||||
.expect("should contain optional retrieved address");
|
||||
assert_eq!(saved_address.guid, retrieved_address.guid);
|
||||
assert_eq!(
|
||||
|
@ -353,19 +283,19 @@ mod tests {
|
|||
);
|
||||
|
||||
// converting the created record into a tombstone to check that it's not returned on a second `get_address` call
|
||||
let delete_result = delete_address(&mut db, &saved_address.guid);
|
||||
let delete_result = delete_address(&db, saved_address.guid.to_string());
|
||||
assert!(delete_result.is_ok());
|
||||
assert!(delete_result.unwrap());
|
||||
|
||||
assert!(get_address(&mut db, &saved_address.guid).is_err());
|
||||
assert!(get_address(&db, saved_address.guid.to_string()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_read_all() {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_address = add_address(
|
||||
&mut db,
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "jane".to_string(),
|
||||
family_name: "doe".to_string(),
|
||||
|
@ -379,7 +309,7 @@ mod tests {
|
|||
.expect("should contain saved address");
|
||||
|
||||
let saved_address2 = add_address(
|
||||
&mut db,
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "john".to_string(),
|
||||
family_name: "deer".to_string(),
|
||||
|
@ -394,7 +324,7 @@ mod tests {
|
|||
|
||||
// creating a third address with a tombstone to ensure it's not retunred
|
||||
let saved_address3 = add_address(
|
||||
&mut db,
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "abraham".to_string(),
|
||||
family_name: "lincoln".to_string(),
|
||||
|
@ -407,12 +337,12 @@ mod tests {
|
|||
)
|
||||
.expect("should contain saved address");
|
||||
|
||||
let delete_result = delete_address(&mut db, &saved_address3.guid);
|
||||
let delete_result = delete_address(&db, saved_address3.guid.to_string());
|
||||
assert!(delete_result.is_ok());
|
||||
assert!(delete_result.unwrap());
|
||||
|
||||
let retrieved_addresses =
|
||||
get_all_addresses(&mut db).expect("Should contain all saved addresses");
|
||||
get_all_addresses(&db).expect("Should contain all saved addresses");
|
||||
|
||||
assert!(!retrieved_addresses.is_empty());
|
||||
let expected_number_of_addresses = 2;
|
||||
|
@ -428,10 +358,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_address_update() {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_address = add_address(
|
||||
&mut db,
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "john".to_string(),
|
||||
family_name: "doe".to_string(),
|
||||
|
@ -446,25 +376,26 @@ mod tests {
|
|||
|
||||
let expected_additional_name = "paul".to_string();
|
||||
let update_result = update_address(
|
||||
&mut db,
|
||||
Address {
|
||||
guid: saved_address.guid.clone(),
|
||||
fields: NewAddressFields {
|
||||
given_name: "john".to_string(),
|
||||
additional_name: expected_additional_name.clone(),
|
||||
family_name: "deer".to_string(),
|
||||
street_address: "123 First Avenue".to_string(),
|
||||
country: "United States".to_string(),
|
||||
|
||||
..NewAddressFields::default()
|
||||
},
|
||||
|
||||
..Address::default()
|
||||
&db,
|
||||
&Address {
|
||||
guid: saved_address.guid.to_string(),
|
||||
given_name: "john".to_string(),
|
||||
additional_name: expected_additional_name.clone(),
|
||||
family_name: "deer".to_string(),
|
||||
organization: "".to_string(),
|
||||
street_address: "123 First Avenue".to_string(),
|
||||
address_level3: "".to_string(),
|
||||
address_level2: "Denver, CO".to_string(),
|
||||
address_level1: "".to_string(),
|
||||
postal_code: "".to_string(),
|
||||
country: "United States".to_string(),
|
||||
tel: "".to_string(),
|
||||
email: "".to_string(),
|
||||
},
|
||||
);
|
||||
assert!(update_result.is_ok());
|
||||
|
||||
let updated_address = get_address(&mut db, &saved_address.guid)
|
||||
let updated_address = get_address(&db, saved_address.guid.to_string())
|
||||
.expect("should contain optional updated address");
|
||||
|
||||
assert_eq!(saved_address.guid, updated_address.guid);
|
||||
|
@ -479,10 +410,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_address_delete() {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_address = add_address(
|
||||
&mut db,
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "jane".to_string(),
|
||||
family_name: "doe".to_string(),
|
||||
|
@ -495,7 +426,7 @@ mod tests {
|
|||
)
|
||||
.expect("should contain saved address");
|
||||
|
||||
let delete_result = delete_address(&mut db, &saved_address.guid);
|
||||
let delete_result = delete_address(&db, saved_address.guid.to_string());
|
||||
assert!(delete_result.is_ok());
|
||||
assert!(delete_result.unwrap());
|
||||
}
|
||||
|
@ -522,7 +453,7 @@ mod tests {
|
|||
assert!(tombstone_result.is_ok());
|
||||
|
||||
// create a new address with the tombstone's guid
|
||||
let address = Address {
|
||||
let address = InternalAddress {
|
||||
guid,
|
||||
fields: NewAddressFields {
|
||||
given_name: "jane".to_string(),
|
||||
|
@ -534,7 +465,7 @@ mod tests {
|
|||
..NewAddressFields::default()
|
||||
},
|
||||
|
||||
..Address::default()
|
||||
..InternalAddress::default()
|
||||
};
|
||||
|
||||
let add_address_result = db.execute_named(
|
||||
|
@ -599,7 +530,7 @@ mod tests {
|
|||
let guid = Guid::random();
|
||||
|
||||
// create an address
|
||||
let address = Address {
|
||||
let address = InternalAddress {
|
||||
guid,
|
||||
fields: NewAddressFields {
|
||||
given_name: "jane".to_string(),
|
||||
|
@ -611,7 +542,7 @@ mod tests {
|
|||
..NewAddressFields::default()
|
||||
},
|
||||
|
||||
..Address::default()
|
||||
..InternalAddress::default()
|
||||
};
|
||||
|
||||
let add_address_result = db.execute_named(
|
||||
|
@ -685,4 +616,33 @@ mod tests {
|
|||
tombstone_result.unwrap_err().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_touch() -> Result<()> {
|
||||
let db = new_mem_db();
|
||||
let saved_address = add_address(
|
||||
&db,
|
||||
NewAddressFields {
|
||||
given_name: "jane".to_string(),
|
||||
family_name: "doe".to_string(),
|
||||
street_address: "123 Second Avenue".to_string(),
|
||||
address_level2: "Chicago, IL".to_string(),
|
||||
country: "United States".to_string(),
|
||||
|
||||
..NewAddressFields::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
assert_eq!(saved_address.sync_change_counter, 1);
|
||||
assert_eq!(saved_address.times_used, 0);
|
||||
|
||||
touch(&db, saved_address.guid.to_string())?;
|
||||
|
||||
let touched_address = get_address(&db, saved_address.guid.to_string())?;
|
||||
|
||||
assert_eq!(touched_address.sync_change_counter, 2);
|
||||
assert_eq!(touched_address.times_used, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,89 +1,23 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use crate::db::models::credit_card::{CreditCard, InternalCreditCard, NewCreditCardFields};
|
||||
use crate::db::schema::CREDIT_CARD_COMMON_COLS;
|
||||
use crate::error::*;
|
||||
use crate::schema::CREDIT_CARD_COMMON_COLS;
|
||||
|
||||
use rusqlite::{Connection, Row, NO_PARAMS};
|
||||
use serde::Serialize;
|
||||
use serde_derive::*;
|
||||
use rusqlite::{Connection, NO_PARAMS};
|
||||
use sync_guid::Guid;
|
||||
use types::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct NewCreditCardFields {
|
||||
pub cc_name: String,
|
||||
|
||||
pub cc_number: String,
|
||||
|
||||
pub cc_exp_month: i64,
|
||||
|
||||
pub cc_exp_year: i64,
|
||||
|
||||
// Credit card types are a fixed set of strings as defined in the link below
|
||||
// (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
|
||||
pub cc_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct CreditCard {
|
||||
pub guid: Guid,
|
||||
|
||||
pub fields: NewCreditCardFields,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeCreated")]
|
||||
pub time_created: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastUsed")]
|
||||
pub time_last_used: Option<Timestamp>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastModified")]
|
||||
pub time_last_modified: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timesUsed")]
|
||||
pub times_used: i64,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "changeCounter")]
|
||||
pub(crate) sync_change_counter: i64,
|
||||
}
|
||||
|
||||
impl CreditCard {
|
||||
pub fn from_row(row: &Row<'_>) -> Result<CreditCard, rusqlite::Error> {
|
||||
let credit_card_fields = NewCreditCardFields {
|
||||
cc_name: row.get("cc_name")?,
|
||||
cc_number: row.get("cc_number")?,
|
||||
cc_exp_month: row.get("cc_exp_month")?,
|
||||
cc_exp_year: row.get("cc_exp_year")?,
|
||||
cc_type: row.get("cc_type")?,
|
||||
};
|
||||
|
||||
Ok(CreditCard {
|
||||
guid: Guid::from_string(row.get("guid")?),
|
||||
fields: credit_card_fields,
|
||||
time_created: row.get("time_created")?,
|
||||
time_last_used: row.get("time_last_used")?,
|
||||
time_last_modified: row.get("time_last_modified")?,
|
||||
times_used: row.get("times_used")?,
|
||||
sync_change_counter: row.get("sync_change_counter")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add_credit_card(
|
||||
conn: &mut Connection,
|
||||
conn: &Connection,
|
||||
new_credit_card_fields: NewCreditCardFields,
|
||||
) -> Result<CreditCard> {
|
||||
let tx = conn.transaction()?;
|
||||
) -> Result<InternalCreditCard> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
|
||||
let credit_card = CreditCard {
|
||||
let credit_card = InternalCreditCard {
|
||||
guid: Guid::random(),
|
||||
fields: new_credit_card_fields,
|
||||
time_created: Timestamp::now(),
|
||||
|
@ -131,9 +65,8 @@ pub fn add_credit_card(
|
|||
Ok(credit_card)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_credit_card(conn: &mut Connection, guid: &Guid) -> Result<CreditCard> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn get_credit_card(conn: &Connection, guid: String) -> Result<InternalCreditCard> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let sql = format!(
|
||||
"SELECT
|
||||
{common_cols}
|
||||
|
@ -142,15 +75,14 @@ pub fn get_credit_card(conn: &mut Connection, guid: &Guid) -> Result<CreditCard>
|
|||
common_cols = CREDIT_CARD_COMMON_COLS
|
||||
);
|
||||
|
||||
let credit_card = tx.query_row(&sql, &[guid.as_str()], CreditCard::from_row)?;
|
||||
let credit_card = tx.query_row(&sql, &[guid.as_str()], InternalCreditCard::from_row)?;
|
||||
|
||||
tx.commit()?;
|
||||
Ok(credit_card)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_all_credit_cards(conn: &mut Connection) -> Result<Vec<CreditCard>> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn get_all_credit_cards(conn: &Connection) -> Result<Vec<InternalCreditCard>> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let credit_cards;
|
||||
let sql = format!(
|
||||
"SELECT
|
||||
|
@ -162,17 +94,16 @@ pub fn get_all_credit_cards(conn: &mut Connection) -> Result<Vec<CreditCard>> {
|
|||
{
|
||||
let mut stmt = tx.prepare(&sql)?;
|
||||
credit_cards = stmt
|
||||
.query_map(NO_PARAMS, CreditCard::from_row)?
|
||||
.collect::<Result<Vec<CreditCard>, _>>()?;
|
||||
.query_map(NO_PARAMS, InternalCreditCard::from_row)?
|
||||
.collect::<Result<Vec<InternalCreditCard>, _>>()?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
Ok(credit_cards)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_credit_card(conn: &mut Connection, credit_card: CreditCard) -> Result<()> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn update_credit_card(conn: &Connection, credit_card: &CreditCard) -> Result<()> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
tx.execute_named(
|
||||
"UPDATE credit_cards_data
|
||||
SET cc_name = :cc_name,
|
||||
|
@ -180,23 +111,17 @@ pub fn update_credit_card(conn: &mut Connection, credit_card: CreditCard) -> Res
|
|||
cc_exp_month = :cc_exp_month,
|
||||
cc_exp_year = :cc_exp_year,
|
||||
cc_type = :cc_type,
|
||||
time_created = :time_created,
|
||||
time_last_used = :time_last_used,
|
||||
time_last_modified = :time_last_modified,
|
||||
times_used = :times_used,
|
||||
sync_change_counter = sync_change_counter + 1
|
||||
WHERE guid = :guid",
|
||||
rusqlite::named_params! {
|
||||
":cc_name": credit_card.fields.cc_name,
|
||||
":cc_number": credit_card.fields.cc_number,
|
||||
":cc_exp_month": credit_card.fields.cc_exp_month,
|
||||
":cc_exp_year": credit_card.fields.cc_exp_year,
|
||||
":cc_type": credit_card.fields.cc_type,
|
||||
":time_created": credit_card.time_created,
|
||||
":time_last_used": credit_card.time_last_used,
|
||||
":time_last_modified": credit_card.time_last_modified,
|
||||
":times_used": credit_card.times_used,
|
||||
":guid": credit_card.guid,
|
||||
":cc_name": credit_card.cc_name,
|
||||
":cc_number": credit_card.cc_number,
|
||||
":cc_exp_month": credit_card.cc_exp_month,
|
||||
":cc_exp_year": credit_card.cc_exp_year,
|
||||
":cc_type": credit_card.cc_type,
|
||||
":time_last_modified": Timestamp::now(),
|
||||
":guid": credit_card.guid.as_str(),
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -204,9 +129,8 @@ pub fn update_credit_card(conn: &mut Connection, credit_card: CreditCard) -> Res
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_credit_card(conn: &mut Connection, guid: &Guid) -> Result<bool> {
|
||||
let tx = conn.transaction()?;
|
||||
pub fn delete_credit_card(conn: &Connection, guid: String) -> Result<bool> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
|
||||
// check that guid exists
|
||||
let exists = tx.query_row(
|
||||
|
@ -265,6 +189,26 @@ pub fn delete_credit_card(conn: &mut Connection, guid: &Guid) -> Result<bool> {
|
|||
Ok(exists)
|
||||
}
|
||||
|
||||
pub fn touch(conn: &Connection, guid: String) -> Result<()> {
|
||||
let tx = conn.unchecked_transaction()?;
|
||||
let now_ms = Timestamp::now();
|
||||
|
||||
tx.execute_named(
|
||||
"UPDATE credit_cards_data
|
||||
SET time_last_used = :time_last_used,
|
||||
times_used = times_used + 1,
|
||||
sync_change_counter = sync_change_counter + 1
|
||||
WHERE guid = :guid",
|
||||
rusqlite::named_params! {
|
||||
":time_last_used": now_ms,
|
||||
":guid": guid.as_str(),
|
||||
},
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -272,10 +216,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_credit_card_create_and_read() -> Result<()> {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_credit_card = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "jane doe".to_string(),
|
||||
cc_number: "2222333344445555".to_string(),
|
||||
|
@ -292,7 +236,7 @@ mod tests {
|
|||
assert_eq!(1, saved_credit_card.sync_change_counter);
|
||||
|
||||
// get created credit card
|
||||
let retrieved_credit_card = get_credit_card(&mut db, &saved_credit_card.guid)?;
|
||||
let retrieved_credit_card = get_credit_card(&db, saved_credit_card.guid.to_string())?;
|
||||
|
||||
assert_eq!(saved_credit_card.guid, retrieved_credit_card.guid);
|
||||
assert_eq!(
|
||||
|
@ -317,21 +261,21 @@ mod tests {
|
|||
);
|
||||
|
||||
// converting the created record into a tombstone to check that it's not returned on a second `get_credit_card` call
|
||||
let delete_result = delete_credit_card(&mut db, &saved_credit_card.guid);
|
||||
let delete_result = delete_credit_card(&db, saved_credit_card.guid.to_string());
|
||||
assert!(delete_result.is_ok());
|
||||
assert!(delete_result?);
|
||||
|
||||
assert!(get_credit_card(&mut db, &saved_credit_card.guid).is_err());
|
||||
assert!(get_credit_card(&db, saved_credit_card.guid.to_string()).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_card_read_all() -> Result<()> {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_credit_card = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "jane doe".to_string(),
|
||||
cc_number: "2222333344445555".to_string(),
|
||||
|
@ -342,7 +286,7 @@ mod tests {
|
|||
)?;
|
||||
|
||||
let saved_credit_card2 = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "john deer".to_string(),
|
||||
cc_number: "1111222233334444".to_string(),
|
||||
|
@ -354,7 +298,7 @@ mod tests {
|
|||
|
||||
// creating a third credit card with a tombstone to ensure it's not retunred
|
||||
let saved_credit_card3 = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "abraham lincoln".to_string(),
|
||||
cc_number: "1111222233334444".to_string(),
|
||||
|
@ -364,11 +308,11 @@ mod tests {
|
|||
},
|
||||
)?;
|
||||
|
||||
let delete_result = delete_credit_card(&mut db, &saved_credit_card3.guid);
|
||||
let delete_result = delete_credit_card(&db, saved_credit_card3.guid.to_string());
|
||||
assert!(delete_result.is_ok());
|
||||
assert!(delete_result?);
|
||||
|
||||
let retrieved_credit_cards = get_all_credit_cards(&mut db)?;
|
||||
let retrieved_credit_cards = get_all_credit_cards(&db)?;
|
||||
|
||||
assert!(!retrieved_credit_cards.is_empty());
|
||||
let expected_number_of_credit_cards = 2;
|
||||
|
@ -389,10 +333,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_credit_card_update() -> Result<()> {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_credit_card = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "john deer".to_string(),
|
||||
cc_number: "1111222233334444".to_string(),
|
||||
|
@ -404,23 +348,19 @@ mod tests {
|
|||
|
||||
let expected_cc_name = "john doe".to_string();
|
||||
let update_result = update_credit_card(
|
||||
&mut db,
|
||||
CreditCard {
|
||||
guid: saved_credit_card.guid.clone(),
|
||||
fields: NewCreditCardFields {
|
||||
cc_name: expected_cc_name.clone(),
|
||||
cc_number: "1111222233334444".to_string(),
|
||||
cc_exp_month: 10,
|
||||
cc_exp_year: 2025,
|
||||
cc_type: "mastercard".to_string(),
|
||||
},
|
||||
|
||||
..CreditCard::default()
|
||||
&db,
|
||||
&CreditCard {
|
||||
guid: saved_credit_card.guid.to_string(),
|
||||
cc_name: expected_cc_name.clone(),
|
||||
cc_number: "1111222233334444".to_string(),
|
||||
cc_type: "mastercard".to_string(),
|
||||
cc_exp_month: 10,
|
||||
cc_exp_year: 2025,
|
||||
},
|
||||
);
|
||||
assert!(update_result.is_ok());
|
||||
|
||||
let updated_credit_card = get_credit_card(&mut db, &saved_credit_card.guid)?;
|
||||
let updated_credit_card = get_credit_card(&db, saved_credit_card.guid.to_string())?;
|
||||
|
||||
assert_eq!(saved_credit_card.guid, updated_credit_card.guid);
|
||||
assert_eq!(expected_cc_name, updated_credit_card.fields.cc_name);
|
||||
|
@ -433,10 +373,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_credit_card_delete() -> Result<()> {
|
||||
let mut db = new_mem_db();
|
||||
let db = new_mem_db();
|
||||
|
||||
let saved_credit_card = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "john deer".to_string(),
|
||||
cc_number: "1111222233334444".to_string(),
|
||||
|
@ -446,12 +386,12 @@ mod tests {
|
|||
},
|
||||
)?;
|
||||
|
||||
let delete_result = delete_credit_card(&mut db, &saved_credit_card.guid);
|
||||
let delete_result = delete_credit_card(&db, saved_credit_card.guid.to_string());
|
||||
assert!(delete_result.is_ok());
|
||||
assert!(delete_result?);
|
||||
|
||||
let saved_credit_card2 = add_credit_card(
|
||||
&mut db,
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "john doe".to_string(),
|
||||
cc_number: "5555666677778888".to_string(),
|
||||
|
@ -500,7 +440,7 @@ mod tests {
|
|||
},
|
||||
)?;
|
||||
|
||||
let delete_result2 = delete_credit_card(&mut db, &saved_credit_card2.guid);
|
||||
let delete_result2 = delete_credit_card(&db, saved_credit_card2.guid.to_string());
|
||||
assert!(delete_result2.is_ok());
|
||||
assert!(delete_result2?);
|
||||
|
||||
|
@ -550,7 +490,7 @@ mod tests {
|
|||
assert!(tombstone_result.is_ok());
|
||||
|
||||
// create a new credit card with the tombstone's guid
|
||||
let credit_card = CreditCard {
|
||||
let credit_card = InternalCreditCard {
|
||||
guid,
|
||||
fields: NewCreditCardFields {
|
||||
cc_name: "john deer".to_string(),
|
||||
|
@ -560,7 +500,7 @@ mod tests {
|
|||
cc_type: "mastercard".to_string(),
|
||||
},
|
||||
|
||||
..CreditCard::default()
|
||||
..InternalCreditCard::default()
|
||||
};
|
||||
|
||||
let add_credit_card_result = db.execute_named(
|
||||
|
@ -611,7 +551,7 @@ mod tests {
|
|||
let guid = Guid::random();
|
||||
|
||||
// create an credit card
|
||||
let credit_card = CreditCard {
|
||||
let credit_card = InternalCreditCard {
|
||||
guid,
|
||||
fields: NewCreditCardFields {
|
||||
cc_name: "jane doe".to_string(),
|
||||
|
@ -621,7 +561,7 @@ mod tests {
|
|||
cc_type: "visa".to_string(),
|
||||
},
|
||||
|
||||
..CreditCard::default()
|
||||
..InternalCreditCard::default()
|
||||
};
|
||||
|
||||
let add_credit_card_result = db.execute_named(
|
||||
|
@ -681,4 +621,31 @@ mod tests {
|
|||
tombstone_result.unwrap_err().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_card_touch() -> Result<()> {
|
||||
let db = new_mem_db();
|
||||
let saved_credit_card = add_credit_card(
|
||||
&db,
|
||||
NewCreditCardFields {
|
||||
cc_name: "john doe".to_string(),
|
||||
cc_number: "5555666677778888".to_string(),
|
||||
cc_exp_month: 5,
|
||||
cc_exp_year: 2024,
|
||||
cc_type: "visa".to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
assert_eq!(saved_credit_card.sync_change_counter, 1);
|
||||
assert_eq!(saved_credit_card.times_used, 0);
|
||||
|
||||
touch(&db, saved_credit_card.guid.to_string())?;
|
||||
|
||||
let touched_credit_card = get_credit_card(&db, saved_credit_card.guid.to_string())?;
|
||||
|
||||
assert_eq!(touched_credit_card.sync_change_counter, 2);
|
||||
assert_eq!(touched_credit_card.times_used, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -2,8 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod addresses;
|
||||
pub mod credit_cards;
|
||||
pub mod models;
|
||||
pub mod schema;
|
||||
pub mod store;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::schema;
|
||||
|
||||
use rusqlite::{Connection, OpenFlags};
|
||||
use std::{
|
|
@ -0,0 +1,145 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use rusqlite::Row;
|
||||
use serde::Serialize;
|
||||
use serde_derive::*;
|
||||
use sync_guid::Guid;
|
||||
use types::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct NewAddressFields {
|
||||
pub given_name: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub additional_name: String,
|
||||
|
||||
pub family_name: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub organization: String,
|
||||
|
||||
pub street_address: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_level3: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_level2: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_level1: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub postal_code: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub country: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub tel: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct InternalAddress {
|
||||
pub guid: Guid,
|
||||
|
||||
pub fields: NewAddressFields,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeCreated")]
|
||||
pub time_created: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastUsed")]
|
||||
pub time_last_used: Option<Timestamp>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastModified")]
|
||||
pub time_last_modified: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timesUsed")]
|
||||
pub times_used: i64,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "changeCounter")]
|
||||
pub(crate) sync_change_counter: i64,
|
||||
}
|
||||
|
||||
impl InternalAddress {
|
||||
pub fn from_row(row: &Row<'_>) -> Result<InternalAddress, rusqlite::Error> {
|
||||
let address_fields = NewAddressFields {
|
||||
given_name: row.get("given_name")?,
|
||||
additional_name: row.get("additional_name")?,
|
||||
family_name: row.get("family_name")?,
|
||||
organization: row.get("organization")?,
|
||||
street_address: row.get("street_address")?,
|
||||
address_level3: row.get("address_level3")?,
|
||||
address_level2: row.get("address_level2")?,
|
||||
address_level1: row.get("address_level1")?,
|
||||
postal_code: row.get("postal_code")?,
|
||||
country: row.get("country")?,
|
||||
tel: row.get("tel")?,
|
||||
email: row.get("email")?,
|
||||
};
|
||||
|
||||
Ok(InternalAddress {
|
||||
guid: Guid::from_string(row.get("guid")?),
|
||||
fields: address_fields,
|
||||
time_created: row.get("time_created")?,
|
||||
time_last_used: row.get("time_last_used")?,
|
||||
time_last_modified: row.get("time_last_modified")?,
|
||||
times_used: row.get("times_used")?,
|
||||
sync_change_counter: row.get("sync_change_counter")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Address {
|
||||
pub guid: String,
|
||||
pub given_name: String,
|
||||
pub additional_name: String,
|
||||
pub family_name: String,
|
||||
pub organization: String,
|
||||
pub street_address: String,
|
||||
pub address_level3: String,
|
||||
pub address_level2: String,
|
||||
pub address_level1: String,
|
||||
pub postal_code: String,
|
||||
pub country: String,
|
||||
pub tel: String,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
pub trait ExternalizeAddress {
|
||||
fn to_external(&self) -> Address;
|
||||
}
|
||||
|
||||
impl ExternalizeAddress for InternalAddress {
|
||||
fn to_external(&self) -> Address {
|
||||
Address {
|
||||
guid: self.guid.to_string(),
|
||||
given_name: self.fields.given_name.to_string(),
|
||||
additional_name: self.fields.additional_name.to_string(),
|
||||
family_name: self.fields.family_name.to_string(),
|
||||
organization: self.fields.organization.to_string(),
|
||||
street_address: self.fields.street_address.to_string(),
|
||||
address_level3: self.fields.address_level3.to_string(),
|
||||
address_level2: self.fields.address_level2.to_string(),
|
||||
address_level1: self.fields.address_level1.to_string(),
|
||||
postal_code: self.fields.postal_code.to_string(),
|
||||
country: self.fields.country.to_string(),
|
||||
tel: self.fields.tel.to_string(),
|
||||
email: self.fields.email.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use rusqlite::Row;
|
||||
use serde::Serialize;
|
||||
use serde_derive::*;
|
||||
use sync_guid::Guid;
|
||||
use types::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct NewCreditCardFields {
|
||||
pub cc_name: String,
|
||||
|
||||
pub cc_number: String,
|
||||
|
||||
pub cc_exp_month: i64,
|
||||
|
||||
pub cc_exp_year: i64,
|
||||
|
||||
// Credit card types are a fixed set of strings as defined in the link below
|
||||
// (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
|
||||
pub cc_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct InternalCreditCard {
|
||||
pub guid: Guid,
|
||||
|
||||
pub fields: NewCreditCardFields,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeCreated")]
|
||||
pub time_created: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastUsed")]
|
||||
pub time_last_used: Option<Timestamp>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timeLastModified")]
|
||||
pub time_last_modified: Timestamp,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "timesUsed")]
|
||||
pub times_used: i64,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "changeCounter")]
|
||||
pub(crate) sync_change_counter: i64,
|
||||
}
|
||||
|
||||
impl InternalCreditCard {
|
||||
pub fn from_row(row: &Row<'_>) -> Result<InternalCreditCard, rusqlite::Error> {
|
||||
let credit_card_fields = NewCreditCardFields {
|
||||
cc_name: row.get("cc_name")?,
|
||||
cc_number: row.get("cc_number")?,
|
||||
cc_exp_month: row.get("cc_exp_month")?,
|
||||
cc_exp_year: row.get("cc_exp_year")?,
|
||||
cc_type: row.get("cc_type")?,
|
||||
};
|
||||
|
||||
Ok(InternalCreditCard {
|
||||
guid: Guid::from_string(row.get("guid")?),
|
||||
fields: credit_card_fields,
|
||||
time_created: row.get("time_created")?,
|
||||
time_last_used: row.get("time_last_used")?,
|
||||
time_last_modified: row.get("time_last_modified")?,
|
||||
times_used: row.get("times_used")?,
|
||||
sync_change_counter: row.get("sync_change_counter")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct CreditCard {
|
||||
pub guid: String,
|
||||
pub cc_name: String,
|
||||
pub cc_number: String,
|
||||
pub cc_exp_month: i64,
|
||||
pub cc_exp_year: i64,
|
||||
|
||||
// Credit card types are a fixed set of strings as defined in the link below
|
||||
// (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
|
||||
pub cc_type: String,
|
||||
}
|
||||
|
||||
pub trait ExternalizeCreditCard {
|
||||
fn to_external(&self) -> CreditCard;
|
||||
}
|
||||
|
||||
impl ExternalizeCreditCard for InternalCreditCard {
|
||||
fn to_external(&self) -> CreditCard {
|
||||
CreditCard {
|
||||
guid: self.guid.to_string(),
|
||||
cc_name: self.clone().fields.cc_name,
|
||||
cc_number: self.clone().fields.cc_number,
|
||||
cc_exp_month: self.fields.cc_exp_month,
|
||||
cc_exp_year: self.fields.cc_exp_year,
|
||||
cc_type: self.clone().fields.cc_type,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod address;
|
||||
pub mod credit_card;
|
|
@ -39,8 +39,8 @@ pub const CREDIT_CARD_COMMON_COLS: &str = "
|
|||
sync_change_counter";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const CREATE_SHARED_SCHEMA_SQL: &str = include_str!("../sql/create_shared_schema.sql");
|
||||
const CREATE_SHARED_TRIGGERS_SQL: &str = include_str!("../sql/create_shared_triggers.sql");
|
||||
const CREATE_SHARED_SCHEMA_SQL: &str = include_str!("../../sql/create_shared_schema.sql");
|
||||
const CREATE_SHARED_TRIGGERS_SQL: &str = include_str!("../../sql/create_shared_triggers.sql");
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn init(db: &Connection) -> Result<()> {
|
|
@ -0,0 +1,104 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::db::models::address::{Address, ExternalizeAddress, NewAddressFields};
|
||||
use crate::db::models::credit_card::{CreditCard, ExternalizeCreditCard, NewCreditCardFields};
|
||||
use crate::db::{addresses, credit_cards, AutofillDb};
|
||||
use crate::error::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Store {
|
||||
db: AutofillDb,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
db: AutofillDb::new(db_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a store backed by an in-memory database.
|
||||
#[cfg(test)]
|
||||
pub fn new_memory(db_path: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
db: AutofillDb::new_memory(db_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add_credit_card(
|
||||
&self,
|
||||
new_credit_card_fields: NewCreditCardFields,
|
||||
) -> Result<CreditCard> {
|
||||
let credit_card = credit_cards::add_credit_card(&self.db.writer, new_credit_card_fields)?;
|
||||
Ok(credit_card.to_external())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_credit_card(&self, guid: String) -> Result<CreditCard> {
|
||||
let credit_card = credit_cards::get_credit_card(&self.db.writer, guid)?;
|
||||
Ok(credit_card.to_external())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_all_credit_cards(&self) -> Result<Vec<CreditCard>> {
|
||||
let credit_cards = credit_cards::get_all_credit_cards(&self.db.writer)?
|
||||
.iter()
|
||||
.map(|x| x.to_external())
|
||||
.collect();
|
||||
Ok(credit_cards)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_credit_card(&self, credit_card: &CreditCard) -> Result<()> {
|
||||
credit_cards::update_credit_card(&self.db.writer, credit_card)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_credit_card(&self, guid: String) -> Result<bool> {
|
||||
credit_cards::delete_credit_card(&self.db.writer, guid)
|
||||
}
|
||||
|
||||
pub fn touch_credit_card(&self, guid: String) -> Result<()> {
|
||||
credit_cards::touch(&self.db.writer, guid)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add_address(&self, new_address: NewAddressFields) -> Result<Address> {
|
||||
let address = addresses::add_address(&self.db.writer, new_address)?;
|
||||
Ok(address.to_external())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_address(&self, guid: String) -> Result<Address> {
|
||||
let address = addresses::get_address(&self.db.writer, guid)?;
|
||||
Ok(address.to_external())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_all_addresses(&self) -> Result<Vec<Address>> {
|
||||
let addresses = addresses::get_all_addresses(&self.db.writer)?
|
||||
.iter()
|
||||
.map(|x| x.to_external())
|
||||
.collect();
|
||||
Ok(addresses)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_address(&self, address: &Address) -> Result<()> {
|
||||
addresses::update_address(&self.db.writer, address)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_address(&self, guid: String) -> Result<bool> {
|
||||
addresses::delete_address(&self.db.writer, guid)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn touch_address(&self, guid: String) -> Result<()> {
|
||||
addresses::touch(&self.db.writer, guid)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use interrupt_support::Interrupted;
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub mod api;
|
||||
pub mod db;
|
||||
pub mod error;
|
||||
mod schema;
|
||||
pub mod store;
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::db::AutofillDb;
|
||||
use crate::error::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Store {
|
||||
db: AutofillDb,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
db: AutofillDb::new(db_path)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a store backed by an in-memory database.
|
||||
#[cfg(test)]
|
||||
pub fn new_memory(db_path: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
db: AutofillDb::new_memory(db_path)?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use anyhow::Result;
|
||||
use autofill::api::{addresses, credit_cards};
|
||||
use autofill::db::AutofillDb;
|
||||
use autofill::db::{
|
||||
models::{address, credit_card},
|
||||
store::Store,
|
||||
};
|
||||
use std::{fs::File, io::BufReader};
|
||||
use structopt::StructOpt;
|
||||
use sync_guid::Guid;
|
||||
|
||||
// Note: this uses doc comments to generate the help text.
|
||||
#[derive(Clone, Debug, StructOpt)]
|
||||
|
@ -107,125 +108,119 @@ enum Command {
|
|||
},
|
||||
}
|
||||
|
||||
fn run_add_address(db: &mut AutofillDb, filename: String) -> Result<()> {
|
||||
fn run_add_address(store: &Store, filename: String) -> Result<()> {
|
||||
println!("Retrieving address data from {}", filename);
|
||||
|
||||
let file = File::open(filename)?;
|
||||
let reader = BufReader::new(file);
|
||||
let address_fields: addresses::NewAddressFields = serde_json::from_reader(reader)?;
|
||||
let address_fields: address::NewAddressFields = serde_json::from_reader(reader)?;
|
||||
|
||||
println!("Making `add_address` api call");
|
||||
let address = addresses::add_address(&mut db.writer, address_fields)?;
|
||||
let address = Store::add_address(store, address_fields)?;
|
||||
|
||||
println!("Created address: {:#?}", address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_get_address(db: &mut AutofillDb, guid: String) -> Result<()> {
|
||||
fn run_get_address(store: &Store, guid: String) -> Result<()> {
|
||||
println!("Getting address for guid `{}`", guid);
|
||||
|
||||
let address = addresses::get_address(&mut db.writer, &Guid::from(guid))?;
|
||||
let address = Store::get_address(store, guid)?;
|
||||
|
||||
println!("Retrieved address: {:#?}", address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_get_all_addresses(db: &mut AutofillDb) -> Result<()> {
|
||||
fn run_get_all_addresses(store: &Store) -> Result<()> {
|
||||
println!("Getting all addresses");
|
||||
|
||||
let addresses = addresses::get_all_addresses(&mut db.writer)?;
|
||||
let addresses = Store::get_all_addresses(store)?;
|
||||
|
||||
println!("Retrieved addresses: {:#?}", addresses);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_update_address(db: &mut AutofillDb, filename: String) -> Result<()> {
|
||||
fn run_update_address(store: &Store, filename: String) -> Result<()> {
|
||||
println!("Updating address data from {}", filename);
|
||||
|
||||
let file = File::open(filename)?;
|
||||
let reader = BufReader::new(file);
|
||||
let address_fields: addresses::Address = serde_json::from_reader(reader)?;
|
||||
let address_fields: address::Address = serde_json::from_reader(reader)?;
|
||||
let guid = address_fields.guid.clone();
|
||||
|
||||
println!(
|
||||
"Making `update_address` api call for guid {}",
|
||||
guid.to_string()
|
||||
);
|
||||
addresses::update_address(&mut db.writer, address_fields)?;
|
||||
println!("Making `update_address` api call for guid {}", guid);
|
||||
Store::update_address(store, &address_fields)?;
|
||||
|
||||
let address = addresses::get_address(&mut db.writer, &guid)?;
|
||||
let address = Store::get_address(store, guid)?;
|
||||
println!("Updated address: {:#?}", address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_delete_address(db: &mut AutofillDb, guid: String) -> Result<()> {
|
||||
fn run_delete_address(store: &Store, guid: String) -> Result<()> {
|
||||
println!("Deleting address for guid `{}`", guid);
|
||||
|
||||
addresses::delete_address(&mut db.writer, &Guid::from(guid))?;
|
||||
Store::delete_address(store, guid)?;
|
||||
|
||||
println!("Successfully deleted address");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_add_credit_card(db: &mut AutofillDb, filename: String) -> Result<()> {
|
||||
fn run_add_credit_card(store: &Store, filename: String) -> Result<()> {
|
||||
println!("Retrieving credit card data from {}", filename);
|
||||
|
||||
let file = File::open(filename)?;
|
||||
let reader = BufReader::new(file);
|
||||
let credit_card_fields: credit_cards::NewCreditCardFields = serde_json::from_reader(reader)?;
|
||||
let credit_card_fields: credit_card::NewCreditCardFields = serde_json::from_reader(reader)?;
|
||||
|
||||
println!("Making `add_credit_card` api call");
|
||||
let credit_card = credit_cards::add_credit_card(&mut db.writer, credit_card_fields)?;
|
||||
let credit_card = Store::add_credit_card(store, credit_card_fields)?;
|
||||
|
||||
println!("Created credit card: {:#?}", credit_card);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_get_credit_card(db: &mut AutofillDb, guid: String) -> Result<()> {
|
||||
fn run_get_credit_card(store: &Store, guid: String) -> Result<()> {
|
||||
println!("Getting credit card for guid `{}`", guid);
|
||||
|
||||
let credit_card = credit_cards::get_credit_card(&mut db.writer, &Guid::from(guid))?;
|
||||
let credit_card = Store::get_credit_card(store, guid)?;
|
||||
|
||||
println!("Retrieved credit card: {:#?}", credit_card);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_get_all_credit_cards(db: &mut AutofillDb) -> Result<()> {
|
||||
fn run_get_all_credit_cards(store: &Store) -> Result<()> {
|
||||
println!("Getting all credit cards");
|
||||
|
||||
let credit_cards = credit_cards::get_all_credit_cards(&mut db.writer)?;
|
||||
let credit_cards = Store::get_all_credit_cards(store)?;
|
||||
|
||||
println!("Retrieved credit cards: {:#?}", credit_cards);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_update_credit_card(db: &mut AutofillDb, filename: String) -> Result<()> {
|
||||
fn run_update_credit_card(store: &Store, filename: String) -> Result<()> {
|
||||
println!("Updating credit card data from {}", filename);
|
||||
|
||||
let file = File::open(filename)?;
|
||||
let reader = BufReader::new(file);
|
||||
let credit_card_fields: credit_cards::CreditCard = serde_json::from_reader(reader)?;
|
||||
let credit_card_fields: credit_card::CreditCard = serde_json::from_reader(reader)?;
|
||||
let guid = credit_card_fields.guid.clone();
|
||||
|
||||
println!(
|
||||
"Making `update_credit_card` api call for guid {}",
|
||||
guid.to_string()
|
||||
);
|
||||
credit_cards::update_credit_card(&mut db.writer, credit_card_fields)?;
|
||||
println!("Making `update_credit_card` api call for guid {}", guid);
|
||||
Store::update_credit_card(store, &credit_card_fields)?;
|
||||
|
||||
let credit_card = credit_cards::get_credit_card(&mut db.writer, &guid)?;
|
||||
let credit_card = Store::get_credit_card(store, guid)?;
|
||||
println!("Updated credit card: {:#?}", credit_card);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_delete_credit_card(db: &mut AutofillDb, guid: String) -> Result<()> {
|
||||
fn run_delete_credit_card(store: &Store, guid: String) -> Result<()> {
|
||||
println!("Deleting credit card for guid `{}`", guid);
|
||||
|
||||
credit_cards::delete_credit_card(&mut db.writer, &Guid::from(guid))?;
|
||||
Store::delete_credit_card(store, guid)?;
|
||||
|
||||
println!("Successfully deleted credit card");
|
||||
Ok(())
|
||||
|
@ -238,19 +233,19 @@ fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
let db_path = opts.database_path;
|
||||
let mut db = AutofillDb::new(db_path)?;
|
||||
let store = Store::new(db_path)?;
|
||||
|
||||
match opts.cmd {
|
||||
Command::AddAddress { input_file } => run_add_address(&mut db, input_file),
|
||||
Command::GetAddress { guid } => run_get_address(&mut db, guid),
|
||||
Command::GetAllAddresses => run_get_all_addresses(&mut db),
|
||||
Command::UpdateAddress { input_file } => run_update_address(&mut db, input_file),
|
||||
Command::DeleteAddress { guid } => run_delete_address(&mut db, guid),
|
||||
Command::AddAddress { input_file } => run_add_address(&store, input_file),
|
||||
Command::GetAddress { guid } => run_get_address(&store, guid),
|
||||
Command::GetAllAddresses => run_get_all_addresses(&store),
|
||||
Command::UpdateAddress { input_file } => run_update_address(&store, input_file),
|
||||
Command::DeleteAddress { guid } => run_delete_address(&store, guid),
|
||||
|
||||
Command::AddCreditCard { input_file } => run_add_credit_card(&mut db, input_file),
|
||||
Command::GetCreditCard { guid } => run_get_credit_card(&mut db, guid),
|
||||
Command::GetAllCreditCards => run_get_all_credit_cards(&mut db),
|
||||
Command::UpdateCreditCard { input_file } => run_update_credit_card(&mut db, input_file),
|
||||
Command::DeleteCreditCard { guid } => run_delete_credit_card(&mut db, guid),
|
||||
Command::AddCreditCard { input_file } => run_add_credit_card(&store, input_file),
|
||||
Command::GetCreditCard { guid } => run_get_credit_card(&store, guid),
|
||||
Command::GetAllCreditCards => run_get_all_credit_cards(&store),
|
||||
Command::UpdateCreditCard { input_file } => run_update_credit_card(&store, input_file),
|
||||
Command::DeleteCreditCard { guid } => run_delete_credit_card(&store, guid),
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче