From 4a48b66e0e151fe286e09fd5507c43a929646fd3 Mon Sep 17 00:00:00 2001 From: Omar Sy Date: Fri, 16 Feb 2024 22:25:19 +0100 Subject: [PATCH] feat: Add Social Network System --- .../gno.land/r/demo/teritori/social/gno.mod | 6 ++ .../r/demo/teritori/social/social.gno | 87 +++++++++++++++++++ .../r/demo/teritori/social/social_test.gno | 48 ++++++++++ .../gno.land/r/demo/teritori/social/spec.md | 44 ++++++++++ .../gno.land/r/demo/teritori/social/user.gno | 58 +++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 examples/gno.land/r/demo/teritori/social/gno.mod create mode 100644 examples/gno.land/r/demo/teritori/social/social.gno create mode 100644 examples/gno.land/r/demo/teritori/social/social_test.gno create mode 100644 examples/gno.land/r/demo/teritori/social/spec.md create mode 100644 examples/gno.land/r/demo/teritori/social/user.gno diff --git a/examples/gno.land/r/demo/teritori/social/gno.mod b/examples/gno.land/r/demo/teritori/social/gno.mod new file mode 100644 index 00000000000..c11628b603c --- /dev/null +++ b/examples/gno.land/r/demo/teritori/social/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/teritori/social + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest +) \ No newline at end of file diff --git a/examples/gno.land/r/demo/teritori/social/social.gno b/examples/gno.land/r/demo/teritori/social/social.gno new file mode 100644 index 00000000000..106ba9c3d3c --- /dev/null +++ b/examples/gno.land/r/demo/teritori/social/social.gno @@ -0,0 +1,87 @@ +package social + +import ( + "std" + + "gno.land/p/demo/avl" +) + +var ( + addr2User avl.Tree // std.Address -> *User +) + +func getOrCreateUser(addr std.Address) *User { + userI, ok := addr2User.Get(addr.String()) + if ok { + return userI.(*User) + } + user := &User{ + address: addr, + } + addr2User.Set(addr.String(), user) + return user +} + +func Follow(addr std.Address) { + // get caller/inviter. + caller := std.PrevRealm().Addr() + callerUser := getOrCreateUser(caller) + user := getOrCreateUser(addr) + callerUser.Follow(user) +} + +func Unfollow(addr std.Address) { + // get caller/inviter. + caller := std.PrevRealm().Addr() + callerUser := getOrCreateUser(caller) + user := getOrCreateUser(addr) + callerUser.Unfollow(user) +} + +func Followers(addr std.Address) []std.Address { + userI, ok := addr2User.Get(addr.String()) + if !ok { + return nil + } + user := userI.(*User) + return user.Followers() +} + +func FollowedCount(addr std.Address) uint{ + userI, ok := addr2User.Get(addr.String()) + if !ok { + return 0 + } + user := userI.(*User) + return uint(user.followeds.Size()) +} + +func Followed(addr std.Address) []std.Address { + userI, ok := addr2User.Get(addr.String()) + if !ok { + return nil + } + + user := userI.(*User) + + return user.Followed() +} + +func FollowersCount(addr std.Address) uint { + userI, ok := addr2User.Get(addr.String()) + if !ok { + return 0 + } + user := userI.(*User) + return uint(user.followers.Size()) +} + +func IsFollower(follower std.Address, followed std.Address) bool { + userI, ok := addr2User.Get(followed.String()) + if !ok { + return false + } + user := userI.(*User) + _, ok = user.followers.Get(follower.String()) + return ok +} \ No newline at end of file diff --git a/examples/gno.land/r/demo/teritori/social/social_test.gno b/examples/gno.land/r/demo/teritori/social/social_test.gno new file mode 100644 index 00000000000..8497c6b17f6 --- /dev/null +++ b/examples/gno.land/r/demo/teritori/social/social_test.gno @@ -0,0 +1,48 @@ +package social + + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + + +func TestFollow_Follow(t *testing.T) { + user := testutils.TestAddress("user") + std.TestSetOrigCaller(user) + + + Follow(user) + + if c := FollowersCount(user); c != 1 { + t.Fatalf("FollowersCount expected to have 1 has %d", c) + } + + if followers := Followers(user); followers[0].String() != string(user) { + t.Fatalf("Followers expected to have %s has %s", string(user), followers[0].String()) + } + + if followed := Followed(user); followed[0].String() != string(user) { + t.Fatalf("Followed expected to have %s has %s", followed[0].String()) + } +} + +func TestFollow_UnFollow(t *testing.T) { + user1 := testutils.TestAddress("user1") + user2 := testutils.TestAddress("user2") + std.TestSetOrigCaller(user1) + + Follow(user2) + if IsFollower(user1, user2) != true { + t.Fatalf("expected to be a follower") + } + + Unfollow(user2) + if IsFollower(user1, user2) != false { + t.Fatalf("expected to not be a follower") + } +} + + diff --git a/examples/gno.land/r/demo/teritori/social/spec.md b/examples/gno.land/r/demo/teritori/social/spec.md new file mode 100644 index 00000000000..115c5afc939 --- /dev/null +++ b/examples/gno.land/r/demo/teritori/social/spec.md @@ -0,0 +1,44 @@ +# Social Network System + +## Overview +The Social Network System is designed to facilitate user interactions within a social network. It allows users to follow and unfollow each other, as well as retrieve information about followers and followed users. + +## Modules + +### 1. User Management +- This module manages user information within the social network. +- Each user is represented by a `User` struct containing their address and lists of followers and followed users. + +### 2. Following Functionality +- Users can follow and unfollow other users. +- When a user follows another user, they are added to the followed user's list of followers, and the followed user is added to the user's list of followed users. +- When a user unfollows another user, they are removed from the followed user's list of followers, and the followed user is removed from the user's list of followed users. + +## Data Structures + +### User +```go +type User struct { + address std.Address + followers *avl.Tree // std.Address -> *User + followeds *avl.Tree // std.Address -> *User +} +``` + +## Functions + +### User Management +- `Followers() []std.Address`: Returns a list of addresses of users who are followers of the user. +- `Followed() []std.Address`: Returns a list of addresses of users whom the user is following. + +### Following Functionality +- `Follow(user *User)`: Adds the given user to the list of users being followed by the user. +- `Unfollow(user *User)`: Removes the given user from the list of users being followed by the user. + +## Realm Configuration Process +- Users are managed within the social network system using the provided functionality. +- The system utilizes an AVL tree data structure to efficiently store and retrieve user information. + +## Usage +- Users interact with the system by following or unfollowing other users. +- User information is maintained and updated dynamically as users follow and unfollow each other. diff --git a/examples/gno.land/r/demo/teritori/social/user.gno b/examples/gno.land/r/demo/teritori/social/user.gno new file mode 100644 index 00000000000..9983b7130b7 --- /dev/null +++ b/examples/gno.land/r/demo/teritori/social/user.gno @@ -0,0 +1,58 @@ +package social + +import ( + "std" + + "gno.land/p/demo/avl" +) + +type User struct { + address std.Address + followers avl.Tree + followeds avl.Tree +} + +func (u *User) Address() std.Address { + return u.address +} + +func (u *User) Followers() []std.Address { + followers := make([]std.Address, 0, u.followers.Size()) + u.followers.Iterate("", "", func(key string, value interface{}) bool { + follower := value.(*User) + followers = append(followers, follower.address) + return false + }) + return followers +} + +func (u *User) Followed() []std.Address { + followeds := make([]std.Address, 0, u.followeds.Size()) + u.followeds.Iterate("", "", func(key string, value interface{}) bool { + followed := value.(*User) + followeds = append(followeds, followed.address) + return false + }) + return followeds +} + +func (u *User) Follow(user *User) { + if _, ok := u.followeds.Get(user.address.String()); ok { + panic("already follow") + } + u.followeds.Set(user.address.String(), user) + user.followers.Set(u.address.String(), u) +} + +func (u *User) Unfollow(user *User) { + if _, ok := u.followeds.Get(user.address.String()); !ok { + panic("not follow") + } + if _, ok := u.followeds.Remove(user.address.String()); !ok { + panic("can't remove on followed") + } + + if _, ok := user.followers.Remove(u.address.String()); !ok { + panic("can't remove on follower") + } +} \ No newline at end of file