seekia/internal/myContacts/myContacts.go
2024-08-11 12:31:40 +00:00

490 lines
16 KiB
Go

// 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
}