···1-package main
23import (
4 "context"
···22 logger *slog.Logger
23}
2425-func newJetstreamConsumer(jsAddr string, logger *slog.Logger, handler *Handler) *JetstreamConsumer {
026 cfg := client.DefaultClientConfig()
27 if jsAddr != "" {
28 cfg.WebsocketURL = jsAddr
···62// Handler is responsible for handling a message consumed from Jetstream
63type Handler struct {
64 store PostStore
0000065}
6667// HandleEvent will handle an event based on the event's commit operation
···1+package feed
23import (
4 "context"
···22 logger *slog.Logger
23}
2425+// NewJetstreamConsumer configures a new jetstream consumer. To run or start you should call the Consume function
26+func NewJetstreamConsumer(jsAddr string, logger *slog.Logger, handler *Handler) *JetstreamConsumer {
27 cfg := client.DefaultClientConfig()
28 if jsAddr != "" {
29 cfg.WebsocketURL = jsAddr
···63// Handler is responsible for handling a message consumed from Jetstream
64type Handler struct {
65 store PostStore
66+}
67+68+// NewFeedHandler returns a new handler
69+func NewFeedHandler(store PostStore) *Handler {
70+ return &Handler{store: store}
71}
7273// 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
···1-package main
23import (
4 "database/sql"
···15 db *sql.DB
16}
1718-func newDatabase(dbPath string) (*Database, error) {
019 if dbPath != ":memory:" {
20 err := createDbFile(dbPath)
21 if err != nil {
···41 return &Database{db: db}, nil
42}
4344-func (d *Database) close() {
045 err := d.db.Close()
46 if err != nil {
47 slog.Error("failed to close db", "error", err)
···57 if err != nil {
58 return fmt.Errorf("create db file : %w", err)
59 }
60- f.Close()
00061 return nil
62}
63···103 if err != nil {
104 return nil, fmt.Errorf("run query to get feed posts: %w", err)
105 }
106- defer rows.Close()
00107108 posts := make([]Post, 0)
109 for rows.Next() {
···1+package feed
23import (
4 "database/sql"
···15 db *sql.DB
16}
1718+// NewDatabase will open a new database. It will ping the database to ensure it is available and error if not
19+func NewDatabase(dbPath string) (*Database, error) {
20 if dbPath != ":memory:" {
21 err := createDbFile(dbPath)
22 if err != nil {
···42 return &Database{db: db}, nil
43}
4445+// Close will cleanly stop the database connection
46+func (d *Database) Close() {
47 err := d.db.Close()
48 if err != nil {
49 slog.Error("failed to close db", "error", err)
···59 if err != nil {
60 return fmt.Errorf("create db file : %w", err)
61 }
62+ err = f.Close()
63+ if err != nil {
64+ return fmt.Errorf("failed to close DB file: %w", err)
65+ }
66 return nil
67}
68···108 if err != nil {
109 return nil, fmt.Errorf("run query to get feed posts: %w", err)
110 }
111+ defer func() {
112+ _ = rows.Close()
113+ }()
114115 posts := make([]Post, 0)
116 for rows.Next() {
-72
feedgenerator.go
···1-package main
2-3-import (
4- "context"
5- "fmt"
6- "log/slog"
7- "strconv"
8-)
9-10-// Post describes a Bluesky post
11-type Post struct {
12- ID int
13- RKey string
14- PostURI string
15- UserDID string
16- CreatedAt int64
17-}
18-19-// PostStore defines the interactions with a store
20-type PostStore interface {
21- GetFeedPosts(cursor, limit int) ([]Post, error)
22- CreatePost(post Post) error
23-}
24-25-// FeedGenerator is responsible for generating a feed
26-type FeedGenerator struct {
27- store PostStore
28-}
29-30-func newFeedGenerator(store PostStore) *FeedGenerator {
31- return &FeedGenerator{
32- store: store,
33- }
34-}
35-36-// GetFeed will fetch a feed and build up a response that can be returned
37-func (f *FeedGenerator) GetFeed(ctx context.Context, feed, cursor string, limit int) (FeedSkeletonReponse, error) {
38- resp := FeedSkeletonReponse{
39- Feed: make([]FeedSkeletonPost, 0),
40- }
41-42- cursorInt, err := strconv.Atoi(cursor)
43- if err != nil && cursor != "" {
44- slog.Error("convert cursor to int", "error", err, "cursor value", cursor)
45- }
46- if cursorInt == 0 {
47- // if no cursor provided use a date waaaaay in the future to start the less than query
48- cursorInt = 9999999999999
49- }
50-51- posts, err := f.store.GetFeedPosts(cursorInt, limit)
52- if err != nil {
53- return resp, fmt.Errorf("get feed from DB: %w", err)
54- }
55-56- usersFeed := make([]FeedSkeletonPost, 0, len(posts))
57- for _, post := range posts {
58- usersFeed = append(usersFeed, FeedSkeletonPost{
59- Post: post.PostURI,
60- })
61- }
62-63- resp.Feed = usersFeed
64-65- // only set the return cursor if there was at least 1 record returned and that the len of records
66- // being returned is the same as the limit
67- if len(posts) > 0 && len(posts) == limit {
68- lastPost := posts[len(posts)-1]
69- resp.Cursor = fmt.Sprintf("%d", lastPost.CreatedAt)
70- }
71- return resp, nil
72-}
···1-package main
23import (
4 "context"
···8 "net/http"
9)
1011-// Feeder describes building up a feed
12-type Feeder interface {
13- GetFeed(ctx context.Context, feed, cursor string, limit int) (FeedSkeletonReponse, error)
000000000014}
1516// Server is the feed server that will be called when a user requests to view a feed
17type Server struct {
18- httpsrv *http.Server
19- feeder Feeder
20- feedHost string
21- feedDidBase string
22- feedName string
23}
2425// NewServer builds a server - call the Run function to start the server
26-func NewServer(port int, feedHost, feedDidBase, feedName string, feeder Feeder) (*Server, error) {
27 srv := &Server{
28- feedHost: feedHost,
29- feedDidBase: feedDidBase,
30- feedName: feedName,
31- feeder: feeder,
32 }
3334 mux := http.NewServeMux()
···1+package feed
23import (
4 "context"
···8 "net/http"
9)
1011+// Post describes a Bluesky post
12+type Post struct {
13+ ID int
14+ RKey string
15+ PostURI string
16+ UserDID string
17+ CreatedAt int64
18+}
19+20+// PostStore defines the interactions with a store
21+type PostStore interface {
22+ GetFeedPosts(cursor, limit int) ([]Post, error)
23+ CreatePost(post Post) error
24}
2526// Server is the feed server that will be called when a user requests to view a feed
27type Server struct {
28+ httpsrv *http.Server
29+ postStore PostStore
30+ feedHost string
31+ feedName string
032}
3334// NewServer builds a server - call the Run function to start the server
35+func NewServer(port int, feedHost, feedName string, postStore PostStore) (*Server, error) {
36 srv := &Server{
37+ feedHost: feedHost,
38+ feedName: feedName,
39+ postStore: postStore,
040 }
4142 mux := http.NewServeMux()