An implementation of the ATProto statusphere example app but in Go

Readme added with brief explanation of how to run etc

+36 -113
+1
.gitignore
··· 1 1 .env 2 2 database.db 3 + statuspherego
-113
cmd/main.go
··· 1 - package main 2 - 3 - import ( 4 - "context" 5 - "errors" 6 - "log" 7 - "log/slog" 8 - "net/http" 9 - "os" 10 - "os/signal" 11 - "path" 12 - "syscall" 13 - "time" 14 - 15 - "github.com/avast/retry-go/v4" 16 - "github.com/joho/godotenv" 17 - "github.com/willdot/statusphere-go" 18 - "github.com/willdot/statusphere-go/database" 19 - "github.com/willdot/statusphere-go/oauth" 20 - ) 21 - 22 - const ( 23 - defaultServerAddr = "wss://jetstream.atproto.tools/subscribe" 24 - httpClientTimeoutDuration = time.Second * 5 25 - transportIdleConnTimeoutDuration = time.Second * 90 26 - ) 27 - 28 - func main() { 29 - err := godotenv.Load(".env") 30 - if err != nil { 31 - if !os.IsNotExist(err) { 32 - log.Fatal("Error loading .env file") 33 - } 34 - } 35 - 36 - host := os.Getenv("HOST") 37 - if host == "" { 38 - slog.Error("missing HOST env variable") 39 - return 40 - } 41 - 42 - dbMountPath := os.Getenv("DATABASE_MOUNT_PATH") 43 - if dbMountPath == "" { 44 - slog.Error("DATABASE_MOUNT_PATH env not set") 45 - return 46 - } 47 - 48 - dbFilename := path.Join(dbMountPath, "database.db") 49 - db, err := database.New(dbFilename) 50 - if err != nil { 51 - slog.Error("create new database", "error", err) 52 - return 53 - } 54 - defer db.Close() 55 - 56 - httpClient := &http.Client{ 57 - Timeout: httpClientTimeoutDuration, 58 - Transport: &http.Transport{ 59 - IdleConnTimeout: transportIdleConnTimeoutDuration, 60 - }, 61 - } 62 - 63 - oauthService, err := oauth.NewService(db, host, httpClient) 64 - if err != nil { 65 - slog.Error("creating new oauth service", "error", err) 66 - return 67 - } 68 - 69 - server, err := statusphere.NewServer(host, 8080, db, oauthService, httpClient) 70 - if err != nil { 71 - slog.Error("create new server", "error", err) 72 - return 73 - } 74 - 75 - signals := make(chan os.Signal, 1) 76 - signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) 77 - 78 - ctx, cancel := context.WithCancel(context.Background()) 79 - defer cancel() 80 - 81 - go func() { 82 - <-signals 83 - cancel() 84 - _ = server.Stop(context.Background()) 85 - }() 86 - 87 - go consumeLoop(ctx, db) 88 - 89 - server.Run() 90 - } 91 - 92 - func consumeLoop(ctx context.Context, db *database.DB) { 93 - jsServerAddr := os.Getenv("JS_SERVER_ADDR") 94 - if jsServerAddr == "" { 95 - jsServerAddr = defaultServerAddr 96 - } 97 - 98 - consumer := statusphere.NewConsumer(jsServerAddr, slog.Default(), db) 99 - 100 - err := retry.Do(func() error { 101 - err := consumer.Consume(ctx) 102 - if err != nil { 103 - if errors.Is(err, context.Canceled) { 104 - return nil 105 - } 106 - slog.Error("consume loop", "error", err) 107 - return err 108 - } 109 - return nil 110 - }, retry.UntilSucceeded()) // retry indefinitly until context canceled 111 - slog.Error(err.Error()) 112 - slog.Warn("exiting consume loop") 113 - }
+4
example.env
··· 1 + PRIVATEJWKS="" 2 + SESSION_KEY="" 3 + HOST="" 4 + DATABASE_MOUNT_PATH="./"
main

This is a binary file and will not be displayed.

+31
readme.md
··· 1 + ## Statusphere Go 2 + 3 + This is an implementation of the example [ATProto application Statusphere](https://atproto.com/guides/applications) but in Go. 4 + 5 + It makes use of an ATProto OAuth [library](https://github.com/haileyok/atproto-oauth-golang). Shout out to [Hailey](https://bsky.app/profile/hailey.at) for implementing a Go OAuth library! 6 + 7 + TLDR of what this app does if you haven't read the [ATProto application Statusphere](https://atproto.com/guides/applications) guide. 8 + 9 + 1: Allows you to log into Bluesky using OAuth. 10 + 2: Allows you to post a status (an emoji) which creates a record in your PDS. 11 + 3: Shows other users status' when they do the same, even if they are using a different app that this. As long as they are using the statusphere lexicon and NSID then this application will consume those records using Jetstream (firehose) and store them in the local database. 12 + 13 + ### Running the app 14 + 15 + A few environment variables are required to run the app. Use the `example.env` file as a template and store your environment variables in a `.env` file. 16 + 17 + * PRIVATEJWKS: This is a private JWKS. You can generate one using the same Go OAuth [library](https://github.com/haileyok/atproto-oauth-golang). 18 + * SESSION_KEY: This can be anything as it's what's used to encrypt session data sent to/from the client. 19 + * HOST: This needs to be a http URL where the server is running. For local dev I suggest using something like [ngrok](https://ngrok.com) to run you app locally and make it accessable externally. This is important for OAuth as the callback URL configured needs to be a publically accessable. 20 + * DATABASE_MOUNT_PATH: This is where you wish the mysql database to be located. 21 + 22 + Run the command `go build -o statuspherego ./cmd/statuspherego/main.go` which will build the app and then `./statuspherego` to run it. 23 + 24 + If running locally I would then run `ngrok http http://localhost:8080` to get your publically accessable URL. 25 + 26 + Go to the home page of the app, log in via OAuth and post your status! 27 + 28 + ### Contributing 29 + This is just a demo app and was mainly for me to learn how to build applications in the ATmosphere and I thought what better way than to use the example statusphere guide but do it in Go. 30 + 31 + That being said if you wish to contribute then feel free to fork and PR any improvements you think there can be.