Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(example): r/mouss #3472

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b55e295
first commit
mous1985 Dec 23, 2024
4cc7820
fix: import
mous1985 Jan 7, 2025
51f75c6
simple home-page
mous1985 Jan 8, 2025
ac0c168
add follower
mous1985 Jan 8, 2025
2d47ee0
add recipe to my homepage
mous1985 Jan 9, 2025
05d0853
refactor v0 of home page
mous1985 Jan 9, 2025
918c773
refactor: replace bytes.Buffer with strings.builder
mous1985 Jan 10, 2025
844a61c
refactor: use p/moul/addrset
mous1985 Jan 10, 2025
b5e2868
ref: add p/moul/md
mous1985 Jan 10, 2025
f64569a
doc
mous1985 Jan 10, 2025
4959189
ref: func writeRecipe to method (r Recipe)Render
mous1985 Jan 10, 2025
91845a6
Merge remote-tracking branch 'upstream/master' into home_page
mous1985 Jan 21, 2025
cdbd8de
ref: replace strings.Builder by var out string
mous1985 Jan 21, 2025
48795c4
Update examples/gno.land/r/mouss/config/config.gno
mous1985 Jan 21, 2025
91baa9c
ref : SetBackup(newAddress std.Address)
mous1985 Jan 21, 2025
e7f2e73
test: add config_test.gno
mous1985 Jan 21, 2025
bb096ac
Update examples/gno.land/r/mouss/home/home.gno
mous1985 Jan 21, 2025
b62f386
Update examples/gno.land/r/mouss/home/home.gno
mous1985 Jan 21, 2025
933f759
Update examples/gno.land/r/mouss/home/home.gno
mous1985 Jan 21, 2025
27a0b5d
ref: add new line between func
mous1985 Jan 21, 2025
2caabf7
test: add home_test.gno
mous1985 Jan 21, 2025
68b6a02
ref: randomnew line in home.gno
mous1985 Jan 22, 2025
e584e45
ref: random new line in home_test.gno
mous1985 Jan 22, 2025
d9f02e1
feat: add unfollow function
mous1985 Jan 22, 2025
0586b1b
fix: ci
mous1985 Jan 22, 2025
aafaf5e
fix: ci
mous1985 Jan 22, 2025
7874d67
fix: ci
mous1985 Jan 22, 2025
7d1e347
fix: ci
mous1985 Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions examples/gno.land/r/mouss/config/config.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package config

import (
"errors"
"std"
)

var (
main std.Address // mouss's main address
backup std.Address // backup address

ErrorInvalidAddr = errors.New("config: invalid address")
ErrorUnauthorized = errors.New("config: unauthorized")
)

func init() {
main = "g1hrfvdh7jdvnlxpk2y20tp3scj9jqal3zzu7wjz"
}

func Address() std.Address {
return main
}

func Backup() std.Address {
return backup
}

func SetBackup(newAddress std.Address) error {
if !newAddress.IsValid() {
return ErrorInvalidAddr
}

if err := checkAuthorized(); err != nil {
return err
}

backup = newAddress
return nil
}

func checkAuthorized() error {
caller := std.GetOrigCaller()
if caller != main && caller != backup {
return ErrorUnauthorized
}

return nil
}
63 changes: 63 additions & 0 deletions examples/gno.land/r/mouss/config/config_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package config
mous1985 marked this conversation as resolved.
Show resolved Hide resolved

import (
"std"
"testing"

"gno.land/p/demo/testutils"
"gno.land/p/demo/uassert"
)

var (
mainAddr = Address()
backupAddr = testutils.TestAddress("backup")
unauthorizedAddr = testutils.TestAddress("unauthorized")
)

func TestAddress(t *testing.T) {
addr := Address()
expected := std.Address("g1hrfvdh7jdvnlxpk2y20tp3scj9jqal3zzu7wjz")
uassert.Equal(t, expected, addr, "Address() should return initialized main address")
}

func TestSetBackup(t *testing.T) {
// Test setting backup as main address
std.TestSetOrigCaller(mainAddr)
validAddr := testutils.TestAddress("validbackup")
err := SetBackup(validAddr)
uassert.NoError(t, err, "main address should be able to set backup")

// Test setting invalid address format
err = SetBackup(std.Address("invalid"))
uassert.ErrorIs(t, err, ErrorInvalidAddr, "should reject invalid address format")

// Test setting empty address
err = SetBackup(std.Address(""))
uassert.ErrorIs(t, err, ErrorInvalidAddr, "should reject empty address")

// Test unauthorized caller
std.TestSetOrigCaller(unauthorizedAddr)
err = SetBackup(validAddr)
uassert.ErrorIs(t, err, ErrorUnauthorized, "should reject unauthorized caller")
}

func TestCheckAuthorized(t *testing.T) {
// Test main address authorization
std.TestSetOrigCaller(mainAddr)
err := checkAuthorized()
uassert.NoError(t, err, "main address should be authorized")

// Test unauthorized address
std.TestSetOrigCaller(unauthorizedAddr)
err = checkAuthorized()
uassert.ErrorIs(t, err, ErrorUnauthorized, "random address should not be authorized")

// Set and test backup address authorization
std.TestSetOrigCaller(mainAddr)
err = SetBackup(backupAddr)
uassert.NoError(t, err, "setting backup address should succeed")

std.TestSetOrigCaller(backupAddr)
err = checkAuthorized()
uassert.NoError(t, err, "backup address should be authorized")
}
1 change: 1 addition & 0 deletions examples/gno.land/r/mouss/config/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/mouss/config
1 change: 1 addition & 0 deletions examples/gno.land/r/mouss/home/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/mouss/home
222 changes: 222 additions & 0 deletions examples/gno.land/r/mouss/home/home.gno
mous1985 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package home

import (
"std"
"strconv"
"strings"

"gno.land/p/demo/mux"
"gno.land/p/demo/ufmt"

"gno.land/p/moul/addrset"
"gno.land/p/moul/md"
"gno.land/r/leon/hof"
"gno.land/r/mouss/config"
)

// Profile represents my personal profile information.
type Profile struct {
AboutMe string
Avatar string
Email string
Github string
LinkedIn string
Followers *addrset.Set // Set of followers addresses.
}

// Recipe represents a cooking recipe with its details.
type Recipe struct {
Name string
Origin string
Author std.Address
Ingredients string
Instructions string
Tips string
}

const (
RealmURL = "/r/mouss/home"
Rec = RealmURL + ":recipe/"
gnoArt = `
-==++.
*@@@@= @- -@
#@@@@@: -==-.-- :-::===: .-++-. @- .===:.- .-.-==- .===:=@
#@@@@@@@: -@@%**%@@ #@@#*#@@- *@@**@@* @- +%=::-*@ +@=-:-@* +%=::-*@
+@%#**#%@@ %@+ :@@ *@+ #@=+@% %@+ @= :@: -@ +% +%.@: -@
-: - *@%:..+@@ *@+ #@=-@@: :@@= @- .@= =@ +@ *%.@= =@
--:==+=-:=. =%@%#*@@ *@+ #@+ =%@%%@%= #* %#=.:%*===*@ +% +% -%*===*@
-++++=++++. =-:::*@# . . .::. .. :: .:: . . .:: .
.-=+++=: .*###%#=
::
`
)

var (
router = mux.NewRouter()
profile Profile
recipes []*Recipe
margheritaPizza *Recipe
)

// init initializes the router with the homepage and recipe routes.
func init() {

mous1985 marked this conversation as resolved.
Show resolved Hide resolved
router.HandleFunc("", renderHomepage)
router.HandleFunc("recipe/", renderRecipes)

profile = Profile{
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.",
Avatar: "https://github.com/mous1985/assets/blob/master/avatar.png?raw=true",
Email: "[email protected]",
Github: "https://github.com/mous1985",
LinkedIn: "https://www.linkedin.com/in/mustapha-benazzouz-88646887/",
Followers: &addrset.Set{},
}

margheritaPizza = &Recipe{
Name: "Authentic Margherita Pizza 🤌",
Origin: "Naples, 🇮🇹",
Author: config.Address(),
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",
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",
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.",
}

hof.Register()
}

// AddRecipe adds a new recipe in recipe page by users
func AddRecipe(name, origin, ingredients, instructions, tips string) string {
if err := validateRecipe(name, ingredients, instructions); err != nil {
panic(err)
}

recipe := &Recipe{
Name: name,
Origin: origin,
Author: std.PrevRealm().Addr(),
Ingredients: ingredients,
Instructions: instructions,
Tips: tips,
}
recipes = append(recipes, recipe)
return "Recipe added successfully!"
}

// validateRecipe checks if the provided recipe details are valid.
func validateRecipe(name, ingredients, instructions string) error {
if name == "" {
return ufmt.Errorf("recipe name cannot be empty")
}
if len(ingredients) == 0 {
return ufmt.Errorf("ingredients cannot be empty")
}
if len(instructions) == 0 {
return ufmt.Errorf("instructions cannot be empty")
}
return nil
}

// Follow allows a users to follow my home page.
// It checks if the caller is a valid user and if the address is already being followed.
// If the caller is not authorized, it returns an error.
// If the address is already being followed, it returns an error.
// Otherwise, it adds the address to the list of followers and returns nil.
// TODO:any user can follow and to be followed by any other user
// TODO: add a function to unfollow
mous1985 marked this conversation as resolved.
Show resolved Hide resolved

func Follow(addr std.Address) error {
caller := std.PrevRealm().Addr()
if !isUser(caller) {
return config.ErrorUnauthorized
}
if profile.Followers.Has(addr) {
return ufmt.Errorf("address %s is already following", addr)
}
profile.Followers.Add(addr) //can't add the same address twice
return nil
}

func isUser(addr std.Address) bool {
return !isAuthorized(addr)
}

func isAuthorized(addr std.Address) bool {
return addr == config.Address() || addr == config.Backup()
}

func renderRecipes(res *mux.ResponseWriter, req *mux.Request) {
var b strings.Builder
b.WriteString("## World Kitchen\n\n------\n\n")

b.WriteString(margheritaPizza.Render())

if len(recipes) == 0 {
b.WriteString("No recipes yet. Be the first to add one!\n")
res.Write(b.String())
return
}

for _, recipe := range recipes {
b.WriteString(recipe.Render())
}

res.Write(b.String())
}

func (r Recipe) Render() string {
var out string
out += md.H2(r.Name)
out += md.Bold("Author:") + "\n" + r.Author.String() + "\n\n"
out += md.Bold("Origin:") + "\n" + r.Origin + "\n\n"
out += md.Bold("Ingredients:") + "\n" + md.BulletList(strings.Split(r.Ingredients, "\n")) + "\n\n"
out += md.Bold("Instructions:") + "\n" + md.OrderedList(strings.Split(r.Instructions, "\n")) + "\n\n"

if r.Tips != "" {
out += md.Italic("💡 Tips:"+"\n"+r.Tips) + "\n\n"
}

out += md.HorizontalRule() + "\n"
return out
}

func renderHomepage(res *mux.ResponseWriter, req *mux.Request) {
var out string
writeNavigation(&out)
out += profile.Render()
res.Write(out)
}

func (p Profile) Render() string {
var out string

out += md.H1("Welcome to my Homepage") + "\n\n" + md.HorizontalRule() + "\n\n"
out += "```\n"
out += gnoArt
out += "```\n------"
out += md.HorizontalRule() + "\n\n" + md.H2("About Me") + "\n\n"
out += md.Image("avatar", p.Avatar) + "\n\n"
out += p.AboutMe + "\n\n" + md.HorizontalRule() + "\n\n"
out += md.H3("Contact") + "\n\n"
out += md.BulletList([]string{
"Email: " + p.Email,
"GitHub: " + md.Link("@mous1985", p.Github),
"LinkedIn: " + md.Link("Mustapha", p.LinkedIn),
})
out += "\n\n" + md.Bold("👤 Followers: ") + strconv.Itoa(p.Followers.Size())

return out
}

func writeNavigation(out *string) {
navItems := []string{
md.Link("Home", ""),
md.Link("World Kitchen", Rec),
md.Link("Hackerspace", "https://github.com/gnolang/hackerspace/issues/86#issuecomment-2535795751"),
}
*out += strings.Join(navItems, " | ") + "\n\n" + md.HorizontalRule() + "\n\n"
}

func Render(path string) string {
return router.Render(path)
}
Loading
Loading