package user import ( "chain" "chain/runtime" "strings" "gno.land/p/nt/avl" ) // Events const ( CreateUserEvent = "CreateUser" DeleteUserEvent = "DeleteUser" SetPropertyEvent = "SetProperty" SetPropertiesEvent = "SetProperties" SetMetadatumEvent = "SetMetadatum" SetMetadataEvent = "SetMetadata" ) // properties: admin-managed user attributes var properties = avl.NewTree() // address => map[string]string // metadatas: user-managed personal data var metadatas = avl.NewTree() // address => map[string]string func HasUser(addr address) bool { _, found := properties.Get(addr.String()) return found } func CreateUser(cur realm, addr address, metadata string) { caller := runtime.PreviousRealm().Address() if caller != getAdmin() && caller != addr { panic("caller must be admin or the user being created") } addrStr := addr.String() _, found := properties.Get(addrStr) if found { panic("user already exists: " + addrStr) } // Create properties node with address properties.Set(addrStr, map[string]string{"address": addrStr}) // Parse and set metadata metas := map[string]string{"address": addrStr} if metadata != "" { keyValuePairs := strings.Split(metadata, "|") if len(keyValuePairs)%2 != 0 { panic("metadata must be key|value pairs") } for i := 0; i < len(keyValuePairs); i += 2 { key := keyValuePairs[i] value := keyValuePairs[i+1] if key == "address" { panic("reserved key: address") } metas[key] = value } } metadatas.Set(addrStr, metas) chain.Emit(CreateUserEvent, "address", addrStr, "caller", caller.String(), ) } func DeleteUser(cur realm, addr address) { caller := runtime.PreviousRealm().Address() if caller != getAdmin() && caller != addr { panic("caller must be admin or the user being deleted") } addrStr := addr.String() _, found := properties.Get(addrStr) if !found { panic("user not found: " + addrStr) } properties.Remove(addrStr) metadatas.Remove(addrStr) chain.Emit(DeleteUserEvent, "address", addrStr, "caller", caller.String(), ) } func SetProperty(cur realm, addr address, key string, value string) { caller := runtime.PreviousRealm().Address() assertIsAdmin(caller) if key == "address" { panic("reserved key: " + key) } addrStr := addr.String() props, found := properties.Get(addrStr) if !found { panic("user not found: " + addrStr) } props.(map[string]string)[key] = value chain.Emit( SetPropertyEvent, "address", addrStr, "key", key, "value", value, "caller", caller.String(), ) } func SetProperties(cur realm, addr address, keyValues string) { caller := runtime.PreviousRealm().Address() assertIsAdmin(caller) keyValuePairs := strings.Split(keyValues, "|") if len(keyValuePairs)%2 != 0 { panic("keyValues must be key|value pairs") } addrStr := addr.String() props, found := properties.Get(addrStr) if !found { panic("user not found: " + addrStr) } propsMap := props.(map[string]string) for i := 0; i < len(keyValuePairs); i += 2 { key := keyValuePairs[i] if key == "address" { continue } value := keyValuePairs[i+1] propsMap[key] = value } chain.Emit( SetPropertiesEvent, "address", addrStr, "caller", caller.String(), ) } func SetMetadatum(cur realm, addr address, key string, value string) { caller := runtime.PreviousRealm().Address() if caller != getAdmin() && caller != addr { panic("caller must be admin or the user owner") } addrStr := addr.String() metas, found := metadatas.Get(addrStr) if !found { panic("user not found: " + addrStr) } if key == "address" { panic("reserved key: " + key) } metas.(map[string]string)[key] = value chain.Emit( SetMetadataEvent, "address", addrStr, "key", key, "value", value, "caller", caller.String(), ) } func SetMetadata(cur realm, addr address, keyValues string) { caller := runtime.PreviousRealm().Address() if caller != getAdmin() && caller != addr { panic("caller must be admin or the user owner") } keyValuePairs := strings.Split(keyValues, "|") if len(keyValuePairs)%2 != 0 { panic("keyValues must be key|value pairs") } addrStr := addr.String() metas, found := metadatas.Get(addrStr) if !found { panic("user not found: " + addrStr) } metasMap := metas.(map[string]string) for i := 0; i < len(keyValuePairs); i += 2 { key := keyValuePairs[i] if key == "address" { continue } value := keyValuePairs[i+1] metasMap[key] = value } chain.Emit( SetMetadataEvent, "address", addrStr, "caller", caller.String(), ) } func ListPropertiesByAddresses(addrs ...address) []map[string]string { result := []map[string]string{} for _, addr := range addrs { addrStr := addr.String() props, found := properties.Get(addrStr) if !found { continue } propsMap := props.(map[string]string) m := map[string]string{} for k, v := range propsMap { m[k] = v } result = append(result, m) } return result } func ListMetadataByAddresses(addrs ...address) []map[string]string { result := []map[string]string{} for _, addr := range addrs { addrStr := addr.String() metas, found := metadatas.Get(addrStr) if !found { continue } metasMap := metas.(map[string]string) m := map[string]string{} for k, v := range metasMap { m[k] = v } result = append(result, m) } return result } func GetTotalUserSize() int { return properties.Size() } func ListUsers(page, count int) []map[string]string { if page < 1 { panic("page must be at least 1") } if count < 1 { panic("count must be at least 1") } if count > listLimit { panic("count exceeds listLimit") } result := []map[string]string{} offset := (page - 1) * count properties.IterateByOffset(offset, count, func(key string, value interface{}) bool { props := value.(map[string]string) m := map[string]string{} for k, v := range props { m[k] = v } result = append(result, m) return false }) return result }