Search Apps Documentation Source Content File Folder Download Copy Actions Download

store.gno

3.75 Kb · 174 lines
  1package users
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"regexp"
  7
  8	"gno.land/p/nt/avl/v0"
  9	"gno.land/p/nt/ufmt/v0"
 10)
 11
 12var (
 13	nameStore    = avl.NewTree() // name/aliases > *UserData
 14	addressStore = avl.NewTree() // address > *UserData
 15
 16	reAddressLookalike = regexp.MustCompile(`^g1[a-z0-9]{20,38}$`)
 17	reAlphanum         = regexp.MustCompile(`^[a-zA-Z0-9_]{1,64}$`)
 18)
 19
 20const (
 21	RegisterUserEvent = "Registered"
 22	UpdateNameEvent   = "Updated"
 23	DeleteUserEvent   = "Deleted"
 24)
 25
 26type UserData struct {
 27	addr     address
 28	username string // contains the latest name of a user
 29	deleted  bool
 30}
 31
 32func (u UserData) Name() string {
 33	return u.username
 34}
 35
 36func (u UserData) Addr() address {
 37	return u.addr
 38}
 39
 40func (u UserData) IsDeleted() bool {
 41	return u.deleted
 42}
 43
 44// RenderLink provides a render link to the user page on gnoweb
 45// `linkText` is optional
 46func (u UserData) RenderLink(linkText string) string {
 47	if linkText == "" {
 48		return ufmt.Sprintf("[@%s](/u/%s)", u.username, u.username)
 49	}
 50
 51	return ufmt.Sprintf("[%s](/u/%s)", linkText, u.username)
 52}
 53
 54// RegisterUser adds a new user to the system.
 55func RegisterUser(cur realm, name string, address_XXX address) error {
 56	// At genesis (height 0), allow any caller to register users.
 57	// After genesis, only whitelisted controllers can register.
 58	if runtime.ChainHeight() > 0 && !controllers.Has(runtime.PreviousRealm().Address()) {
 59		return NewErrNotWhitelisted()
 60	}
 61
 62	// Validate name
 63	if err := validateName(name); err != nil {
 64		return err
 65	}
 66
 67	// Validate address
 68	if !address_XXX.IsValid() {
 69		return ErrInvalidAddress
 70	}
 71
 72	// Check if name is taken
 73	if nameStore.Has(name) {
 74		return ErrNameTaken
 75	}
 76
 77	raw, ok := addressStore.Get(address_XXX.String())
 78	if ok {
 79		// Cannot re-register after deletion
 80		if raw.(*UserData).IsDeleted() {
 81			return ErrDeletedUser
 82		}
 83
 84		// For a second name, use UpdateName
 85		return ErrAlreadyHasName
 86	}
 87
 88	// Create UserData
 89	data := &UserData{
 90		addr:     address_XXX,
 91		username: name,
 92		deleted:  false,
 93	}
 94
 95	// Set corresponding stores
 96	nameStore.Set(name, data)
 97	addressStore.Set(address_XXX.String(), data)
 98
 99	chain.Emit(RegisterUserEvent,
100		"name", name,
101		"address", address_XXX.String(),
102	)
103	return nil
104}
105
106// UpdateName adds a name that is associated with a specific address
107// All previous names are preserved and resolvable.
108// The new name is the default value returned for address lookups.
109func (u *UserData) UpdateName(newName string) error {
110	if u == nil { // either doesnt exists or was deleted
111		return ErrUserNotExistOrDeleted
112	}
113
114	// Validate caller
115	if !controllers.Has(runtime.CurrentRealm().Address()) {
116		return NewErrNotWhitelisted()
117	}
118
119	// Validate name
120	if err := validateName(newName); err != nil {
121		return err
122	}
123
124	// Check if the requested Alias is already taken
125	if nameStore.Has(newName) {
126		return ErrNameTaken
127	}
128
129	u.username = newName
130	nameStore.Set(newName, u)
131
132	chain.Emit(UpdateNameEvent,
133		"alias", newName,
134		"address", u.addr.String(),
135	)
136	return nil
137}
138
139// Delete marks a user and all their aliases as deleted.
140func (u *UserData) Delete() error {
141	if u == nil {
142		return ErrUserNotExistOrDeleted
143	}
144
145	// Validate caller
146	if !controllers.Has(runtime.CurrentRealm().Address()) {
147		return NewErrNotWhitelisted()
148	}
149
150	u.deleted = true
151
152	chain.Emit(DeleteUserEvent, "address", u.addr.String())
153	return nil
154}
155
156// Validate validates username and address passed in
157// Most of the validation is done in the controllers
158// This provides more flexibility down the line
159func validateName(username string) error {
160	if username == "" {
161		return ErrEmptyUsername
162	}
163
164	if !reAlphanum.MatchString(username) {
165		return ErrInvalidUsername
166	}
167
168	// Check if the username can be decoded or looks like a valid address
169	if address(username).IsValid() || reAddressLookalike.MatchString(username) {
170		return ErrNameLikeAddress
171	}
172
173	return nil
174}