feeds.gno
4.34 Kb ยท 190 lines
1package social_feed
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nt/avl"
8 "gno.land/p/nt/seqid"
9 feedsv1 "gno.land/p/zenao/feeds/v1"
10 "gno.land/p/zenao/realmid"
11)
12
13type Feed struct {
14 CrossNetwork bool
15 AuthFunc AuthFn
16}
17
18func (f *Feed) auth() string {
19 if f.AuthFunc == nil {
20 return realmid.Previous()
21 }
22
23 id, ok := f.AuthFunc()
24 if !ok {
25 panic("the user is not allowed to interact with this feed")
26 }
27 return id
28}
29
30type AuthFn func() (userID string, authorized bool)
31
32var (
33 id seqid.ID
34
35 reactions *avl.Tree // post_id:icon -> count
36 userReactions *avl.Tree // post_id:icon:user_address -> struct{}
37
38 posts *avl.Tree // local_post_id -> *Post // for search with specific id (get/delete)
39 postsByFeed *avl.Tree // feed_id:local_post_id -> *Post // for search with feed id (iterate)
40
41 // XXX: add feed index into the composite key to ensure reply are in the same feed ?
42 postsByParent *avl.Tree // parent_id:local_post_id -> *Post // for search with parent_id
43 feeds *avl.Tree // slug -> *Feed
44
45 feedsByPost *avl.Tree // post_id -> feed_id
46)
47
48func init() {
49 reactions = avl.NewTree()
50 userReactions = avl.NewTree()
51
52 posts = avl.NewTree()
53 postsByFeed = avl.NewTree()
54 postsByParent = avl.NewTree()
55 feeds = avl.NewTree()
56 feedsByPost = avl.NewTree()
57}
58
59func NewFeed(_ realm, slug string, crossNetwork bool, authFunc AuthFn) string {
60 assertActive()
61
62 creator := realmid.Previous()
63 if crossNetwork && authFunc != nil {
64 panic("crossNetwork feeds cannot have an authFunc")
65 }
66
67 feed := &Feed{
68 CrossNetwork: crossNetwork,
69 AuthFunc: authFunc,
70 }
71 feedId := creator + ":" + slug
72 if feeds.Has(feedId) {
73 panic("feed already exists")
74 }
75 feeds.Set(feedId, feed)
76 return feedId
77}
78
79// XXX: do a registry by user ?
80// XXX: handle flagged posts
81// XXX: handle tags with an array of string
82// XXX: optimize research by tags with a new tree ?
83func GetFeedPosts(feedId string, offset uint32, limit uint32, tags string, user string) []*feedsv1.PostView {
84 if feedId == "" {
85 panic("feedId is empty")
86 }
87 var res []*feedsv1.PostView
88 i := 0
89 start := feedId + ":"
90 end := feedId + ";"
91 tagsSplit := []string{}
92 if tags != "" {
93 tagsSplit = strings.Split(tags, ",")
94 }
95 postsByFeed.ReverseIterate(start, end, func(key string, value interface{}) bool {
96 post := value.(*feedsv1.Post)
97 for _, tag := range tagsSplit {
98 if !hasTag(post.Tags, tag) {
99 return false
100 }
101 }
102 if post.DeletedAt != 0 {
103 return false
104 }
105 if post.ParentUri != "" {
106 return false
107 }
108 if i >= int(offset) {
109 postView := &feedsv1.PostView{
110 Post: post,
111 ChildrenCount: CountChildren(post.LocalPostId),
112 Reactions: GetPostReactions(post.LocalPostId, user),
113 }
114 res = append(res, postView)
115 }
116 i++
117 if i >= int(offset+limit) {
118 return true
119 }
120 return false
121 })
122 return res
123}
124
125// XXX: merge with above function (deduct from param the tree to use) ?
126func GetChildrenPosts(parentId string, offset uint32, limit uint32, tags string, user string) []*feedsv1.PostView {
127 if parentId == "" {
128 panic("parentId is empty")
129 }
130 parentIdInt, err := strconv.ParseUint(parentId, 10, 64)
131 if err != nil {
132 panic("parentId is not a valid uint64")
133 }
134 var res []*feedsv1.PostView
135 i := 0
136 start := seqid.ID(parentIdInt).String() + ":"
137 end := seqid.ID(parentIdInt).String() + ";"
138 tagsSplit := []string{}
139 if tags != "" {
140 tagsSplit = strings.Split(tags, ",")
141 }
142 postsByParent.ReverseIterate(start, end, func(key string, value interface{}) bool {
143 post := value.(*feedsv1.Post)
144 for _, tag := range tagsSplit {
145 if !hasTag(post.Tags, tag) {
146 return false
147 }
148 }
149 if post.DeletedAt != 0 {
150 return false
151 }
152 if i >= int(offset) {
153 postView := &feedsv1.PostView{
154 Post: post,
155 ChildrenCount: CountChildren(post.LocalPostId),
156 Reactions: GetPostReactions(post.LocalPostId, user),
157 }
158 res = append(res, postView)
159 }
160 i++
161 if i >= int(offset+limit) {
162 return true
163 }
164 return false
165 })
166 return res
167}
168
169func CountChildren(parentId uint64) uint64 {
170 count := uint64(0)
171 parentIdStr := seqid.ID(parentId).String()
172 postsByParent.ReverseIterate(parentIdStr+":", parentIdStr+";", func(key string, value interface{}) bool {
173 post := value.(*feedsv1.Post)
174 if post.DeletedAt != 0 {
175 return false
176 }
177 count++
178 return false
179 })
180 return count
181}
182
183func hasTag(tags []string, tag string) bool {
184 for _, t := range tags {
185 if t == tag {
186 return true
187 }
188 }
189 return false
190}