store.gno

3.64 Kb ยท 173 lines
  1package users
  2
  3import (
  4	"regexp"
  5	"std"
  6
  7	"gno.land/p/demo/avl"
  8	"gno.land/p/demo/ufmt"
  9)
 10
 11var (
 12	nameStore    = avl.NewTree() // name/aliases > *UserData
 13	addressStore = avl.NewTree() // address > *UserData
 14
 15	reAddressLookalike = regexp.MustCompile(`^g1[a-z0-9]{20,38}$`)
 16	reAlphanum         = regexp.MustCompile(`^[a-zA-Z0-9_]{1,64}$`)
 17)
 18
 19const (
 20	RegisterUserEvent = "Registered"
 21	UpdateNameEvent   = "Updated"
 22	DeleteUserEvent   = "Deleted"
 23)
 24
 25type UserData struct {
 26	addr     std.Address
 27	username string // contains the latest name of a user
 28	deleted  bool
 29}
 30
 31func (u UserData) Name() string {
 32	return u.username
 33}
 34
 35func (u UserData) Addr() std.Address {
 36	return u.addr
 37}
 38
 39func (u UserData) IsDeleted() bool {
 40	return u.deleted
 41}
 42
 43// RenderLink provides a render link to the user page on gnoweb
 44// `linkText` is optional
 45func (u UserData) RenderLink(linkText string) string {
 46	// TODO switch to /u/username once the gnoweb page is ready.
 47	if linkText == "" {
 48		return ufmt.Sprintf("[@%s](/r/gnoland/users/v1:%s)", u.username, u.username)
 49	}
 50
 51	return ufmt.Sprintf("[%s](/r/gnoland/users/v1:%s)", linkText, u.username)
 52}
 53
 54// RegisterUser adds a new user to the system.
 55func RegisterUser(name string, address std.Address) error {
 56	// Validate caller
 57	if !controllers.Has(std.PreviousRealm().Address()) {
 58		return ErrNotWhitelisted
 59	}
 60
 61	// Validate name
 62	if err := validateName(name); err != nil {
 63		return err
 64	}
 65
 66	// Validate address
 67	if !address.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.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,
 90		username: name,
 91		deleted:  false,
 92	}
 93
 94	// Set corresponding stores
 95	nameStore.Set(name, data)
 96	addressStore.Set(address.String(), data)
 97
 98	std.Emit(RegisterUserEvent,
 99		"name", name,
100		"address", address.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(std.PreviousRealm().Address()) {
115		return ErrNotWhitelisted
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	std.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(std.PreviousRealm().Address()) {
146		return ErrNotWhitelisted
147	}
148
149	u.deleted = true
150
151	std.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 std.Address(username).IsValid() || reAddressLookalike.MatchString(username) {
169		return ErrNameLikeAddress
170	}
171
172	return nil
173}