Search Apps Documentation Source Content File Folder Download Copy Actions Download

store.gno

3.61 Kb ยท 173 lines
  1package users
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"regexp"
  7
  8	"gno.land/p/nt/avl"
  9	"gno.land/p/nt/ufmt"
 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	// Validate caller
 57	if !controllers.Has(runtime.PreviousRealm().Address()) {
 58		return NewErrNotWhitelisted()
 59	}
 60
 61	// Validate name
 62	if err := validateName(name); err != nil {
 63		return err
 64	}
 65
 66	// Validate address
 67	if !address_XXX.IsValid() {
 68		return ErrInvalidAddress
 69	}
 70
 71	// Check if name is taken
 72	if nameStore.Has(name) {
 73		return ErrNameTaken
 74	}
 75
 76	raw, ok := addressStore.Get(address_XXX.String())
 77	if ok {
 78		// Cannot re-register after deletion
 79		if raw.(*UserData).IsDeleted() {
 80			return ErrDeletedUser
 81		}
 82
 83		// For a second name, use UpdateName
 84		return ErrAlreadyHasName
 85	}
 86
 87	// Create UserData
 88	data := &UserData{
 89		addr:     address_XXX,
 90		username: name,
 91		deleted:  false,
 92	}
 93
 94	// Set corresponding stores
 95	nameStore.Set(name, data)
 96	addressStore.Set(address_XXX.String(), data)
 97
 98	chain.Emit(RegisterUserEvent,
 99		"name", name,
100		"address", address_XXX.String(),
101	)
102	return nil
103}
104
105// UpdateName adds a name that is associated with a specific address
106// All previous names are preserved and resolvable.
107// The new name is the default value returned for address lookups.
108func (u *UserData) UpdateName(newName string) error {
109	if u == nil { // either doesnt exists or was deleted
110		return ErrUserNotExistOrDeleted
111	}
112
113	// Validate caller
114	if !controllers.Has(runtime.CurrentRealm().Address()) {
115		return NewErrNotWhitelisted()
116	}
117
118	// Validate name
119	if err := validateName(newName); err != nil {
120		return err
121	}
122
123	// Check if the requested Alias is already taken
124	if nameStore.Has(newName) {
125		return ErrNameTaken
126	}
127
128	u.username = newName
129	nameStore.Set(newName, u)
130
131	chain.Emit(UpdateNameEvent,
132		"alias", newName,
133		"address", u.addr.String(),
134	)
135	return nil
136}
137
138// Delete marks a user and all their aliases as deleted.
139func (u *UserData) Delete() error {
140	if u == nil {
141		return ErrUserNotExistOrDeleted
142	}
143
144	// Validate caller
145	if !controllers.Has(runtime.CurrentRealm().Address()) {
146		return NewErrNotWhitelisted()
147	}
148
149	u.deleted = true
150
151	chain.Emit(DeleteUserEvent, "address", u.addr.String())
152	return nil
153}
154
155// Validate validates username and address passed in
156// Most of the validation is done in the controllers
157// This provides more flexibility down the line
158func validateName(username string) error {
159	if username == "" {
160		return ErrEmptyUsername
161	}
162
163	if !reAlphanum.MatchString(username) {
164		return ErrInvalidUsername
165	}
166
167	// Check if the username can be decoded or looks like a valid address
168	if address(username).IsValid() || reAddressLookalike.MatchString(username) {
169		return ErrNameLikeAddress
170	}
171
172	return nil
173}