diff --git a/examples/gno.land/r/profile/profile.gno b/examples/gno.land/r/profile/profile.gno new file mode 100644 index 00000000000..5080cd72964 --- /dev/null +++ b/examples/gno.land/r/profile/profile.gno @@ -0,0 +1,59 @@ +package profile + +import ( + "std" + + "gno.land/p/ufmt" + // "gno.land/r/users" +) + +// FIXME: manage privacy? + +func newProfile(addr std.Address) *Profile { + now := std.GetTimestamp() + return &Profile{ + address: addr, + created: now, + } +} + +type Profile struct { + dict map[string]interface{} + address std.Address + created std.Time + updated std.Time +} + +func (p *Profile) save() { + now := std.GetTimestamp() + if p.created == 0 { + p.created = now + } + p.updated = now +} + +func (p *Profile) update(dict map[string]interface{}) { + for k, v := range dict { + // TODO: additional checks here + // TODO: check v.Type() + // TODO: check v.Len() + if k == "address" || k == "created" || k == "updated" { + panic("reserved profile key") + } + p.dict[k] = v + } + p.save() +} + +func (p *Profile) Render() string { + output := "" + output += ufmt.Sprintf("* address: %q\n", p.address) + output += ufmt.Sprintf("* created: %v\n", p.created) + if p.updated > 0 { + output += ufmt.Sprintf("* updated: %v\n", p.updated) + } + for k, v := range p.dict { + output += ufmt.Sprintf("* %s: %v\n", k, v) + } + return output +} diff --git a/examples/gno.land/r/profile/profiles.gno b/examples/gno.land/r/profile/profiles.gno new file mode 100644 index 00000000000..e1b2a1877a1 --- /dev/null +++ b/examples/gno.land/r/profile/profiles.gno @@ -0,0 +1,66 @@ +package profile + +import ( + "std" + "strings" + + "gno.land/p/avl" + "gno.land/p/ufmt" + "gno.land/r/users" +) + +// TODO: makes sense to implement an allowance system? + +var profiles *avl.MutTree // std.Address.String() -> *Profile + +func init() { + // profiles = avl.NewMutTree() +} + +func Update(dict map[string]interface{}) { + // FIXME: ask a price per stored data length? + currentUser := std.GetOrigCaller() + profile := getOrCreateProfileByAddress(currentUser) + profile.update(dict) +} + +func GetByAddressOrName(aon users.AddressOrName) *Profile { + addr := aon.Resolve() + profile, found := profiles.Get(addr.String()) + if !found { + return nil + } + return profile.(*Profile) +} + +func getOrCreateProfileByAddress(addr std.Address) *Profile { + // lookup existing profile + profile, found := profiles.Get(addr.String()) + if found { + return profile.(*Profile) + } + + // create + newProfile := &Profile{address: addr} + profiles.Set(addr.String(), newProfile) + return newProfile +} + +func Render(path string) string { + parts := strings.Split(path, "/") + + switch { + case path == "": + output := ufmt.Sprintf("stats: %d known profiles\n", profiles.Size()) + return output + case len(parts) == 1: + aon := users.AddressOrName(parts[0]) + profile := GetByAddressOrName(aon) + if profile != nil { + return profile.Render() + } + return "404: no such profile" + } + + return "404: invalid URL" +} diff --git a/examples/gno.land/r/profile/profiles_test.gno b/examples/gno.land/r/profile/profiles_test.gno new file mode 100644 index 00000000000..6792a250c75 --- /dev/null +++ b/examples/gno.land/r/profile/profiles_test.gno @@ -0,0 +1,17 @@ +package profile + +import ( + "fmt" + "testing" + + "gno.land/r/profile" +) + +func TestRender(t *testing.T) { + got := profile.Render("") + fmt.Println(got) + //_ = profile.Render + // various data types + // avatar + // ip address +}