···11-package main
11+package feed
2233import (
44 "context"
···2222 logger *slog.Logger
2323}
24242525-func newJetstreamConsumer(jsAddr string, logger *slog.Logger, handler *Handler) *JetstreamConsumer {
2525+// NewJetstreamConsumer configures a new jetstream consumer. To run or start you should call the Consume function
2626+func NewJetstreamConsumer(jsAddr string, logger *slog.Logger, handler *Handler) *JetstreamConsumer {
2627 cfg := client.DefaultClientConfig()
2728 if jsAddr != "" {
2829 cfg.WebsocketURL = jsAddr
···6263// Handler is responsible for handling a message consumed from Jetstream
6364type Handler struct {
6465 store PostStore
6666+}
6767+6868+// NewFeedHandler returns a new handler
6969+func NewFeedHandler(store PostStore) *Handler {
7070+ return &Handler{store: store}
6571}
66726773// HandleEvent will handle an event based on the event's commit operation
database.db
This is a binary file and will not be displayed.
+12-5
database.go
···11-package main
11+package feed
2233import (
44 "database/sql"
···1515 db *sql.DB
1616}
17171818-func newDatabase(dbPath string) (*Database, error) {
1818+// NewDatabase will open a new database. It will ping the database to ensure it is available and error if not
1919+func NewDatabase(dbPath string) (*Database, error) {
1920 if dbPath != ":memory:" {
2021 err := createDbFile(dbPath)
2122 if err != nil {
···4142 return &Database{db: db}, nil
4243}
43444444-func (d *Database) close() {
4545+// Close will cleanly stop the database connection
4646+func (d *Database) Close() {
4547 err := d.db.Close()
4648 if err != nil {
4749 slog.Error("failed to close db", "error", err)
···5759 if err != nil {
5860 return fmt.Errorf("create db file : %w", err)
5961 }
6060- f.Close()
6262+ err = f.Close()
6363+ if err != nil {
6464+ return fmt.Errorf("failed to close DB file: %w", err)
6565+ }
6166 return nil
6267}
6368···103108 if err != nil {
104109 return nil, fmt.Errorf("run query to get feed posts: %w", err)
105110 }
106106- defer rows.Close()
111111+ defer func() {
112112+ _ = rows.Close()
113113+ }()
107114108115 posts := make([]Post, 0)
109116 for rows.Next() {
-72
feedgenerator.go
···11-package main
22-33-import (
44- "context"
55- "fmt"
66- "log/slog"
77- "strconv"
88-)
99-1010-// Post describes a Bluesky post
1111-type Post struct {
1212- ID int
1313- RKey string
1414- PostURI string
1515- UserDID string
1616- CreatedAt int64
1717-}
1818-1919-// PostStore defines the interactions with a store
2020-type PostStore interface {
2121- GetFeedPosts(cursor, limit int) ([]Post, error)
2222- CreatePost(post Post) error
2323-}
2424-2525-// FeedGenerator is responsible for generating a feed
2626-type FeedGenerator struct {
2727- store PostStore
2828-}
2929-3030-func newFeedGenerator(store PostStore) *FeedGenerator {
3131- return &FeedGenerator{
3232- store: store,
3333- }
3434-}
3535-3636-// GetFeed will fetch a feed and build up a response that can be returned
3737-func (f *FeedGenerator) GetFeed(ctx context.Context, feed, cursor string, limit int) (FeedSkeletonReponse, error) {
3838- resp := FeedSkeletonReponse{
3939- Feed: make([]FeedSkeletonPost, 0),
4040- }
4141-4242- cursorInt, err := strconv.Atoi(cursor)
4343- if err != nil && cursor != "" {
4444- slog.Error("convert cursor to int", "error", err, "cursor value", cursor)
4545- }
4646- if cursorInt == 0 {
4747- // if no cursor provided use a date waaaaay in the future to start the less than query
4848- cursorInt = 9999999999999
4949- }
5050-5151- posts, err := f.store.GetFeedPosts(cursorInt, limit)
5252- if err != nil {
5353- return resp, fmt.Errorf("get feed from DB: %w", err)
5454- }
5555-5656- usersFeed := make([]FeedSkeletonPost, 0, len(posts))
5757- for _, post := range posts {
5858- usersFeed = append(usersFeed, FeedSkeletonPost{
5959- Post: post.PostURI,
6060- })
6161- }
6262-6363- resp.Feed = usersFeed
6464-6565- // only set the return cursor if there was at least 1 record returned and that the len of records
6666- // being returned is the same as the limit
6767- if len(posts) > 0 && len(posts) == limit {
6868- lastPost := posts[len(posts)-1]
6969- resp.Cursor = fmt.Sprintf("%d", lastPost.CreatedAt)
7070- }
7171- return resp, nil
7272-}
+41-11
handlers.go
···11-package main
11+package feed
2233import (
44+ "context"
45 "encoding/json"
56 "fmt"
67 "log/slog"
···66676768 cursor := params.Get("cursor")
68696969- resp, err := s.feeder.GetFeed(r.Context(), feed, cursor, limit)
7070+ resp, err := s.getFeed(r.Context(), feed, cursor, limit)
7071 if err != nil {
7172 slog.Error("get feed", "error", err, "feed", feed)
7273 http.Error(w, "error getting feed", http.StatusInternalServerError)
···103104 DID: fmt.Sprintf("did:web:%s", s.feedHost),
104105 Feeds: []Feed{
105106 {
106106- URI: fmt.Sprintf("at://%s/app.bsky.feed.generator/%s", s.feedDidBase, s.feedName),
107107+ URI: fmt.Sprintf("at://%s/app.bsky.feed.generator/%s", s.feedHost, s.feedName),
107108 },
108109 },
109110 }
···167168 return limit, nil
168169}
169170170170-func checkUserAuth(r *http.Request) (string, error) {
171171- usersDID, err := getRequestUserDID(r)
172172- if err != nil {
173173- return "", fmt.Errorf("getting users did from request: %w", err)
174174- }
175175- return usersDID, nil
176176-}
177177-178171func getRequestUserDID(r *http.Request) (string, error) {
179172 headerValues := r.Header["Authorization"]
180173···215208216209 return string(syntax.DID(issVal)), nil
217210}
211211+212212+func (s *Server) getFeed(ctx context.Context, feed, cursor string, limit int) (FeedSkeletonReponse, error) {
213213+ resp := FeedSkeletonReponse{
214214+ Feed: make([]FeedSkeletonPost, 0),
215215+ }
216216+217217+ cursorInt, err := strconv.Atoi(cursor)
218218+ if err != nil && cursor != "" {
219219+ slog.Error("convert cursor to int", "error", err, "cursor value", cursor)
220220+ }
221221+ if cursorInt == 0 {
222222+ // if no cursor provided use a date waaaaay in the future to start the less than query
223223+ cursorInt = 9999999999999
224224+ }
225225+226226+ posts, err := s.postStore.GetFeedPosts(cursorInt, limit)
227227+ if err != nil {
228228+ return resp, fmt.Errorf("get feed from DB: %w", err)
229229+ }
230230+231231+ usersFeed := make([]FeedSkeletonPost, 0, len(posts))
232232+ for _, post := range posts {
233233+ usersFeed = append(usersFeed, FeedSkeletonPost{
234234+ Post: post.PostURI,
235235+ })
236236+ }
237237+238238+ resp.Feed = usersFeed
239239+240240+ // only set the return cursor if there was at least 1 record returned and that the len of records
241241+ // being returned is the same as the limit
242242+ if len(posts) > 0 && len(posts) == limit {
243243+ lastPost := posts[len(posts)-1]
244244+ resp.Cursor = fmt.Sprintf("%d", lastPost.CreatedAt)
245245+ }
246246+ return resp, nil
247247+}