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}