// myContacts provides functions to manage a user's contacts package myContacts import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/myDatastores/myList" import "seekia/internal/myDatastores/myMapList" import "errors" import "strings" import "sync" import "slices" import "time" // This mutex will be locked whenever we edit contacts and their categories var updatingMyContactsMutex sync.Mutex var myContactsMapListDatastore *myMapList.MyMapList var myMateContactCategoriesListDatastore *myList.MyList var myHostContactCategoriesListDatastore *myList.MyList var myModeratorContactCategoriesListDatastore *myList.MyList func getContactCategoriesListDatastoreFromIdentityType(identityType string)(*myList.MyList, error){ if (identityType == "Mate"){ return myMateContactCategoriesListDatastore, nil } if (identityType == "Host"){ return myHostContactCategoriesListDatastore, nil } if (identityType == "Moderator"){ return myModeratorContactCategoriesListDatastore, nil } return nil, errors.New("getContactCategoriesListDatastoreFromIdentityType called with invalid IdentityType: " + identityType) } // This function must be called whenever an app user signs in func InitializeMyContactDatastores()error{ updatingMyContactsMutex.Lock() defer updatingMyContactsMutex.Unlock() newMyContactsMapListDatastore, err := myMapList.CreateNewMapList("MyContacts") if (err != nil) { return err } myContactsMapListDatastore = newMyContactsMapListDatastore newMateContactCategoriesListDatastore, err := myList.CreateNewList("MyMateContactCategories") if (err != nil) { return err } newHostContactCategoriesListDatastore, err := myList.CreateNewList("MyHostContactCategories") if (err != nil) { return err } newModeratorContactCategoriesListDatastore, err := myList.CreateNewList("MyModeratorContactCategories") if (err != nil) { return err } myMateContactCategoriesListDatastore = newMateContactCategoriesListDatastore myHostContactCategoriesListDatastore = newHostContactCategoriesListDatastore myModeratorContactCategoriesListDatastore = newModeratorContactCategoriesListDatastore return nil } //Outputs: // -bool: Contact already exists // -error func AddContact(userIdentityHash [16]byte, contactName string, categoriesList []string, contactDescription string)(bool, error){ updatingMyContactsMutex.Lock() defer updatingMyContactsMutex.Unlock() userIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(userIdentityHash) if (err != nil) { userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:]) return false, errors.New("AddContact called with invalid identity hash: " + userIdentityHashHex) } if (contactName == ""){ return false, errors.New("AddContact called with empty contactName.") } containsDuplicates, _ := helpers.CheckIfListContainsDuplicates(categoriesList) if (containsDuplicates == true){ return false, errors.New("AddContact called with CategoriesList that contains duplicates.") } contactExists, _, _, _, _, err := GetMyContactDetails(userIdentityHash) if (err != nil) { return false, err } if (contactExists == true){ // GUI should prevent this from happening. return false, errors.New("AddContact called with pre-existing contact.") } currentTime := time.Now().Unix() currentTimeString := helpers.ConvertInt64ToString(currentTime) newMapItem := map[string]string{ "IdentityHash": userIdentityHashString, "Name": contactName, "AddedTime": currentTimeString, "Description": contactDescription, } if (len(categoriesList) != 0){ categoriesListBase64 := make([]string, 0, len(categoriesList)) for _, categoryName := range categoriesList{ if (categoryName == ""){ return false, errors.New("AddContact called with categoriesList containing empty category.") } categoryBase64 := encoding.EncodeBytesToBase64String([]byte(categoryName)) categoriesListBase64 = append(categoriesListBase64, categoryBase64) } categoriesListJoined := strings.Join(categoriesListBase64, "+") newMapItem["Categories"] = categoriesListJoined } err = myContactsMapListDatastore.AddMapListItem(newMapItem) if (err != nil) { return false, err } return false, nil } func DeleteContact(userIdentityHash [16]byte)error{ updatingMyContactsMutex.Lock() defer updatingMyContactsMutex.Unlock() userIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(userIdentityHash) if (err != nil) { userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:]) return errors.New("DeleteContact called with invalid identity hash: " + userIdentityHashHex) } mapToDelete := map[string]string{ "IdentityHash": userIdentityHashString, } err = myContactsMapListDatastore.DeleteMapListItems(mapToDelete) if (err != nil) { return err } return nil } // Outputs: // -[][16]byte: My contact identity hashes list // -error func GetMyContactsList(identityType string)([][16]byte, error){ myContactsMapList, err := GetMyContactsMapList(identityType) if (err != nil) { return nil, err } myContactIdentityHashesList := make([][16]byte, 0, len(myContactsMapList)) for _, contactMap := range myContactsMapList{ contactIdentityHashString, exists := contactMap["IdentityHash"] if (exists == false) { return nil, errors.New("Malformed contact map: Missing IdentityHash") } contactIdentityHash, contactIdentityType, err := identity.ReadIdentityHashString(contactIdentityHashString) if (err != nil){ return nil, errors.New("Malformed contact map: Contains invalid identityHash: " + contactIdentityHashString) } if (contactIdentityType != identityType){ return nil, errors.New("Malformed contact map: Contains identityHash of a different identityType.") } myContactIdentityHashesList = append(myContactIdentityHashesList, contactIdentityHash) } return myContactIdentityHashesList, nil } //Outputs: // -[]map[string]string: My contacts map list // -error func GetMyContactsMapList(identityType string)([]map[string]string, error){ if (identityType != "Mate" && identityType != "Host" && identityType != "Moderator"){ return nil, errors.New("GetMyContactsMapList called with invalid identity type: " + identityType) } myContactsMapList, err := myContactsMapListDatastore.GetMapList() if (err != nil) { return nil, err } identityTypeContacts := make([]map[string]string, 0) for _, contactMap := range myContactsMapList{ currentIdentityHash, exists := contactMap["IdentityHash"] if (exists == false) { return nil, errors.New("MyContacts map missing IdentityHash") } _, currentIdentityType, err := identity.ReadIdentityHashString(currentIdentityHash) if (err != nil) { return nil, errors.New("MyContacts map contains invalid IdentityHash: " + currentIdentityHash) } if (currentIdentityType == identityType) { identityTypeContacts = append(identityTypeContacts, contactMap) } } return identityTypeContacts, nil } func CheckIfUserIsMyContact(userIdentityHash [16]byte) (bool, error) { contactExists, _, _, _, _, err := GetMyContactDetails(userIdentityHash) if (err != nil) { return false, err } return contactExists, nil } //Outputs // -bool: contact exists // -string: Contact name // -int64: Added time // -[]string: List of categories // -string: Description // -error func GetMyContactDetails(userIdentityHash [16]byte)(bool, string, int64, []string, string, error){ userIdentityHashString, userIdentityType, err := identity.EncodeIdentityHashBytesToString(userIdentityHash) if (err != nil) { userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:]) return false, "", 0, nil, "", errors.New("GetMyContactDetails called with invalid identity hash: " + userIdentityHashHex) } lookupMap := map[string]string{ "IdentityHash": userIdentityHashString, } anyItemsFound, contactsMapList, err := myContactsMapListDatastore.GetMapListItems(lookupMap) if (err != nil) { return false, "", 0, nil, "", err } if (anyItemsFound == false) { return false, "", 0, nil, "", nil } if (len(contactsMapList) != 1) { return false, "", 0, nil, "", errors.New("Malformed contacts map. Two contacts found for 1 identity hash.") } contactDetailsMap := contactsMapList[0] contactName, exists := contactDetailsMap["Name"] if (exists == false) { return false, "", 0, nil, "", errors.New("Malformed MyContacts map list: Item missing Name") } addedTime, exists := contactDetailsMap["AddedTime"] if (exists == false){ return false, "", 0, nil, "", errors.New("Malformed MyContacts map list: Item missing AddedTime") } addedTimeInt64, err := helpers.ConvertStringToInt64(addedTime) if (err != nil) { return false, "", 0, nil, "", errors.New("Malformed MyContacts map list: AddedTime is invalid: " + addedTime) } contactDescription, exists := contactDetailsMap["Description"] if (exists == false){ return false, "", 0, nil, "", errors.New("Malformed MyContacts map list: Item missing Description") } contactCategoriesListString, exists := contactDetailsMap["Categories"] if (exists == false) { emptyList := make([]string, 0) return true, contactName, addedTimeInt64, emptyList, contactDescription, nil } contactCategoriesListBase64 := strings.Split(contactCategoriesListString, "+") contactCategoriesList := make([]string, 0, len(contactCategoriesListBase64)) for _, categoryBase64 := range contactCategoriesListBase64{ categoryString, err := encoding.DecodeBase64StringToUnicodeString(categoryBase64) if (err != nil) { return false, "", 0, nil, "", errors.New("MyContacts map list malformed: Category name is not Base64: " + categoryBase64) } contactCategoriesList = append(contactCategoriesList, categoryString) } // We prune any deleted categories (there shouldn't be any) allContactCategoriesList, err := GetAllMyContactCategories(userIdentityType) if (err != nil) { return false, "", 0, nil, "", err } contactCategoriesListPruned := helpers.GetSharedItemsOfTwoStringLists(contactCategoriesList, allContactCategoriesList) return true, contactName, addedTimeInt64, contactCategoriesListPruned, contactDescription, nil } // A user's categories are always selected from the categoriesMapList // Thus, there will never be a need to add a category to the categoriesMapList from this function (or the addContact function) // All new categories must be added via AddContactCategory function func EditContact(userIdentityHash [16]byte, newContactName string, newContactCategoriesList []string, newContactDescription string)error{ updatingMyContactsMutex.Lock() defer updatingMyContactsMutex.Unlock() userIdentityHashString, userIdentityType, err := identity.EncodeIdentityHashBytesToString(userIdentityHash) if (err != nil) { userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:]) return errors.New("EditContact called with invalid identity hash: " + userIdentityHashHex) } containsDuplicates, _ := helpers.CheckIfListContainsDuplicates(newContactCategoriesList) if (containsDuplicates == true){ return errors.New("EditContact called with newContactCategoriesList that contains duplicates") } myContactsMapList, err := GetMyContactsMapList(userIdentityType) if (err != nil) { return err } newContactsMapList := make([]map[string]string, 0) for _, contactMap := range myContactsMapList{ currentIdentityHash, exists := contactMap["IdentityHash"] if (exists == false) { return errors.New("MyContacts map list item missing IdentityHash") } if (currentIdentityHash != userIdentityHashString){ newContactsMapList = append(newContactsMapList, contactMap) continue } contactAddedTime, exists := contactMap["AddedTime"] if (exists == false) { return errors.New("MyContacts map list item missing AddedTime") } newContactMap := map[string]string{ "IdentityHash": userIdentityHashString, "AddedTime": contactAddedTime, "Name": newContactName, "Description": newContactDescription, } if (len(newContactCategoriesList) != 0){ newCategoriesBase64List := make([]string, 0) for _, categoryName := range newContactCategoriesList{ if (categoryName == ""){ return errors.New("EditContact called with newContactCategoriesList which contains empty string.") } categoryNameBase64 := encoding.EncodeBytesToBase64String([]byte(categoryName)) newCategoriesBase64List = append(newCategoriesBase64List, categoryNameBase64) } newCategoriesListString := strings.Join(newCategoriesBase64List, "+") newContactMap["Categories"] = newCategoriesListString } newContactsMapList = append(newContactsMapList, newContactMap) } err = myContactsMapListDatastore.OverwriteMapList(newContactsMapList) if (err != nil) { return err } return nil } func GetAllMyContactCategories(identityType string)([]string, error){ contactCategoriesListDatastore, err := getContactCategoriesListDatastoreFromIdentityType(identityType) if (err != nil) { return nil, err } myContactCategoriesList, err := contactCategoriesListDatastore.GetList() if (err != nil) { return nil, err } allCategoriesListPruned := helpers.RemoveDuplicatesFromStringList(myContactCategoriesList) return allCategoriesListPruned, nil } func AddContactCategory(identityType string, categoryName string)error{ updatingMyContactsMutex.Lock() defer updatingMyContactsMutex.Unlock() contactCategoriesListDatastore, err := getContactCategoriesListDatastoreFromIdentityType(identityType) if (err != nil) { return err } myContactCategoriesList, err := contactCategoriesListDatastore.GetList() if (err != nil) { return err } categoryExists := slices.Contains(myContactCategoriesList, categoryName) if (categoryExists == true){ // GUI should prevent this return errors.New("AddContactCategory called with pre-existing category.") } err = contactCategoriesListDatastore.AddListItem(categoryName) if (err != nil) { return err } return nil } func DeleteContactCategory(identityType string, categoryToDeleteName string)error{ updatingMyContactsMutex.Lock() defer updatingMyContactsMutex.Unlock() contactCategoriesListDatastore, err := getContactCategoriesListDatastoreFromIdentityType(identityType) if (err != nil) { return err } err = contactCategoriesListDatastore.DeleteListItem(categoryToDeleteName) if (err != nil) { return err } // Now we prune any deleted categories from each contact entry categoryToDeleteNameBase64 := encoding.EncodeBytesToBase64String([]byte(categoryToDeleteName)) myContactsMapList, err := GetMyContactsMapList(identityType) if (err != nil) { return err } newContactsMapList := make([]map[string]string, 0) for _, contactMap := range myContactsMapList{ currentIdentityHash, exists := contactMap["IdentityHash"] if (exists == false) { return errors.New("MyContacts map list item missing IdentityHash") } _, userIdentityType, err := identity.ReadIdentityHashString(currentIdentityHash) if (err != nil) { return errors.New("MyContacts map list item contains invalid IdentityHash: " + currentIdentityHash) } if (userIdentityType != identityType){ newContactsMapList = append(newContactsMapList, contactMap) continue } contactCategoriesListString, exists := contactMap["Categories"] if (exists == false) { newContactsMapList = append(newContactsMapList, contactMap) continue } contactCategoriesListBase64 := strings.Split(contactCategoriesListString, "+") newCategoriesList, deletedAny := helpers.DeleteAllMatchingItemsFromList(contactCategoriesListBase64, categoryToDeleteNameBase64) if (deletedAny == false){ newContactsMapList = append(newContactsMapList, contactMap) continue } newCategoriesListJoined := strings.Join(newCategoriesList, "+") contactMap["Categories"] = newCategoriesListJoined newContactsMapList = append(newContactsMapList, contactMap) } err = myContactsMapListDatastore.OverwriteMapList(newContactsMapList) if (err != nil) { return err } return nil }