Live video on the AT Protocol
1package model
2
3import (
4 "context"
5 "errors"
6 "fmt"
7
8 "github.com/bluesky-social/indigo/api/bsky"
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 lexutil "github.com/bluesky-social/indigo/lex/util"
11 "gorm.io/gorm"
12 "gorm.io/gorm/clause"
13 "stream.place/streamplace/pkg/spid"
14)
15
16type BskyProfile struct {
17 URI string `json:"uri" gorm:"primaryKey;column:uri"`
18 CID string `json:"cid" gorm:"column:cid"`
19 RepoDID string `json:"repoDID" gorm:"column:repo_did"`
20 Repo *Repo `json:"repo,omitempty" gorm:"foreignKey:DID;references:RepoDID"`
21 Record *[]byte `json:"record" gorm:"column:record"`
22 WasStreamplace bool `json:"wasStreamplace" gorm:"primaryKey;column:was_streamplace"`
23}
24
25func (m *DBModel) UpsertBskyProfile(ctx context.Context, aturi syntax.ATURI, recBs []byte, wasStreamplace bool) error {
26 cid, err := spid.GetCIDFromBytes(recBs)
27 if err != nil {
28 return fmt.Errorf("failed to get cid: %w", err)
29 }
30 dbProfile := &BskyProfile{
31 URI: aturi.String(),
32 CID: cid.String(),
33 RepoDID: aturi.Authority().String(),
34 Record: &recBs,
35 WasStreamplace: wasStreamplace,
36 }
37
38 // Use GORM's OnConflict to handle unique/primary conflicts
39 // If a conflict (same PK), then update the relevant fields
40 return m.DB.
41 Clauses(
42 // Conflict columns: uri, was_streamplace (matching primaryKey in struct)
43 clause.OnConflict{
44 Columns: []clause.Column{{Name: "uri"}, {Name: "was_streamplace"}},
45 DoUpdates: clause.AssignmentColumns([]string{"cid", "repo_did", "record"}),
46 },
47 ).
48 Create(dbProfile).Error
49}
50
51func (m *DBModel) GetBskyProfile(ctx context.Context, did string, wasStreamplace bool) (*bsky.ActorProfile, error) {
52 var profile BskyProfile
53 err := m.DB.Where("uri = ? AND was_streamplace = ?", fmt.Sprintf("at://%s/app.bsky.actor.profile/self", did), wasStreamplace).First(&profile).Error
54 if errors.Is(err, gorm.ErrRecordNotFound) {
55 return nil, nil
56 }
57 if err != nil {
58 return nil, err
59 }
60 rec, err := lexutil.CborDecodeValue(*profile.Record)
61 if err != nil {
62 return nil, fmt.Errorf("failed to decode profile record: %w", err)
63 }
64 bskyProfile, ok := rec.(*bsky.ActorProfile)
65 if !ok {
66 return nil, fmt.Errorf("invalid profile record")
67 }
68 return bskyProfile, nil
69}