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}