1package home
2
3import (
4 "std"
5 "strconv"
6 "strings"
7
8 "gno.land/p/demo/avl"
9 "gno.land/p/demo/mux"
10 "gno.land/p/demo/ufmt"
11 "gno.land/p/moul/addrset"
12 "gno.land/p/moul/md"
13 "gno.land/r/leon/hof"
14 "gno.land/r/mouss/config"
15)
16
17// Profile represents my personal profile information.
18type Profile struct {
19 AboutMe string
20 Avatar string
21 Email string
22 Github string
23 LinkedIn string
24 Followers *addrset.Set // Set of followers addresses.
25}
26
27// Recipe represents a cooking recipe with its details.
28type Recipe struct {
29 Name string
30 Origin string
31 Author std.Address
32 Ingredients string
33 Instructions string
34 Tips string
35}
36
37const (
38 realmURL = "/r/mouss/home"
39 rec = realmURL + ":recipe/"
40 gnoArt = `
41 -==++.
42 *@@@@= @- -@
43 #@@@@@: -==-.-- :-::===: .-++-. @- .===:.- .-.-==- .===:=@
44 #@@@@@@@: -@@%**%@@ #@@#*#@@- *@@**@@* @- +%=::-*@ +@=-:-@* +%=::-*@
45 +@%#**#%@@ %@+ :@@ *@+ #@=+@% %@+ @= :@: -@ +% +%.@: -@
46 -: - *@%:..+@@ *@+ #@=-@@: :@@= @- .@= =@ +@ *%.@= =@
47 --:==+=-:=. =%@%#*@@ *@+ #@+ =%@%%@%= #* %#=.:%*===*@ +% +% -%*===*@
48 -++++=++++. =-:::*@# . . .::. .. :: .:: . . .:: .
49 .-=+++=: .*###%#=
50 ::
51`
52)
53
54var (
55 router = mux.NewRouter()
56 profile Profile
57 recipes = avl.NewTree()
58 margheritaPizza *Recipe
59)
60
61// init initializes the router with the home page and recipe routes
62// sets up my profile information, and my recipe
63// and registers the home page in the hall of fame.
64func init() {
65 router.HandleFunc("", renderHomepage)
66 router.HandleFunc("recipe/", renderRecipes)
67 router.HandleFunc("recipe/{name}", renderRecipe)
68 profile = Profile{
69 AboutMe: "๐ I'm Mustapha, a contributor to gno.land project from France. I'm passionate about coding, exploring new technologies, and contributing to open-source projects. Besides my tech journey, I'm also a pizzaiolo ๐ who loves cooking and savoring good food.",
70 Avatar: "https://github.com/mous1985/assets/blob/master/avatar.png?raw=true",
71 Email: "mustapha.benazzouz@outlook.fr",
72 Github: "https://github.com/mous1985",
73 LinkedIn: "https://www.linkedin.com/in/mustapha-benazzouz-88646887/",
74 Followers: &addrset.Set{},
75 }
76 margheritaPizza = &Recipe{
77 Name: "Authentic Margherita Pizza ๐ค",
78 Origin: "Naples, ๐ฎ๐น",
79 Author: config.OwnableMain.Owner(),
80 Ingredients: " 1kg 00 flour\n 500ml water\n 3g fresh yeast\n 20g sea salt\n San Marzano tomatoes\n Fresh buffalo mozzarella\n Fresh basil\n Extra virgin olive oil",
81 Instructions: " Mix flour and water until incorporated\n Add yeast and salt, knead for 20 minutes\n Let rise for 2 hours at room temperature\n Divide into 250g balls\n Cold ferment for 24-48 hours\n Shape by hand, being gentle with the dough\n Top with crushed tomatoes, torn mozzarella, and basil\n Cook at 450ยฐC for 60-90 seconds",
82 Tips: "Use a pizza steel or stone preheated for at least 1 hour. The dough should be soft and extensible. For best results, cook in a wood-fired oven.",
83 }
84 hof.Register()
85}
86
87// AddRecipe adds a new recipe in recipe page by users
88func AddRecipe(name, origin, ingredients, instructions, tips string) string {
89 if err := validateRecipe(name, ingredients, instructions); err != nil {
90 panic(err)
91 }
92 recipe := &Recipe{
93 Name: name,
94 Origin: origin,
95 Author: std.PreviousRealm().Address(),
96 Ingredients: ingredients,
97 Instructions: instructions,
98 Tips: tips,
99 }
100 recipes.Set(name, recipe)
101 return "Recipe added successfully"
102}
103
104func UpdateAboutMe(about string) error {
105 if !config.IsAuthorized(std.PreviousRealm().Address()) {
106 panic(config.ErrUnauthorized)
107 }
108 profile.AboutMe = about
109 return nil
110}
111
112func UpdateAvatar(avatar string) error {
113 if !config.IsAuthorized(std.PreviousRealm().Address()) {
114 panic(config.ErrUnauthorized)
115 }
116 profile.Avatar = avatar
117 return nil
118}
119
120// validateRecipe checks if the provided recipe details are valid.
121func validateRecipe(name, ingredients, instructions string) error {
122 if name == "" {
123 return ufmt.Errorf("recipe name cannot be empty")
124 }
125 if len(ingredients) == 0 {
126 return ufmt.Errorf("ingredients cannot be empty")
127 }
128 if len(instructions) == 0 {
129 return ufmt.Errorf("instructions cannot be empty")
130 }
131 return nil
132}
133
134// Follow allows a users to follow my home page.
135// If the caller is admin it returns error.
136func Follow() error {
137 caller := std.PreviousRealm().Address()
138
139 if caller == config.OwnableMain.Owner() {
140 return ufmt.Errorf("you cannot follow yourself")
141 }
142 if profile.Followers.Add(caller) {
143 return nil
144 }
145 return ufmt.Errorf("you are already following")
146
147}
148
149// Unfollow allows a user to unfollow my home page.
150func Unfollow() error {
151 caller := std.PreviousRealm().Address()
152
153 if profile.Followers.Remove(caller) {
154 return nil
155 }
156 return ufmt.Errorf("you are not following")
157}
158
159// renderRecipes renders the list of recipes.
160func renderRecipes(res *mux.ResponseWriter, req *mux.Request) {
161 var out string
162 out += Header()
163 out += "## World Kitchen\n\n------\n\n"
164
165 // Link to margarita pizza recipe
166 out += "### Available Recipes:\n\n"
167 out += "* " + md.Link(margheritaPizza.Name, rec+"margheritaPizza") + "By : " + string(margheritaPizza.Author) + "\n"
168
169 // The list of all other recipes with clickable links
170 if recipes.Size() > 0 {
171 recipes.Iterate("", "", func(key string, value interface{}) bool {
172 recipe := value.(*Recipe)
173 out += "* " + md.Link(recipe.Name, rec+recipe.Name) + " By : " + recipe.Author.String() + "\n"
174 return false // continue iterating
175 })
176 out += "\n------\n\n"
177 } else {
178 out += "\nNo additional recipes yet. Be the first to add one!\n"
179 }
180 res.Write(out)
181}
182
183// renderRecipe renders the recipe details.
184func renderRecipe(res *mux.ResponseWriter, req *mux.Request) {
185 name := req.GetVar("name")
186 if name == "margheritaPizza" {
187 res.Write(margheritaPizza.Render())
188 return
189 }
190 value, exists := recipes.Get(name)
191 if !exists {
192 res.Write("Recipe not found")
193 return
194 }
195 recipe := value.(*Recipe)
196 res.Write(recipe.Render())
197}
198
199func (r Recipe) Render() string {
200 var out string
201 out += Header()
202 out += md.H2(r.Name)
203 out += md.Bold("Author:") + "\n" + r.Author.String() + "\n\n"
204 out += md.Bold("Origin:") + "\n" + r.Origin + "\n\n"
205 out += md.Bold("Ingredients:") + "\n" + md.BulletList(strings.Split(r.Ingredients, "\n")) + "\n\n"
206 out += md.Bold("Instructions:") + "\n" + md.OrderedList(strings.Split(r.Instructions, "\n")) + "\n\n"
207 if r.Tips != "" {
208 out += md.Italic("๐ก Tips:"+"\n"+r.Tips) + "\n\n"
209 }
210 out += md.HorizontalRule() + "\n"
211 return out
212}
213
214func renderHomepage(res *mux.ResponseWriter, req *mux.Request) {
215 var out string
216 out += Header()
217 out += profile.Render()
218 res.Write(out)
219}
220
221func (p Profile) Render() string {
222 var out string
223 out += md.H1("Welcome to my Homepage") + "\n\n" + md.HorizontalRule() + "\n\n"
224 out += "```\n"
225 out += gnoArt
226 out += "```\n------"
227 out += md.HorizontalRule() + "\n\n" + md.H2("About Me") + "\n\n"
228 out += md.Image("avatar", p.Avatar) + "\n\n"
229 out += p.AboutMe + "\n\n" + md.HorizontalRule() + "\n\n"
230 out += md.H3("Contact") + "\n\n"
231 out += md.BulletList([]string{
232 "Email: " + p.Email,
233 "GitHub: " + md.Link("@mous1985", p.Github),
234 "LinkedIn: " + md.Link("Mustapha", p.LinkedIn),
235 })
236 out += "\n\n" + md.Bold("๐ค Followers: ") + strconv.Itoa(p.Followers.Size())
237 return out
238}
239
240func Header() string {
241 navItems := []string{
242 md.Link("Home", realmURL),
243 md.Link("World Kitchen", rec),
244 md.Link("Hackerspace", "https://github.com/gnolang/hackerspace/issues/86#issuecomment-2535795751"),
245 }
246 return strings.Join(navItems, " | ") + "\n\n" + md.HorizontalRule() + "\n\n"
247}
248
249func Render(path string) string {
250 return router.Render(path)
251}
home.gno
7.91 Kb ยท 251 lines