`atwork-cli`: basic demo tool for interacting with `at://work` =============================================================== `at://work` (https://atwork.place) is a job board build using AT ([atproto](https://atproto.com)). This little CLI project is really a demo/tutorial for AT developer tools, including [indigo](https://github.com/bluesky-social/indigo) (the Go SDK) and [glot](https://tangled.org/bnewbold.net/cobalt/tree/main/cmd/glot) (lexicon management and codegen for Go). ## Project Setup Tutorial In the context of this little CLI project, we are going to work with existing Lexicons, instead of declaring our own. These Lexicons span several namespaces, and not all of them have maintained public SDKs for Go. We'll use [gloat](https://tangled.org/bnewbold.net/cobalt/tree/main/cmd/glot) to work with published lexicons, though note a bunch of glot functionality is expected to get merged in to the `goat` command soon. Starting in an empty project directory, and with `glot` installed, we'll start by pulling down the `place.atwork.*` Lexicons. The trailing `.` in this command tells it to fetch all schemas under the Lexicon group: ``` $ glot pull place.atwork. 🟢 place.atwork.searchListings 🟢 place.atwork.profile 🟢 place.atwork.listing 🟢 place.atwork.getListings 🟢 place.atwork.getListing 🟢 place.atwork.endorsementProof 🟢 place.atwork.endorsement ``` Next we'll initialize the Go project, and try to generate types for these Lexicons: ``` $ go mod init tangled.org/bnewbold.net/atwork-cli go: creating new go.mod: module tangled.org/bnewbold.net/atwork-cli $ glot codegen lexicons/place/atwork --output-dir . 🟠 lexicons/place/atwork/endorsement.json [failed]: failed to format codegen output (lexicons/place/atwork/endorsement.json): could not resolve lexicon reference (com.atproto.repo.strongRef): schema not found in catalog: com.atproto.repo.strongRef#main 🟢 lexicons/place/atwork/endorsementProof.json 🟢 lexicons/place/atwork/getListing.json 🟢 lexicons/place/atwork/getListings.json 🟠 lexicons/place/atwork/listing.json [failed]: failed to format codegen output (lexicons/place/atwork/listing.json): could not resolve lexicon reference (app.bsky.richtext.facet): schema not found in catalog: app.bsky.richtext.facet#main 🟠 lexicons/place/atwork/profile.json [failed]: failed to format codegen output (lexicons/place/atwork/profile.json): could not resolve lexicon reference (app.bsky.richtext.facet): schema not found in catalog: app.bsky.richtext.facet#main 🟢 lexicons/place/atwork/searchListings.json error: some codegen failed ``` This hits some errors because the Lexicons reference other namespaces (`lexicons.community.*`, `app.bsky.richtext.*`, `com.atproto.repo.strongRef`). In the future these might get pulled in automatically, but for now let's pull them in to our namespace: ``` $ glot pull app.bsky.richtext.facet community.lexicon.location.hthree com.atproto.repo.strongRef 🟢 app.bsky.richtext.facet 🟢 community.lexicon.location.hthree 🟢 com.atproto.repo.strongRef ``` The `app.bsky.*` namespace has a published Go package, and the `com.atproto.*` namespace is protocol-level, but `lexicons.community.*` doesn't have package, so we'll generate code for that Lexicon as well: ``` $ glot codegen lexicons/place/atwork lexicons/community/lexicon --output-dir . 🟢 lexicons/community/lexicon/location/hthree.json 🟢 lexicons/place/atwork/endorsement.json 🟢 lexicons/place/atwork/endorsementProof.json 🟢 lexicons/place/atwork/getListing.json 🟢 lexicons/place/atwork/getListings.json 🟢 lexicons/place/atwork/listing.json 🟢 lexicons/place/atwork/profile.json 🟢 lexicons/place/atwork/searchListings.json ``` Great, now we have generated types for all the Lexicons we use. ## Basic API Request: Search The `place.atwork.*` lexicons include both record types and API endpoints. Let's try calling an unauthenticated public API endpoint using the indigo API client. ```go // pseudo-code; see main.go for actual implementation import ( "github.com/bluesky-social/indigo/atproto/atclient" "tangled.org/bnewbold.net/atwork-cli/placeatwork" ) func main() { searchQuery := "intern" apiServer := "https://atwork.place" client := atclient.NewAPIClient(apiServer) resp, err := placeatwork.SearchListings(ctx, client, searchQuery) if err != nil { return err } for _, hit := range resp.Listings { fmt.Prinln(hit.Value.Title) } } ``` The API client gets injected in to the generated API endpoint code (`placeatwork.SearchListings`), which converts the response JSON into the expected struct type.