Search Apps Documentation Source Content File Folder Download Copy Actions Download

store.gno

4.48 Kb · 199 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 without checking controllers
 55func registerUser(cur realm, name string, address_XXX address) error {
 56	// Validate name
 57	if err := validateName(name); err != nil {
 58		return err
 59	}
 60
 61	// Validate address
 62	if !address_XXX.IsValid() {
 63		return ErrInvalidAddress
 64	}
 65
 66	// Check if name is taken
 67	if nameStore.Has(name) {
 68		return ErrNameTaken
 69	}
 70
 71	raw, ok := addressStore.Get(address_XXX.String())
 72	if ok {
 73		// Cannot re-register after deletion
 74		if raw.(*UserData).IsDeleted() {
 75			return ErrDeletedUser
 76		}
 77
 78		// For a second name, use UpdateName
 79		return ErrAlreadyHasName
 80	}
 81
 82	// Create UserData
 83	data := &UserData{
 84		addr:     address_XXX,
 85		username: name,
 86		deleted:  false,
 87	}
 88
 89	// Set corresponding stores
 90	nameStore.Set(name, data)
 91	addressStore.Set(address_XXX.String(), data)
 92
 93	chain.Emit(RegisterUserEvent,
 94		"name", name,
 95		"address", address_XXX.String(),
 96	)
 97	return nil
 98}
 99
100// RegisterUser adds a new user to the system.
101func RegisterUser(cur realm, name string, address_XXX address) error {
102	// At genesis (height 0), allow any caller to register users.
103	// After genesis, only whitelisted controllers can register.
104	if runtime.ChainHeight() > 0 && !controllers.Has(runtime.PreviousRealm().Address()) {
105		return NewErrNotWhitelisted()
106	}
107
108	return registerUser(cur, name, address_XXX)
109}
110
111// updateName adds a name that is associated with a specific address without checking controllers
112// All previous names are preserved and resolvable.
113// The new name is the default value returned for address lookups.
114func (u *UserData) updateName(newName string) error {
115	if u == nil { // either doesn't exist or was deleted
116		return ErrUserNotExistOrDeleted
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// UpdateName adds a name that is associated with a specific address
140// All previous names are preserved and resolvable.
141// The new name is the default value returned for address lookups.
142func (u *UserData) UpdateName(newName string) error {
143	if u == nil {
144		return ErrUserNotExistOrDeleted
145	}
146
147	// Validate caller
148	if !controllers.Has(runtime.CurrentRealm().Address()) {
149		return NewErrNotWhitelisted()
150	}
151
152	return u.updateName(newName)
153}
154
155// delete marks a user and all their aliases as deleted without checking controllers.
156func (u *UserData) delete() error {
157	if u == nil {
158		return ErrUserNotExistOrDeleted
159	}
160
161	u.deleted = true
162
163	chain.Emit(DeleteUserEvent, "address", u.addr.String())
164	return nil
165}
166
167// Delete marks a user and all their aliases as deleted.
168func (u *UserData) Delete() error {
169	if u == nil {
170		return ErrUserNotExistOrDeleted
171	}
172
173	// Validate caller
174	if !controllers.Has(runtime.CurrentRealm().Address()) {
175		return NewErrNotWhitelisted()
176	}
177
178	return u.delete()
179}
180
181// Validate validates username and address passed in
182// Most of the validation is done in the controllers
183// This provides more flexibility down the line
184func validateName(username string) error {
185	if username == "" {
186		return ErrEmptyUsername
187	}
188
189	if !reAlphanum.MatchString(username) {
190		return ErrInvalidUsername
191	}
192
193	// Check if the username can be decoded or looks like a valid address
194	if address(username).IsValid() || reAddressLookalike.MatchString(username) {
195		return ErrNameLikeAddress
196	}
197
198	return nil
199}