Live video on the AT Protocol
1package model
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "time"
8
9 "github.com/bluesky-social/indigo/api/bsky"
10 lexutil "github.com/bluesky-social/indigo/lex/util"
11 "gorm.io/gorm"
12)
13
14type FeedPost struct {
15 URI string `json:"uri" gorm:"primaryKey;column:uri"`
16 CID string `json:"cid" gorm:"column:cid"`
17 CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;index:recent_replies"`
18 FeedPost *[]byte `json:"feedPost" gorm:"column:feed_post"`
19 RepoDID string `json:"repoDID" gorm:"column:repo_did"`
20 Repo *Repo `json:"repo,omitempty" gorm:"foreignKey:DID;references:RepoDID"`
21 Type string `json:"type" gorm:"column:type"`
22 ReplyRootURI *string `json:"replyRootURI,omitempty" gorm:"column:reply_root_uri"`
23 ReplyRoot *FeedPost `json:"replyRoot,omitempty" gorm:"foreignKey:uri;references:ReplyRootURI"`
24 ReplyRootRepoDID *string `json:"replyRootRepoDID,omitempty" gorm:"column:reply_root_repo_did;index:recent_replies"`
25 ReplyRootRepo *Repo `json:"replyRootRepo,omitempty" gorm:"foreignKey:DID;references:ReplyRootRepoDID"`
26 IndexedAt *time.Time `json:"indexedAt,omitempty" gorm:"column:indexed_at"`
27}
28
29func (fp *FeedPost) ToBskyPostView() (*bsky.FeedDefs_PostView, error) {
30 rec, err := lexutil.CborDecodeValue(*fp.FeedPost)
31 if err != nil {
32 return nil, fmt.Errorf("error decoding feed post: %w", err)
33 }
34 postView := bsky.FeedDefs_PostView{
35 LexiconTypeID: "app.bsky.feed.defs#postView",
36 Cid: fp.CID,
37 Uri: fp.URI,
38 Author: &bsky.ActorDefs_ProfileViewBasic{
39 Did: fp.RepoDID,
40 },
41 Record: &lexutil.LexiconTypeDecoder{Val: rec},
42 IndexedAt: fp.IndexedAt.UTC().Format(time.RFC3339Nano),
43 }
44 if fp.Repo != nil {
45 postView.Author.Handle = fp.Repo.Handle
46 }
47 return &postView, nil
48}
49
50func (m *DBModel) CreateFeedPost(ctx context.Context, post *FeedPost) error {
51 return m.DB.Create(post).Error
52}
53
54func (m *DBModel) ListFeedPosts() ([]FeedPost, error) {
55 posts := []FeedPost{}
56 err := m.DB.Find(&posts).Error
57 if err != nil {
58 return nil, fmt.Errorf("error retrieving chat posts: %w", err)
59 }
60 return posts, nil
61}
62func (m *DBModel) ListFeedPostsByType(feedType string, limit int, after int64) ([]FeedPost, error) {
63 if after == 0 {
64 after = time.Now().Add(48 * time.Hour).UnixMilli()
65 }
66 time := time.UnixMilli(after)
67 posts := []FeedPost{}
68 // exclude scumb.ag for now (so my dev streams don't show up)
69 err := m.DB.Where("type = ? AND created_at < ? AND repo_did != ?", feedType, time.UTC(), "did:plc:dkh4rwafdcda4ko7lewe43ml").
70 Order("created_at DESC").
71 Group("uri").
72 Limit(limit).
73 Find(&posts).Error
74 if err != nil {
75 return nil, fmt.Errorf("error retrieving feed posts: %w", err)
76 }
77 return posts, nil
78}
79
80func (m *DBModel) GetFeedPost(uri string) (*FeedPost, error) {
81 post := FeedPost{}
82 err := m.DB.Where("uri = ?", uri).First(&post).Error
83 if errors.Is(err, gorm.ErrRecordNotFound) {
84 return nil, nil
85 }
86 if err != nil {
87 return nil, fmt.Errorf("error retrieving feed post: %w", err)
88 }
89 return &post, nil
90}
91
92func (m *DBModel) GetReplies(repoDID string) ([]*bsky.FeedDefs_PostView, error) {
93 posts := []FeedPost{}
94 err := m.DB.
95 Preload("Repo").
96 Where("reply_root_repo_did = ? AND type = ?", repoDID, "reply").
97 Limit(100).
98 Order("created_at DESC").
99 Find(&posts).Error
100 if err != nil {
101 return nil, fmt.Errorf("error retrieving replies: %w", err)
102 }
103 bskyPosts := []*bsky.FeedDefs_PostView{}
104 for _, post := range posts {
105 bskyPost, err := post.ToBskyPostView()
106 if err != nil {
107 return nil, fmt.Errorf("error converting feed post to bsky post view: %w", err)
108 }
109 bskyPosts = append(bskyPosts, bskyPost)
110 }
111 return bskyPosts, nil
112}
113
114type StreamplaceFeedPostLivestream struct {
115 URL string `json:"url"`
116 Title string `json:"title"`
117}
118
119func (m *DBModel) GetLatestLivestream(repoDID string) (*bsky.FeedDefs_PostView, error) {
120 posts := []FeedPost{}
121 err := m.DB.
122 Preload("Repo").
123 Where("type = ?", "livestream").
124 Where("repo_did = ?", repoDID).
125 Limit(1).
126 Order("created_at DESC").
127 Find(&posts).Error
128 if err != nil {
129 return nil, fmt.Errorf("error retrieving livestream: %w", err)
130 }
131
132 if len(posts) == 0 {
133 return nil, nil
134 }
135
136 view, err := posts[0].ToBskyPostView()
137 if err != nil {
138 return nil, fmt.Errorf("error converting feed post to bsky post view: %w", err)
139 }
140
141 return view, nil
142}