A locally focused bluesky appview
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 161 lines 4.4 kB view raw
1package feed 2 3import ( 4 "bytes" 5 "log/slog" 6 "net/http" 7 "time" 8 9 "github.com/bluesky-social/indigo/api/bsky" 10 "github.com/bluesky-social/indigo/atproto/identity" 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 cid "github.com/ipfs/go-cid" 13 "github.com/labstack/echo/v4" 14 mh "github.com/multiformats/go-multihash" 15 "github.com/whyrusleeping/konbini/hydration" 16 "github.com/whyrusleeping/konbini/views" 17 "gorm.io/gorm" 18) 19 20// HandleGetFeedGenerator implements app.bsky.feed.getFeedGenerator 21func HandleGetFeedGenerator(c echo.Context, db *gorm.DB, hydrator *hydration.Hydrator, dir identity.Directory) error { 22 ctx := c.Request().Context() 23 24 // Parse parameters 25 feedURI := c.QueryParam("feed") 26 if feedURI == "" { 27 return c.JSON(http.StatusBadRequest, map[string]any{ 28 "error": "InvalidRequest", 29 "message": "feed parameter is required", 30 }) 31 } 32 33 nu, err := hydrator.NormalizeUri(ctx, feedURI) 34 if err != nil { 35 return err 36 } 37 feedURI = nu 38 39 viewer := getUserDID(c) 40 _ = viewer 41 42 // Extract feed generator DID and rkey from URI 43 did := extractDIDFromURI(feedURI) 44 rkey := extractRkeyFromURI(feedURI) 45 46 if did == "" || rkey == "" { 47 return c.JSON(http.StatusBadRequest, map[string]any{ 48 "error": "InvalidRequest", 49 "message": "invalid feed URI format", 50 }) 51 } 52 53 // Query feed generator from database 54 type feedGenRow struct { 55 ID uint 56 Did string 57 Raw []byte 58 AuthorDid string 59 Indexed time.Time 60 } 61 var feedGen feedGenRow 62 err = db.Raw(` 63 SELECT fg.id, fg.did, fg.raw, r.did as author_did, indexed 64 FROM feed_generators fg 65 JOIN repos r ON r.id = fg.author 66 WHERE r.did = ? AND fg.rkey = ? 67 `, did, rkey).Scan(&feedGen).Error 68 69 if err != nil || feedGen.ID == 0 { 70 // Track this missing feed generator for fetching 71 hydrator.AddMissingRecord(feedURI, true) 72 73 return c.JSON(http.StatusNotFound, map[string]any{ 74 "error": "NotFound", 75 "message": "feed generator not found", 76 }) 77 } 78 79 // Decode the feed generator record 80 var feedGenRecord bsky.FeedGenerator 81 if err := feedGenRecord.UnmarshalCBOR(bytes.NewReader(feedGen.Raw)); err != nil { 82 slog.Error("failed to decode feed generator record", "error", err) 83 return c.JSON(http.StatusInternalServerError, map[string]any{ 84 "error": "InternalError", 85 "message": "failed to decode feed generator record", 86 }) 87 } 88 89 // Compute CID from raw bytes 90 hash, err := mh.Sum(feedGen.Raw, mh.SHA2_256, -1) 91 if err != nil { 92 slog.Error("failed to hash record", "error", err) 93 return c.JSON(http.StatusInternalServerError, map[string]any{ 94 "error": "InternalError", 95 "message": "failed to compute CID", 96 }) 97 } 98 recordCid := cid.NewCidV1(cid.DagCBOR, hash).String() 99 100 // Hydrate the creator 101 creatorInfo, err := hydrator.HydrateActor(ctx, feedGen.AuthorDid) 102 if err != nil { 103 slog.Error("failed to hydrate creator", "error", err, "did", feedGen.AuthorDid) 104 return c.JSON(http.StatusInternalServerError, map[string]any{ 105 "error": "InternalError", 106 "message": "failed to hydrate creator", 107 }) 108 } 109 110 // Count likes for this feed generator 111 var likeCount int64 112 113 // Check if viewer has liked this feed generator 114 viewerLike := "" 115 116 // Validate the service DID (check if it's resolvable) 117 serviceDID, err := syntax.ParseDID(feedGenRecord.Did) 118 if err != nil { 119 slog.Error("invalid service DID in feed generator", "error", err, "did", feedGenRecord.Did) 120 return c.JSON(http.StatusInternalServerError, map[string]any{ 121 "error": "InternalError", 122 "message": "invalid service DID", 123 }) 124 } 125 126 // Try to resolve the service DID to check if it's online/valid 127 isOnline := true 128 isValid := true 129 serviceIdent, err := dir.LookupDID(ctx, serviceDID) 130 if err != nil { 131 slog.Warn("failed to resolve service DID", "error", err, "did", serviceDID) 132 isOnline = false 133 isValid = false 134 } else { 135 // Check if service has an endpoint 136 serviceEndpoint := serviceIdent.PDSEndpoint() 137 if serviceEndpoint == "" { 138 slog.Warn("service has no PDS endpoint", "did", serviceDID) 139 isValid = false 140 } 141 } 142 143 // Build the generator view 144 generatorView := views.GeneratorView( 145 feedURI, 146 recordCid, 147 &feedGenRecord, 148 creatorInfo, 149 likeCount, 150 viewerLike, 151 feedGen.Indexed.Format(time.RFC3339), 152 ) 153 154 output := &bsky.FeedGetFeedGenerator_Output{ 155 View: generatorView, 156 IsOnline: isOnline, 157 IsValid: isValid, 158 } 159 160 return c.JSON(http.StatusOK, output) 161}