forked from gnolang/gno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Following this [PR](gnolang#181 (comment)) concerning the creation of a realm `profile` I created this realm which allows the creation of profile as well as the associated functions to display the information of a profile with an address or a username. I have some questions concerning this realm: - Currently, if a user modifies his username, his old username is not freed and is therefore no longer available, even if it is no longer in use. Should I free the old username when the user has changed username? - To make it possible to search by username and address, I've created a second avl tree containing both username and address, so that I can find the profile indexed by its address by searching for it by its username. This is the most efficient solution I've found. I'd like to get some feedback on this and know if I should do things differently so that searching by username is more optimized. - Do you have any other suggestions for completing the profile fields, or other interesting features to add? Thanks in advance for your feedback <details><summary>Contributors' checklist...</summary> - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details> Closes gnolang#181 --------- Co-authored-by: kazai <kazai@777> Co-authored-by: Manfred Touron <[email protected]> Co-authored-by: Leon Hudak <[email protected]>
- Loading branch information
1 parent
04cc2d5
commit 5c57f21
Showing
4 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module gno.land/r/demo/profile | ||
|
||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
gno.land/p/demo/mux v0.0.0-latest | ||
gno.land/p/demo/testutils v0.0.0-latest | ||
gno.land/p/demo/uassert v0.0.0-latest | ||
gno.land/p/demo/ufmt v0.0.0-latest | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package profile | ||
|
||
import ( | ||
"errors" | ||
"std" | ||
|
||
"gno.land/p/demo/avl" | ||
"gno.land/p/demo/mux" | ||
) | ||
|
||
var ( | ||
fields = avl.NewTree() | ||
router = mux.NewRouter() | ||
) | ||
|
||
const ( | ||
DisplayName = "DisplayName" | ||
Homepage = "Homepage" | ||
Bio = "Bio" | ||
Age = "Age" | ||
Location = "Location" | ||
Avatar = "Avatar" | ||
GravatarEmail = "GravatarEmail" | ||
AvailableForHiring = "AvailableForHiring" | ||
InvalidField = "InvalidField" | ||
) | ||
|
||
func init() { | ||
router.HandleFunc("", homeHandler) | ||
router.HandleFunc("u/{addr}", profileHandler) | ||
router.HandleFunc("f/{addr}/{field}", fieldHandler) | ||
} | ||
|
||
// list of supported string fields | ||
var stringFields = map[string]bool{ | ||
DisplayName: true, | ||
Homepage: true, | ||
Bio: true, | ||
Location: true, | ||
Avatar: true, | ||
GravatarEmail: true, | ||
} | ||
|
||
// list of support int fields | ||
var intFields = map[string]bool{ | ||
Age: true, | ||
} | ||
|
||
// list of support bool fields | ||
var boolFields = map[string]bool{ | ||
AvailableForHiring: true, | ||
} | ||
|
||
// Setters | ||
|
||
func SetStringField(field, value string) error { | ||
addr := std.PrevRealm().Addr() | ||
if _, ok := stringFields[field]; !ok { | ||
return errors.New("invalid string field") | ||
} | ||
|
||
key := addr.String() + ":" + field | ||
fields.Set(key, value) | ||
|
||
return nil | ||
} | ||
|
||
func SetIntField(field string, value int) error { | ||
addr := std.PrevRealm().Addr() | ||
|
||
if _, ok := intFields[field]; !ok { | ||
return errors.New("invalid int field") | ||
} | ||
|
||
key := addr.String() + ":" + field | ||
fields.Set(key, value) | ||
|
||
return nil | ||
} | ||
|
||
func SetBoolField(field string, value bool) error { | ||
addr := std.PrevRealm().Addr() | ||
|
||
if _, ok := boolFields[field]; !ok { | ||
return errors.New("invalid bool field") | ||
} | ||
|
||
key := addr.String() + ":" + field | ||
fields.Set(key, value) | ||
|
||
return nil | ||
} | ||
|
||
// Getters | ||
|
||
func GetStringField(addr std.Address, field, def string) string { | ||
key := addr.String() + ":" + field | ||
if value, ok := fields.Get(key); ok { | ||
return value.(string) | ||
} | ||
|
||
return def | ||
} | ||
|
||
func GetBoolField(addr std.Address, field string, def bool) bool { | ||
key := addr.String() + ":" + field | ||
if value, ok := fields.Get(key); ok { | ||
return value.(bool) | ||
} | ||
|
||
return def | ||
} | ||
|
||
func GetIntField(addr std.Address, field string, def int) int { | ||
key := addr.String() + ":" + field | ||
if value, ok := fields.Get(key); ok { | ||
return value.(int) | ||
} | ||
|
||
return def | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package profile | ||
|
||
import ( | ||
"std" | ||
"testing" | ||
|
||
"gno.land/p/demo/testutils" | ||
"gno.land/p/demo/uassert" | ||
) | ||
|
||
// Global addresses for test users | ||
var ( | ||
alice = testutils.TestAddress("alice") | ||
bob = testutils.TestAddress("bob") | ||
charlie = testutils.TestAddress("charlie") | ||
dave = testutils.TestAddress("dave") | ||
eve = testutils.TestAddress("eve") | ||
frank = testutils.TestAddress("frank") | ||
user1 = testutils.TestAddress("user1") | ||
user2 = testutils.TestAddress("user2") | ||
) | ||
|
||
func TestStringFields(t *testing.T) { | ||
std.TestSetRealm(std.NewUserRealm(alice)) | ||
|
||
// Get before setting | ||
name := GetStringField(alice, DisplayName, "anon") | ||
uassert.Equal(t, "anon", name) | ||
|
||
// Set | ||
err := SetStringField(DisplayName, "Alice foo") | ||
uassert.NoError(t, err) | ||
err = SetStringField(Homepage, "https://example.com") | ||
uassert.NoError(t, err) | ||
|
||
// Get after setting | ||
name = GetStringField(alice, DisplayName, "anon") | ||
homepage := GetStringField(alice, Homepage, "") | ||
bio := GetStringField(alice, Bio, "42") | ||
|
||
uassert.Equal(t, "Alice foo", name) | ||
uassert.Equal(t, "https://example.com", homepage) | ||
uassert.Equal(t, "42", bio) | ||
} | ||
|
||
func TestIntFields(t *testing.T) { | ||
std.TestSetRealm(std.NewUserRealm(bob)) | ||
|
||
// Get before setting | ||
age := GetIntField(bob, Age, 25) | ||
uassert.Equal(t, 25, age) | ||
|
||
// Set | ||
err := SetIntField(Age, 30) | ||
uassert.NoError(t, err) | ||
|
||
// Get after setting | ||
age = GetIntField(bob, Age, 25) | ||
uassert.Equal(t, 30, age) | ||
} | ||
|
||
func TestBoolFields(t *testing.T) { | ||
std.TestSetRealm(std.NewUserRealm(charlie)) | ||
|
||
// Get before setting | ||
hiring := GetBoolField(charlie, AvailableForHiring, false) | ||
uassert.Equal(t, false, hiring) | ||
|
||
// Set | ||
err := SetBoolField(AvailableForHiring, true) | ||
uassert.NoError(t, err) | ||
|
||
// Get after setting | ||
hiring = GetBoolField(charlie, AvailableForHiring, false) | ||
uassert.Equal(t, true, hiring) | ||
} | ||
|
||
func TestInvalidStringField(t *testing.T) { | ||
std.TestSetRealm(std.NewUserRealm(dave)) | ||
|
||
err := SetStringField(InvalidField, "test") | ||
uassert.Error(t, err) | ||
} | ||
|
||
func TestInvalidIntField(t *testing.T) { | ||
std.TestSetRealm(std.NewUserRealm(eve)) | ||
|
||
err := SetIntField(InvalidField, 123) | ||
uassert.Error(t, err) | ||
} | ||
|
||
func TestInvalidBoolField(t *testing.T) { | ||
std.TestSetRealm(std.NewUserRealm(frank)) | ||
|
||
err := SetBoolField(InvalidField, true) | ||
uassert.Error(t, err) | ||
} | ||
|
||
func TestMultipleProfiles(t *testing.T) { | ||
// Set profile for user1 | ||
std.TestSetRealm(std.NewUserRealm(user1)) | ||
err := SetStringField(DisplayName, "User One") | ||
uassert.NoError(t, err) | ||
|
||
// Set profile for user2 | ||
std.TestSetRealm(std.NewUserRealm(user2)) | ||
err = SetStringField(DisplayName, "User Two") | ||
uassert.NoError(t, err) | ||
|
||
// Get profiles | ||
std.TestSetRealm(std.NewUserRealm(user1)) // Switch back to user1 | ||
name1 := GetStringField(user1, DisplayName, "anon") | ||
std.TestSetRealm(std.NewUserRealm(user2)) // Switch back to user2 | ||
name2 := GetStringField(user2, DisplayName, "anon") | ||
|
||
uassert.Equal(t, "User One", name1) | ||
uassert.Equal(t, "User Two", name2) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package profile | ||
|
||
import ( | ||
"bytes" | ||
"std" | ||
|
||
"gno.land/p/demo/mux" | ||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
const ( | ||
BaseURL = "/r/demo/profile" | ||
SetStringFieldURL = BaseURL + "?help&__func=SetStringField&field=%s" | ||
SetIntFieldURL = BaseURL + "?help&__func=SetIntField&field=%s" | ||
SetBoolFieldURL = BaseURL + "?help&__func=SetBoolField&field=%s" | ||
ViewAllFieldsURL = BaseURL + ":u/%s" | ||
ViewFieldURL = BaseURL + ":f/%s/%s" | ||
) | ||
|
||
func homeHandler(res *mux.ResponseWriter, req *mux.Request) { | ||
var b bytes.Buffer | ||
|
||
b.WriteString("## Setters\n") | ||
for field := range stringFields { | ||
link := ufmt.Sprintf(SetStringFieldURL, field) | ||
b.WriteString(ufmt.Sprintf("- [Set %s](%s)\n", field, link)) | ||
} | ||
|
||
for field := range intFields { | ||
link := ufmt.Sprintf(SetIntFieldURL, field) | ||
b.WriteString(ufmt.Sprintf("- [Set %s](%s)\n", field, link)) | ||
} | ||
|
||
for field := range boolFields { | ||
link := ufmt.Sprintf(SetBoolFieldURL, field) | ||
b.WriteString(ufmt.Sprintf("- [Set %s Field](%s)\n", field, link)) | ||
} | ||
|
||
b.WriteString("\n---\n\n") | ||
|
||
res.Write(b.String()) | ||
} | ||
|
||
func profileHandler(res *mux.ResponseWriter, req *mux.Request) { | ||
var b bytes.Buffer | ||
addr := req.GetVar("addr") | ||
|
||
b.WriteString(ufmt.Sprintf("# Profile %s\n", addr)) | ||
|
||
address := std.Address(addr) | ||
|
||
for field := range stringFields { | ||
value := GetStringField(address, field, "n/a") | ||
link := ufmt.Sprintf(SetStringFieldURL, field) | ||
b.WriteString(ufmt.Sprintf("- %s: %s [Edit](%s)\n", field, value, link)) | ||
} | ||
|
||
for field := range intFields { | ||
value := GetIntField(address, field, 0) | ||
link := ufmt.Sprintf(SetIntFieldURL, field) | ||
b.WriteString(ufmt.Sprintf("- %s: %d [Edit](%s)\n", field, value, link)) | ||
} | ||
|
||
for field := range boolFields { | ||
value := GetBoolField(address, field, false) | ||
link := ufmt.Sprintf(SetBoolFieldURL, field) | ||
b.WriteString(ufmt.Sprintf("- %s: %t [Edit](%s)\n", field, value, link)) | ||
} | ||
|
||
res.Write(b.String()) | ||
} | ||
|
||
func fieldHandler(res *mux.ResponseWriter, req *mux.Request) { | ||
var b bytes.Buffer | ||
addr := req.GetVar("addr") | ||
field := req.GetVar("field") | ||
|
||
b.WriteString(ufmt.Sprintf("# Field %s for %s\n", field, addr)) | ||
|
||
address := std.Address(addr) | ||
value := "n/a" | ||
var editLink string | ||
|
||
if _, ok := stringFields[field]; ok { | ||
value = ufmt.Sprintf("%s", GetStringField(address, field, "n/a")) | ||
editLink = ufmt.Sprintf(SetStringFieldURL+"&addr=%s&value=%s", field, addr, value) | ||
} else if _, ok := intFields[field]; ok { | ||
value = ufmt.Sprintf("%d", GetIntField(address, field, 0)) | ||
editLink = ufmt.Sprintf(SetIntFieldURL+"&addr=%s&value=%s", field, addr, value) | ||
} else if _, ok := boolFields[field]; ok { | ||
value = ufmt.Sprintf("%t", GetBoolField(address, field, false)) | ||
editLink = ufmt.Sprintf(SetBoolFieldURL+"&addr=%s&value=%s", field, addr, value) | ||
} | ||
|
||
b.WriteString(ufmt.Sprintf("- %s: %s [Edit](%s)\n", field, value, editLink)) | ||
|
||
res.Write(b.String()) | ||
} | ||
|
||
func Render(path string) string { | ||
return router.Render(path) | ||
} |