@@ -351,6 +351,74 @@ impl LoginDb {
351351 Ok ( ( ) )
352352 }
353353
354+ /// Adds multiple logins within a single transaction and returns the successfully saved logins.
355+ pub fn add_many (
356+ & self ,
357+ entries : Vec < LoginEntry > ,
358+ encdec : & dyn EncryptorDecryptor ,
359+ ) -> Result < Vec < Result < EncryptedLogin > > > {
360+ let now_ms = util:: system_time_ms_i64 ( SystemTime :: now ( ) ) ;
361+
362+ let entries_with_records = entries
363+ . into_iter ( )
364+ . map ( |entry| {
365+ let guid = Guid :: random ( ) ;
366+ LoginEntryWithRecordFields {
367+ entry,
368+ record : RecordFields {
369+ id : guid. to_string ( ) ,
370+ time_created : now_ms,
371+ time_password_changed : now_ms,
372+ time_last_used : now_ms,
373+ times_used : 1 ,
374+ }
375+ }
376+ } )
377+ . collect ( ) ;
378+
379+ self . add_many_with_records ( entries_with_records, encdec)
380+ }
381+
382+ /// Adds multiple logins with records within a single transaction and returns the successfully saved logins.
383+ pub fn add_many_with_records (
384+ & self ,
385+ entries_with_records : Vec < LoginEntryWithRecordFields > ,
386+ encdec : & dyn EncryptorDecryptor ,
387+ ) -> Result < Vec < Result < EncryptedLogin > > > {
388+ let tx = self . unchecked_transaction ( ) ?;
389+ let mut results = vec ! [ ] ;
390+ for entry_with_record in entries_with_records {
391+ let guid = Guid :: from_string ( entry_with_record. record . id . clone ( ) ) ;
392+ match self . fixup_and_check_for_dupes ( & guid, entry_with_record. entry , encdec) {
393+ Ok ( new_entry) => {
394+ let sec_fields = SecureLoginFields {
395+ username : new_entry. username ,
396+ password : new_entry. password ,
397+ } ;
398+ let encrypted_login = EncryptedLogin {
399+ record : entry_with_record. record ,
400+ fields : LoginFields {
401+ origin : new_entry. origin ,
402+ form_action_origin : new_entry. form_action_origin ,
403+ http_realm : new_entry. http_realm ,
404+ username_field : new_entry. username_field ,
405+ password_field : new_entry. password_field ,
406+ } ,
407+ sec_fields : sec_fields. encrypt ( encdec) ?,
408+ } ;
409+ let result = self
410+ . insert_new_login ( & encrypted_login)
411+ . map ( |_| encrypted_login) ;
412+ results. push ( result) ;
413+ }
414+
415+ Err ( error) => results. push ( Err ( error) ) ,
416+ }
417+ }
418+ tx. commit ( ) ?;
419+ Ok ( results)
420+ }
421+
354422 pub fn add (
355423 & self ,
356424 entry : LoginEntry ,
@@ -359,32 +427,27 @@ impl LoginDb {
359427 let guid = Guid :: random ( ) ;
360428 let now_ms = util:: system_time_ms_i64 ( SystemTime :: now ( ) ) ;
361429
362- let new_entry = self . fixup_and_check_for_dupes ( & guid, entry, encdec) ?;
363- let sec_fields = SecureLoginFields {
364- username : new_entry. username ,
365- password : new_entry. password ,
366- } ;
367- let result = EncryptedLogin {
430+ let entry_with_record = LoginEntryWithRecordFields {
431+ entry,
368432 record : RecordFields {
369433 id : guid. to_string ( ) ,
370434 time_created : now_ms,
371435 time_password_changed : now_ms,
372436 time_last_used : now_ms,
373437 times_used : 1 ,
374- } ,
375- fields : LoginFields {
376- origin : new_entry. origin ,
377- form_action_origin : new_entry. form_action_origin ,
378- http_realm : new_entry. http_realm ,
379- username_field : new_entry. username_field ,
380- password_field : new_entry. password_field ,
381- } ,
382- sec_fields : sec_fields. encrypt ( encdec) ?,
438+ }
383439 } ;
384- let tx = self . unchecked_transaction ( ) ?;
385- self . insert_new_login ( & result) ?;
386- tx. commit ( ) ?;
387- Ok ( result)
440+
441+ self . add_with_record ( entry_with_record, encdec)
442+ }
443+
444+ pub fn add_with_record (
445+ & self ,
446+ entry_with_record : LoginEntryWithRecordFields ,
447+ encdec : & dyn EncryptorDecryptor ,
448+ ) -> Result < EncryptedLogin > {
449+ let mut results = self . add_many_with_records ( vec ! [ entry_with_record] , encdec) ?;
450+ results. pop ( ) . expect ( "there should be a single result" )
388451 }
389452
390453 pub fn update (
@@ -988,6 +1051,130 @@ mod tests {
9881051 assert_eq ! ( db. get_all( ) . unwrap( ) . len( ) , 2 ) ;
9891052 }
9901053
1054+ #[ test]
1055+ fn test_add_many ( ) {
1056+ ensure_initialized ( ) ;
1057+
1058+ let login_a = LoginEntry {
1059+ origin : "https://a.example.com" . into ( ) ,
1060+ http_realm : Some ( "https://www.example.com" . into ( ) ) ,
1061+ username : "test" . into ( ) ,
1062+ password : "sekret" . into ( ) ,
1063+ ..LoginEntry :: default ( )
1064+ } ;
1065+
1066+ let login_b = LoginEntry {
1067+ origin : "https://b.example.com" . into ( ) ,
1068+ http_realm : Some ( "https://www.example.com" . into ( ) ) ,
1069+ username : "test" . into ( ) ,
1070+ password : "sekret" . into ( ) ,
1071+ ..LoginEntry :: default ( )
1072+ } ;
1073+
1074+ let db = LoginDb :: open_in_memory ( ) . unwrap ( ) ;
1075+ let added = db
1076+ . add_many ( vec ! [ login_a. clone( ) , login_b. clone( ) ] , & * TEST_ENCDEC )
1077+ . expect ( "should be able to add logins" ) ;
1078+
1079+ let [ added_a, added_b] = added. as_slice ( ) else {
1080+ panic ! ( "there should really be 2" )
1081+ } ;
1082+
1083+ let fetched_a = db
1084+ . get_by_id ( & added_a. as_ref ( ) . unwrap ( ) . record . id )
1085+ . expect ( "should work" )
1086+ . expect ( "should get a record" ) ;
1087+
1088+ assert_eq ! ( fetched_a. fields. origin, login_a. origin) ;
1089+
1090+ let fetched_b = db
1091+ . get_by_id ( & added_b. as_ref ( ) . unwrap ( ) . record . id )
1092+ . expect ( "should work" )
1093+ . expect ( "should get a record" ) ;
1094+
1095+ assert_eq ! ( fetched_b. fields. origin, login_b. origin) ;
1096+ }
1097+
1098+ #[ test]
1099+ fn test_add_many_with_failed_constraint ( ) {
1100+ ensure_initialized ( ) ;
1101+
1102+ let login_a = LoginEntry {
1103+ origin : "https://example.com" . into ( ) ,
1104+ http_realm : Some ( "https://www.example.com" . into ( ) ) ,
1105+ username : "test" . into ( ) ,
1106+ password : "sekret" . into ( ) ,
1107+ ..LoginEntry :: default ( )
1108+ } ;
1109+
1110+ let login_b = LoginEntry {
1111+ // same origin will result in duplicate error
1112+ origin : "https://example.com" . into ( ) ,
1113+ http_realm : Some ( "https://www.example.com" . into ( ) ) ,
1114+ username : "test" . into ( ) ,
1115+ password : "sekret" . into ( ) ,
1116+ ..LoginEntry :: default ( )
1117+ } ;
1118+
1119+ let db = LoginDb :: open_in_memory ( ) . unwrap ( ) ;
1120+ let added = db
1121+ . add_many ( vec ! [ login_a. clone( ) , login_b. clone( ) ] , & * TEST_ENCDEC )
1122+ . expect ( "should be able to add logins" ) ;
1123+
1124+ let [ added_a, added_b] = added. as_slice ( ) else {
1125+ panic ! ( "there should really be 2" )
1126+ } ;
1127+
1128+ // first entry has been saved successfully
1129+ let fetched_a = db
1130+ . get_by_id ( & added_a. as_ref ( ) . unwrap ( ) . record . id )
1131+ . expect ( "should work" )
1132+ . expect ( "should get a record" ) ;
1133+
1134+ assert_eq ! ( fetched_a. fields. origin, login_a. origin) ;
1135+
1136+ // second entry failed
1137+ assert ! ( !added_b. is_ok( ) ) ;
1138+ }
1139+
1140+ #[ test]
1141+ fn test_add_with_record ( ) {
1142+ ensure_initialized ( ) ;
1143+
1144+ let guid = Guid :: random ( ) ;
1145+ let now_ms = util:: system_time_ms_i64 ( SystemTime :: now ( ) ) ;
1146+ let login = LoginEntry {
1147+ origin : "https://www.example.com" . into ( ) ,
1148+ http_realm : Some ( "https://www.example.com" . into ( ) ) ,
1149+ username : "test" . into ( ) ,
1150+ password : "sekret" . into ( ) ,
1151+ ..LoginEntry :: default ( )
1152+ } ;
1153+ let record = RecordFields {
1154+ id : guid. to_string ( ) ,
1155+ time_created : now_ms,
1156+ time_password_changed : now_ms + 100 ,
1157+ time_last_used : now_ms + 10 ,
1158+ times_used : 42 ,
1159+ } ;
1160+
1161+ let db = LoginDb :: open_in_memory ( ) . unwrap ( ) ;
1162+ let entry_with_record = LoginEntryWithRecordFields {
1163+ entry : login. clone ( ) ,
1164+ record : record. clone ( ) ,
1165+ } ;
1166+ let added = db
1167+ . add_with_record ( entry_with_record, & * TEST_ENCDEC )
1168+ . expect ( "should be able to add login with record" ) ;
1169+
1170+ let fetched = db
1171+ . get_by_id ( & added. record . id )
1172+ . expect ( "should work" )
1173+ . expect ( "should get a record" ) ;
1174+
1175+ assert_eq ! ( fetched. record, record) ;
1176+ }
1177+
9911178 #[ test]
9921179 fn test_unicode_submit ( ) {
9931180 ensure_initialized ( ) ;
0 commit comments