Kind Realm (renderable)
Name user
Namespace akkadia / beta
Exported functions 14
Module gno.land/r/akkadia/beta/user
gno 0.9 user.gno gno
⧉
1 package user 2 3 import ( 4 "chain" 5 "net/url" 6 "strconv" 7 "time" 8 AdminCreateUser (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string)
AdminDeleteUser (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string)
AdminUpdateUser (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string, propKeys string, propValues string)
CreateUser (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
DeleteAll (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
DeleteUser (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
GetListLimit () int
GetTotalUserSize () int
HasUser (addr string) bool
ListUsers (page int, count int) []string
ListUsersByAddresses (addrs ....uverse.address) []map[string]string
Render (iUrl string) string
SetFrozen (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, frozen bool)
SetListLimit (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, limit int)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
9 "gno.land/p/akkadia/beta/accesscontrol"
10 "gno.land/p/nt/bptree/v0"
11 "gno.land/r/akkadia/beta/admin"
12 )
13
14 const (
15 CreateUserEvent = "CreateUser"
16 UpdateUserEvent = "UpdateUser"
17 DeleteUserEvent = "DeleteUser"
18
19 createdAtKey = "created_at"
20 )
21
22 // users is address string -> map[string]string.
23 // The zero value is an empty BPTree with fanout 32.
24 var users bptree.BPTree
25
26 func HasUser(addr address) bool {
27 assertIsValidAddress(addr)
28 return users.Has(addr.String())
29 }
30
31 func CreateUser(cur realm) {
32 addr := accesscontrol.MustGetUserCaller( 0 , cur)
33 if isFrozen {
34 panic( "user self-service is frozen" )
35 }
36 addrStr := addr.String()
37 if users.Has(addrStr) {
38 panic( "user already exists: " + addrStr)
39 }
40 users.Set(addrStr, map [ string ] string {
41 createdAtKey: strconv.FormatInt(time.Now().Unix(), 10 ),
42 })
43 chain.Emit(CreateUserEvent, "address" , addrStr)
44 }
45
46 // AdminCreateUser is idempotent so an operator can safely retry registration.
47 func AdminCreateUser(cur realm, addr address) {
48 accesscontrol.AssertIsAdminOrOperator( 0 , cur, admin.IsAdmin, admin.IsOperator)
49 assertIsValidAddress(addr)
50 addrStr := addr.String()
51 if users.Has(addrStr) {
52 return
53 }
54 users.Set(addrStr, map [ string ] string {
55 createdAtKey: strconv.FormatInt(time.Now().Unix(), 10 ),
56 })
57 chain.Emit(CreateUserEvent, "address" , addrStr)
58 }
59
60 func DeleteUser(cur realm) {
61 addr := accesscontrol.MustGetUserCaller( 0 , cur)
62 if isFrozen {
63 panic( "user self-service is frozen" )
64 }
65 deleteUser(addr)
66 }
67
68 func AdminDeleteUser(cur realm, addr address) {
69 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
70 assertIsValidAddress(addr)
71 deleteUser(addr)
72 }
73
74 // DeleteAll removes registrations while preserving freeze and list settings.
75 func DeleteAll(cur realm) {
76 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
77 users = bptree.BPTree{}
78 }
79
80 func AdminUpdateUser(cur realm, addr address, propKeys, propValues string ) {
81 accesscontrol.AssertIsAdmin( 0 , cur, admin.IsAdmin)
82 assertIsValidAddress(addr)
83 addrStr := addr.String()
84 current := users.Get(addrStr)
85 if current == nil {
86 panic( "user not found: " + addrStr)
87 }
88 props := copyStringMap(current.( map [ string ] string ))
89 updates := parsePropertiesCSV(propKeys, propValues)
90 for key, value := range updates {
91 props[key] = value
92 }
93 users.Set(addrStr, props)
94 chain.Emit(UpdateUserEvent, "address" , addrStr)
95 }
96
97 func GetTotalUserSize() int {
98 return users.Size()
99 }
100
101 func ListUsers(page, count int ) [] string {
102 assertListPageCount(page, count)
103 result := [] string {}
104 offset := (page - 1 ) * count
105 users.IterateByOffset(offset, count, func (key string , _ any ) bool {
106 result = append(result, key)
107 return false
108 })
109 return result
110 }
111
112 func ListUsersByAddresses(addrs ...address) [] map [ string ] string {
113 assertListLimit( "addrs" , len(addrs))
114 result := [] map [ string ] string {}
115 for _, addr := range addrs {
116 assertIsValidAddress(addr)
117 addrStr := addr.String()
118 current := users.Get(addrStr)
119 if current == nil {
120 continue
121 }
122 props := copyStringMap(current.( map [ string ] string ))
123 props[ "address" ] = addrStr
124 result = append(result, props)
125 }
126 return result
127 }
128
129 func deleteUser(addr address) {
130 addrStr := addr.String()
131 if _, removed := users.Remove(addrStr); !removed {
132 panic( "user not found: " + addrStr)
133 }
134 chain.Emit(DeleteUserEvent, "address" , addrStr)
135 }
136
137 func copyStringMap(source map [ string ] string ) map [ string ] string {
138 result := map [ string ] string {}
139 for key, value := range source {
140 result[key] = value
141 }
142 return result
143 }
144
145 // parsePropertiesCSV accepts comma-separated keys and comma-separated values.
146 // Each value must be URL-encoded so commas and other separators can be carried
147 // safely; this parser URL-decodes values before returning the map.
148 func parsePropertiesCSV(keys, values string ) map [ string ] string {
149 if keys == "" {
150 panic( "properties keys must be not empty" )
151 }
152
153 result := map [ string ] string {}
154 keyStart, valStart := 0 , 0
155 keyIdx, valIdx := 0 , 0
156 for {
157 for keyIdx < len(keys) && keys[keyIdx] != ',' {
158 keyIdx++
159 }
160 for valIdx < len(values) && values[valIdx] != ',' {
161 valIdx++
162 }
163
164 key := keys[keyStart:keyIdx]
165 if key == "" {
166 panic( "properties key must be not empty" )
167 }
168 if _, found := result[key]; found {
169 panic( "properties key duplicated: " + key)
170 }
171
172 value, err := url.PathUnescape(values[valStart:valIdx])
173 if err != nil {
174 panic( "properties value URL decode failed: " + key)
175 }
176 result[key] = value
177
178 keyEnd := keyIdx >= len(keys)
179 valEnd := valIdx >= len(values)
180 if keyEnd != valEnd {
181 panic( "properties keys and values count mismatch" )
182 }
183 if keyEnd {
184 break
185 }
186
187 keyIdx++
188 valIdx++
189 keyStart = keyIdx
190 valStart = valIdx
191 }
192
193 return result
194 }
195
196 func assertIsValidAddress(addr address) {
197 if !addr.IsValid() {
198 panic( "invalid address: " + addr.String())
199 }
200 }
201
202 func assertListLimit(field string , count int ) {
203 if count > listLimit {
204 panic(field + " exceeds listLimit (max: " + strconv.Itoa(listLimit) + ")" )
205 }
206 }
207
208 func assertListPageCount(page int , count int ) {
209 if page < 1 {
210 panic( "page must be at least 1" )
211 }
212 if count < 1 {
213 panic( "count must be at least 1" )
214 }
215 assertListLimit( "count" , count)
216 }
217