Skip to content

Commit

Permalink
feat: add social follow system (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
omarsy authored and Villaquiranm committed Aug 18, 2024
1 parent a2e42d9 commit 1c0c4ee
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 0 deletions.
56 changes: 56 additions & 0 deletions examples/gno.land/r/demo/teritori/social_follow/CMD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
gnokey maketx addpkg \
-deposit="1ugnot" \
-gas-fee="1ugnot" \
-gas-wanted="5000000" \
-broadcast="true" \
-remote="51.15.236.215:26657" \
-chainid="teritori-1" \
-pkgdir="." \
-pkgpath="gno.land/r/demo/social_follow_2" \
mykey2

gnokey maketx call \
-gas-fee="1ugnot" \
-gas-wanted="5000000" \
-broadcast="true" \
-remote="51.15.236.215:26657" \
-chainid="teritori-1" \
-pkgpath="gno.land/r/demo/social_follow_2" \
-func="Follow" \
-args="g1c5y8jpe585uezcvlmgdjmk5jt2glfw88wxa3xq" \
mykey3

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
Followers("g1c5y8jpe585uezcvlmgdjmk5jt2glfw88wxa3xq",0,1)' -remote="51.15.236.215:26657"

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
FollowersCount("g1c5y8jpe585uezcvlmgdjmk5jt2glfw88wxa3xq")' -remote="51.15.236.215:26657"

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
Followed("g1j3ylca07vlhklzftrznw7jyquzqf2wtxvjdm4r",0,1)' -remote="51.15.236.215:26657"

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
FollowedCount("g1j3ylca07vlhklzftrznw7jyquzqf2wtxvjdm4r")' -remote="51.15.236.215:26657"

gnokey maketx call \
-gas-fee="1ugnot" \
-gas-wanted="5000000" \
-broadcast="true" \
-remote="51.15.236.215:26657" \
-chainid="teritori-1" \
-pkgpath="gno.land/r/demo/social_follow_2" \
-func="Unfollow" \
-args="g1c5y8jpe585uezcvlmgdjmk5jt2glfw88wxa3xq" \
mykey3

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
Followers("g1c5y8jpe585uezcvlmgdjmk5jt2glfw88wxa3xq",0,1)' -remote="51.15.236.215:26657"

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
FollowersCount("g1c5y8jpe585uezcvlmgdjmk5jt2glfw88wxa3xq")' -remote="51.15.236.215:26657"

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
Followed("g1j3ylca07vlhklzftrznw7jyquzqf2wtxvjdm4r",0,1)' -remote="51.15.236.215:26657"

gnokey query "vm/qeval" -data='gno.land/r/demo/social_follow_2
FollowedCount("g1j3ylca07vlhklzftrznw7jyquzqf2wtxvjdm4r")' -remote="51.15.236.215:26657"
83 changes: 83 additions & 0 deletions examples/gno.land/r/demo/teritori/social_follow/follow.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package social_follow

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) {
caller := std.PrevRealm().Addr()
callerUser := getOrCreateUser(caller)
user := getOrCreateUser(addr)
callerUser.Follow(user)
}

func Unfollow(addr std.Address) {
caller := std.PrevRealm().Addr()
callerUser := getOrCreateUser(caller)
user := getOrCreateUser(addr)
callerUser.Unfollow(user)
}

func Followers(addr std.Address, page, pageSize int) []std.Address {
userI, ok := addr2User.Get(addr.String())
if !ok {
return nil
}
user := userI.(*User)
return user.Followers(page, pageSize)
}

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, page, pageSize int) []std.Address {
userI, ok := addr2User.Get(addr.String())
if !ok {
return nil
}

user := userI.(*User)

return user.Followed(page, pageSize)
}

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
}
106 changes: 106 additions & 0 deletions examples/gno.land/r/demo/teritori/social_follow/follow_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package social_follow

import (
"std"
"testing"

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

func TestFollow_Follow(t *testing.T) {
addr2User = avl.Tree{}
main := testutils.TestAddress("main")
std.TestSetOrigCaller(main)

user := testutils.TestAddress("user")

Follow(user)

if c := FollowersCount(user); c != 1 {
t.Fatalf("FollowersCount expected to have 1 has %d", c)
}

if followers := Followers(user, 0, 1); followers[0].String() != string(main) {
t.Fatalf("Followers expected to have %s has %s", string(main), followers[0].String())
}

if followed := Followed(main, 0, 1); followed[0].String() != string(user) {
t.Fatalf("Followed expected to have %s has %s", string(user), followed[0].String())
}
}

func TestFollow_UnFollow(t *testing.T) {
addr2User = avl.Tree{}

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")
}
}

func TestFollow_FollowedPagination(t *testing.T) {
addr2User = avl.Tree{}

main := testutils.TestAddress("main")
std.TestSetOrigCaller(main)
for i := 0; i < 10; i++ {
user := testutils.TestAddress("user" + string(i))
Follow(user)
}

for i := 0; i < 10; i++ {
if followed := Followed(main, i, 1); len(followed) != 1 {
t.Fatalf("at page %d expected to have 1 has %d", i, len(followed))
}
}

if followed := Followed(main, 10, 1); len(followed) != 0 {
t.Fatalf("at page 10 expected to have 0 has %d", len(followed))
}

if followed := Followed(main, 0, 10); len(followed) != 10 {
t.Fatalf("at page 0 expected to have 10 has %d", len(followed))
}

if followed := Followed(main, 1, 8); len(followed) != 2 {
t.Fatalf("at page 2 expected to have 2 has %d", len(followed))
}
}

func TestFollow_FollowersPagination(t *testing.T) {
main := testutils.TestAddress("main")

for i := 0; i < 10; i++ {
user := testutils.TestAddress("user" + string(i))
std.TestSetOrigCaller(user)
Follow(main)
}

for i := 0; i < 10; i++ {
if followers := Followers(main, i, 1); len(followers) != 1 {
t.Fatalf("at page %d expected to have 1 has %d", i, len(followers))
}
}

if followers := Followers(main, 10, 1); len(followers) != 0 {
t.Fatalf("at page 10 expected to have 0 has %d", len(followers))
}

if followers := Followers(main, 0, 10); len(followers) != 10 {
t.Fatalf("at page 0 expected to have 10 has %d", len(followers))
}

if followers := Followers(main, 1, 8); len(followers) != 2 {
t.Fatalf("at page 2 expected to have 2 has %d", len(followers))
}
}
6 changes: 6 additions & 0 deletions examples/gno.land/r/demo/teritori/social_follow/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module gno.land/r/demo/teritori/social_follow

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/testutils v0.0.0-latest
)
46 changes: 46 additions & 0 deletions examples/gno.land/r/demo/teritori/social_follow/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Social Follow 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 follow.
- 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(page, pageSize int) []std.Address `: Returns a list of addresses of users following the user.
- `Followed(page, pageSize int) []std.Address `: Returns a list of addresses of users whom the user is following.
- `FollowedCount(addr std.Address) uint`: Returns the number of users being followed by the user with the given address.
- `FollowersCount(addr std.Address) uint`: Returns the number of users following the user with the given address.

### 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.
61 changes: 61 additions & 0 deletions examples/gno.land/r/demo/teritori/social_follow/user.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package social_follow

import (
"std"

"gno.land/p/demo/avl"
)

type User struct {
address std.Address
followers avl.Tree // std.Address -> *User
followeds avl.Tree // std.Address -> *User
}

func (u *User) Address() std.Address {
return u.address
}

func (u *User) Followers(page, pageSize int) []std.Address {
followers := make([]std.Address, 0, u.followers.Size())
u.followers.IterateByOffset(page*pageSize, pageSize, func(key string, value interface{}) bool {
follower := value.(*User)
followers = append(followers, follower.address)
return false
})
return followers
}

func (u *User) Followed(page, pageSize int) []std.Address {
followeds := make([]std.Address, 0, u.followeds.Size())
u.followeds.IterateByOffset(page*pageSize, pageSize, 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 u.address == user.address {
panic("can't follow self")
}
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")
}
}

0 comments on commit 1c0c4ee

Please sign in to comment.