store.gno

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