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 CID string `json:"cid" gorm:"primaryKey;column:cid"`
16 URI string `json:"uri"`
17 CreatedAt time.Time `json:"createdAt" gorm:"column:created_at;index:recent_replies"`
18 FeedPost *[]byte `json:"feedPost"`
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 ReplyRootCID *string `json:"replyRootCID,omitempty" gorm:"column:reply_root_cid"`
23 ReplyRoot *FeedPost `json:"replyRoot,omitempty" gorm:"foreignKey:cid;references:ReplyRootCID"`
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 Handle: fp.Repo.Handle,
41 },
42 Record: &lexutil.LexiconTypeDecoder{Val: rec},
43 IndexedAt: fp.IndexedAt.UTC().Format(time.RFC3339Nano),
44 }
45 return &postView, nil
46}
47
48func (m *DBModel) CreateFeedPost(ctx context.Context, post *FeedPost) error {
49 return m.DB.Create(post).Error
50}
51
52func (m *DBModel) ListFeedPosts() ([]FeedPost, error) {
53 posts := []FeedPost{}
54 err := m.DB.Find(&posts).Error
55 if err != nil {
56 return nil, fmt.Errorf("error retrieving chat posts: %w", err)
57 }
58 return posts, nil
59}
60func (m *DBModel) ListFeedPostsByType(feedType string, limit int, after int64) ([]FeedPost, error) {
61 if after == 0 {
62 after = time.Now().Add(48 * time.Hour).UnixMilli()
63 }
64 time := time.UnixMilli(after)
65 posts := []FeedPost{}
66 // exclude scumb.ag for now (so my dev streams don't show up)
67 err := m.DB.Where("type = ? AND created_at < ? AND repo_did != ?", feedType, time.UTC(), "did:plc:dkh4rwafdcda4ko7lewe43ml").
68 Order("created_at DESC").
69 Group("uri").
70 Limit(limit).
71 Find(&posts).Error
72 if err != nil {
73 return nil, fmt.Errorf("error retrieving feed posts: %w", err)
74 }
75 return posts, nil
76}
77
78func (m *DBModel) GetFeedPost(cid string) (*FeedPost, error) {
79 post := FeedPost{}
80 err := m.DB.Where("CID = ?", cid).First(&post).Error
81 if errors.Is(err, gorm.ErrRecordNotFound) {
82 return nil, nil
83 }
84 if err != nil {
85 return nil, fmt.Errorf("error retrieving feed post: %w", err)
86 }
87 return &post, nil
88}
89
90func (m *DBModel) GetReplies(repoDID string) ([]*bsky.FeedDefs_PostView, error) {
91 posts := []FeedPost{}
92 err := m.DB.
93 Preload("Repo").
94 Where("reply_root_repo_did = ? AND type = ?", repoDID, "reply").
95 Limit(100).
96 Order("created_at DESC").
97 Find(&posts).Error
98 if err != nil {
99 return nil, fmt.Errorf("error retrieving replies: %w", err)
100 }
101 bskyPosts := []*bsky.FeedDefs_PostView{}
102 for _, post := range posts {
103 bskyPost, err := post.ToBskyPostView()
104 if err != nil {
105 return nil, fmt.Errorf("error converting feed post to bsky post view: %w", err)
106 }
107 bskyPosts = append(bskyPosts, bskyPost)
108 }
109 return bskyPosts, nil
110}
111
112type StreamplaceFeedPostLivestream struct {
113 URL string `json:"url"`
114 Title string `json:"title"`
115}
116
117func (m *DBModel) GetLatestLivestream(repoDID string) (*bsky.FeedDefs_PostView, error) {
118 posts := []FeedPost{}
119 err := m.DB.
120 Preload("Repo").
121 Where("type = ?", "livestream").
122 Where("repo_did = ?", repoDID).
123 Limit(1).
124 Order("created_at DESC").
125 Find(&posts).Error
126 if err != nil {
127 return nil, fmt.Errorf("error retrieving livestream: %w", err)
128 }
129
130 if len(posts) == 0 {
131 return nil, nil
132 }
133
134 view, err := posts[0].ToBskyPostView()
135 if err != nil {
136 return nil, fmt.Errorf("error converting feed post to bsky post view: %w", err)
137 }
138
139 return view, nil
140}