Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ListUsersByPrefix and ListJsonUsersByPrefix #35

Merged
merged 3 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 53 additions & 51 deletions realm/public.gno
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@ func PostMessage(body string) PostID {
panic("please register")
}

userPosts := getUserPosts(caller)
if userPosts == nil {
url := "/r/berty/social:" + name
userPosts = newUserPosts(url, caller)
gUserPostsByAddress.Set(caller.String(), userPosts)
}

userPosts := getOrCreateUserPosts(caller, name)
thread := userPosts.AddThread(body)
return thread.id
}
Expand Down Expand Up @@ -77,12 +71,7 @@ func RepostThread(userPostsAddr std.Address, threadid PostID, comment string) Po
if name == "" {
panic("please register")
}
dstUserPosts := getUserPosts(caller)
if dstUserPosts == nil {
url := "/r/berty/social:" + name
dstUserPosts = newUserPosts(url, caller)
gUserPostsByAddress.Set(caller.String(), dstUserPosts)
}
dstUserPosts := getOrCreateUserPosts(caller, name)

if userPostsAddr == caller {
panic("Cannot repost a user's own message")
Expand All @@ -99,36 +88,6 @@ func RepostThread(userPostsAddr std.Address, threadid PostID, comment string) Po
return repost.id
}

// Get all users with their address. The response is an avl.Tree where the key is the
// (sorted) user name and the value is the address.
func GetUsers() *avl.Tree {
allUsers := avl.Tree{}
gUserPostsByAddress.Iterate("", "", func(addr string, userPostsI interface{}) bool {
if user := users.GetUserByAddress(std.Address(addr)); user != nil {
// allUsers will be sorted by the name key.
allUsers.Set(user.Name, addr)
}
return false
})

return &allUsers
}

// Get all users with their address (using GetUsers), sorted by name. The response is a JSON string.
func GetJsonUsers() string {
allUsers := GetUsers()
usersJson := ""
allUsers.Iterate("", "", func(name string, addr interface{}) bool {
if usersJson != "" {
usersJson += ",\n "
}
usersJson += ufmt.Sprintf("{\"name\": \"%s\", \"address\": \"%s\"}", name, addr.(string))
return false
})

return ufmt.Sprintf("{\"n_users\": %d, \"users\": [\n %s]}", allUsers.Size(), usersJson)
}

// Get posts in a thread for a user. A thread is the sequence of posts without replies.
// While each post has an an arbitrary id, it also has an index within the thread starting from 0.
// Limit the response to posts from startIndex up to (not including) endIndex within the thread.
Expand Down Expand Up @@ -224,14 +183,8 @@ func Follow(followedAddr std.Address) {
panic("you can't follow yourself")
}

userPosts := getUserPosts(caller)
if userPosts == nil {
// A user can follow someone before doing any posts, so create the UserPosts.
url := "/r/berty/social:" + name
userPosts = newUserPosts(url, caller)
gUserPostsByAddress.Set(caller.String(), userPosts)
}

// A user can follow someone before doing any posts, so create the UserPosts if needed.
userPosts := getOrCreateUserPosts(caller, name)
userPosts.Follow(followedAddr)
}

Expand Down Expand Up @@ -265,3 +218,52 @@ func GetJsonUserByAddress(addr std.Address) string {
"{\"address\": \"%s\", \"name\": \"%s\", \"profile\": %s, \"number\": %d, \"invites\": %d, \"inviter\": \"%s\"}",
user.Address.String(), user.Name, strconv.Quote(user.Profile), user.Number, user.Invites, user.Inviter.String())
}

// TODO: This is a temporary copy. Remove this when they merge https://github.com/gnolang/gno/pull/1708.
func listKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string {
end := ""
n := len(prefix)
// To make the end of the search, increment the final character ASCII by one.
for n > 0 {
if ascii := int(prefix[n-1]); ascii < 0xff {
end = prefix[0:n-1] + string(ascii+1)
break
}

// The last character is 0xff. Try the previous character.
n--
}

result := []string{}
tree.Iterate(prefix, end, func(key string, value interface{}) bool {
result = append(result, key)
if len(result) >= maxResults {
return true
}
return false
})
return result
}

// Get a list of user names starting from the given prefix. Limit the
// number of results to maxResults.
func ListUsersByPrefix(prefix string, maxResults int) []string {
return listKeysByPrefix(gUserAddressByName, prefix, maxResults)
}

// Get a list of user names starting from the given prefix. Limit the
// number of results to maxResults.
// The response is a JSON string.
func ListJsonUsersByPrefix(prefix string, maxResults int) string {
names := ListUsersByPrefix(prefix, maxResults)

json := "["
for i, name := range names {
if i > 0 {
json += ", "
}
json += strconv.Quote(name)
}
json += "]"
return json
}
15 changes: 3 additions & 12 deletions realm/render.gno
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package social

import (
"sort"
"std"
"strconv"
"strings"

Expand All @@ -13,18 +11,11 @@ func Render(path string) string {
if path == "" {
str := "Welcome to GnoSocial!\n\n"

// List the users who have posted, sorted by name.
names := []string{}
gUserPostsByAddress.Iterate("", "", func(key string, value interface{}) bool {
if user := users.GetUserByAddress(std.Address(key)); user != nil {
names = append(names, user.Name)
}
// List the users who have posted. gUserAddressByName is already sorted by name.
gUserAddressByName.Iterate("", "", func(name string, value interface{}) bool {
str += " * [@" + name + "](/r/berty/social:" + name + ")" + "\n"
return false
})
sort.Strings(names)
for _, name := range names {
str += " * [@" + name + "](/r/berty/social:" + name + ")" + "\n"
}

return str
}
Expand Down
1 change: 1 addition & 0 deletions realm/social.gno
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import (

var (
gUserPostsByAddress avl.Tree // user's std.Address -> *UserPosts
gUserAddressByName avl.Tree // user's username -> std.Address
postsCtr uint64 // increments Post.id globally
)
26 changes: 25 additions & 1 deletion realm/util.gno
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
//----------------------------------------
// private utility methods

// Get the userPosts for the user.
// Get the UserPosts for the user.
func getUserPosts(userAddr std.Address) *UserPosts {
userPosts, exists := gUserPostsByAddress.Get(userAddr.String())
if !exists {
Expand All @@ -21,6 +21,30 @@ func getUserPosts(userAddr std.Address) *UserPosts {
return userPosts.(*UserPosts)
}

// Get the UserPosts for the userAddr. If not found, add a new UserPosts to
// gUserPostsByAddress and update gUserAddressByName with the username.
// (The caller usually has already called usernameOf to get the username, but if
// it is "" then this will get it.)
Comment on lines +26 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't understand this comment :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has an argument for the user address, but also an argument for the username. We want to save CPU cycles. In all the cases where code needs to call this function, it has already looked up the user name by user address.
https://github.com/gnolang/gnosocial/blob/1c86452df7b8f9400863386b98edeaa3c7c12aa9/realm/public.gno#L70
So it should not be necessary for this function to look it up again. The comment is justifying why this function has a username argument. For completeness, this function will look up the username by user address, but only if the caller doesn't provide the username.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think that the comment needs to be updated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the next PR ;)

func getOrCreateUserPosts(userAddr std.Address, username string) *UserPosts {
userPosts := getUserPosts(userAddr)
if userPosts != nil {
return userPosts
}

if username == "" {
username := usernameOf(userAddr)
if username == "" {
panic("no username for address " + userAddr.String())
}
}

userPosts = newUserPosts("/r/berty/social:"+username, userAddr)
gUserPostsByAddress.Set(userAddr.String(), userPosts)
gUserAddressByName.Set(username, userAddr)

return userPosts
}

func padZero(u64 uint64, length int) string {
str := strconv.Itoa(int(u64))
if len(str) >= length {
Expand Down