diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 921492d81b4..ef6dce6f1d4 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -8,8 +8,18 @@ import ( "gno.land/p/demo/ui" blog "gno.land/r/gnoland/blog" events "gno.land/r/gnoland/events" + "gno.land/r/sys/teams" ) +func init() { + teams.SetMembers( + "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", teams.AdminRole, // @manfred + // TODO: add more people in the init + ) +} + +// TODO: add helpers to manage the team set from a DAO + // XXX: p/demo/ui API is crappy, we need to make it more idiomatic // XXX: use an updatable block system to update content from a DAO // XXX: var blocks avl.Tree diff --git a/examples/gno.land/r/sys/teams/teams.gno b/examples/gno.land/r/sys/teams/teams.gno new file mode 100644 index 00000000000..882d26ca7e1 --- /dev/null +++ b/examples/gno.land/r/sys/teams/teams.gno @@ -0,0 +1,89 @@ +package teams + +import ( + "errors" + "std" + "strings" + + "gno.land/p/demo/avl" +) + +type Team struct { + Admins []std.Address + Editors []std.Address +} + +var ( + ErrNotHomeCaller = errors.New("must be called by r//home userland logic") + ErrInvalidMembers = errors.New("invalid members argument") +) + +type Role uint + +const ( + RoleUnset = "" + RoleAdmin = "admin" // editor + right to upgrade r//home + RoleEditor = "editor" // can add package under {p,r}//... + // XXX: RoleReadonly = "readonly"? +) + +var teams avl.Tree // team -> []users + +// TODO: doc +func SetMembers(attrs ...string) { + // XXX: panic if members list is too big for now. + + attrsLen := len(attrs) + if attrsLen%2 != 0 { + panic(ErrInvalidMembers) + } + + prev := std.PrevRealm() + if prev.IsUser() { + panic(ErrNotHomeCaller) + } + + prevPath := prev.PkgPath() + parts := strings.Split(prevPath, "/") + if len(parts) != 4 || !strings.HasPrefix(prevPath, "gno.land/r/") || !strings.HasSuffix(prevPath, "/home") { + panic(ErrNotHomeCaller) + } + teamName := parts[2] + + team := Team{ + Admins: []std.Address{}, + Editors: []std.Address{}, + } + for i := 0; i < attrsLen-1; i += 2 { + addr := std.Address(attrs[i]) + role := attrs[i+1] + if role == RoleAdmin { + team.Admins = append(team.Admins, addr) + } + if role == RoleEditor { + team.Editors = append(team.Editors, addr) + } + } + + teams.Set(teamName, &team) +} + +func GetAdmins(teamName string) []std.Address { + t, ok := teams.Get(teamName) + if !ok { + return nil + } + team := t.(*Team) + return team.Admins +} + +func GetEditors(teamName string) []std.Address { + t, ok := teams.Get(teamName) + if !ok { + return nil + } + team := t.(*Team) + return append(team.Admins, team.Editors...) +} + +// XXX: func GetUserTeams(addr std.Address) diff --git a/examples/gno.land/r/sys/teams/teams_test.gno b/examples/gno.land/r/sys/teams/teams_test.gno new file mode 100644 index 00000000000..87128afccc8 --- /dev/null +++ b/examples/gno.land/r/sys/teams/teams_test.gno @@ -0,0 +1,48 @@ +package teams + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + +func TestPackage(t *testing.T) { + if GetAdmins("gnoland") != nil { + t.Errorf("gnoland should have no admins") + } + if GetEditors("gnoland") != nil { + t.Errorf("gnoland should have no editors") + } + + std.TestSetRealm(std.NewCodeRealm("gno.land/r/gnoland/home")) + test1 := testutils.TestAddress("test1") + test2 := testutils.TestAddress("test2") + SetMembers( + string(test1), string(RoleAdmin), + string(test2), string(RoleEditor), + ) + + admins := GetAdmins("gnoland") + if !isEqualAddressSlice(admins, []std.Address{test1}) { + t.Errorf("gnoland should have test1 as admins") + } + editors := GetEditors("gnoland") + if !isEqualAddressSlice(editors, []std.Address{test1, test2}) { + t.Errorf("gnoland should have test1 and test2 as editors") + } +} + +func isEqualAddressSlice(a, b []std.Address) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true +}