github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/r/demo/userbook/userbook.gno (about) 1 // This realm demonstrates a small userbook system working with gnoweb 2 package userbook 3 4 import ( 5 "std" 6 "strconv" 7 8 "gno.land/p/demo/avl" 9 "gno.land/p/demo/mux" 10 "gno.land/p/demo/ufmt" 11 ) 12 13 type Signup struct { 14 account string 15 height int64 16 } 17 18 // signups - keep a slice of signed up addresses efficient pagination 19 var signups []Signup 20 21 // tracker - keep track of who signed up 22 var ( 23 tracker *avl.Tree 24 router *mux.Router 25 ) 26 27 const ( 28 defaultPageSize = 20 29 pathArgument = "number" 30 subPath = "page/{" + pathArgument + "}" 31 ) 32 33 func init() { 34 // Set up tracker tree 35 tracker = avl.NewTree() 36 37 // Set up route handling 38 router = mux.NewRouter() 39 router.HandleFunc("", renderHelper) 40 router.HandleFunc(subPath, renderHelper) 41 42 // Sign up the deployer 43 SignUp() 44 } 45 46 func SignUp() string { 47 // Get transaction caller 48 caller := std.PrevRealm().Addr().String() 49 height := std.GetHeight() 50 51 if _, exists := tracker.Get(caller); exists { 52 panic(caller + " is already signed up!") 53 } 54 55 tracker.Set(caller, struct{}{}) 56 signup := Signup{ 57 caller, 58 height, 59 } 60 61 signups = append(signups, signup) 62 return ufmt.Sprintf("%s added to userbook up at block #%d!", signup.account, signup.height) 63 } 64 65 func GetSignupsInRange(page, pageSize int) ([]Signup, int) { 66 if page < 1 { 67 panic("page number cannot be less than 1") 68 } 69 70 if pageSize < 1 || pageSize > 50 { 71 panic("page size must be from 1 to 50") 72 } 73 74 // Pagination 75 // Calculate indexes 76 startIndex := (page - 1) * pageSize 77 endIndex := startIndex + pageSize 78 79 // If page does not contain any users 80 if startIndex >= len(signups) { 81 return nil, -1 82 } 83 84 // If page contains fewer users than the page size 85 if endIndex > len(signups) { 86 endIndex = len(signups) 87 } 88 89 return signups[startIndex:endIndex], endIndex 90 } 91 92 func renderHelper(res *mux.ResponseWriter, req *mux.Request) { 93 totalSignups := len(signups) 94 res.Write("# Welcome to UserBook!\n\n") 95 96 // Get URL parameter 97 page, err := strconv.Atoi(req.GetVar("number")) 98 if err != nil { 99 page = 1 // render first page on bad input 100 } 101 102 // Fetch paginated signups 103 fetchedSignups, endIndex := GetSignupsInRange(page, defaultPageSize) 104 // Handle empty page case 105 if len(fetchedSignups) == 0 { 106 res.Write("No users on this page!\n\n") 107 res.Write("---\n\n") 108 res.Write("[Back to Page #1](/r/demo/userbook:page/1)\n\n") 109 return 110 } 111 112 // Write page title 113 res.Write(ufmt.Sprintf("## UserBook - Page #%d:\n\n", page)) 114 115 // Write signups 116 pageStartIndex := defaultPageSize * (page - 1) 117 for i, signup := range fetchedSignups { 118 out := ufmt.Sprintf("#### User #%d - %s - signed up at Block #%d\n", pageStartIndex+i, signup.account, signup.height) 119 res.Write(out) 120 } 121 122 res.Write("---\n\n") 123 124 // Write UserBook info 125 latestSignupIndex := totalSignups - 1 126 res.Write(ufmt.Sprintf("#### Total users: %d\n", totalSignups)) 127 res.Write(ufmt.Sprintf("#### Latest signup: User #%d at Block #%d\n", latestSignupIndex, signups[latestSignupIndex].height)) 128 129 res.Write("---\n\n") 130 131 // Write page number 132 res.Write(ufmt.Sprintf("You're viewing page #%d", page)) 133 134 // Write navigation buttons 135 var prevPage string 136 var nextPage string 137 // If we are on any page that is not the first page 138 if page > 1 { 139 prevPage = ufmt.Sprintf(" - [Previous page](/r/demo/userbook:page/%d)", page-1) 140 } 141 142 // If there are more pages after the current one 143 if endIndex < totalSignups { 144 nextPage = ufmt.Sprintf(" - [Next page](/r/demo/userbook:page/%d)\n\n", page+1) 145 } 146 147 res.Write(prevPage) 148 res.Write(nextPage) 149 } 150 151 func Render(path string) string { 152 return router.Render(path) 153 }