···11+22+`atwork-cli`: basic demo tool for interacting with `at://work`
33+===============================================================
44+55+`at://work` (https://atwork.place) is a job board build using AT ([atproto](https://atproto.com)).
66+77+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).
88+99+1010+## Project Setup Tutorial
1111+1212+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.
1313+1414+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.
1515+1616+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:
1717+1818+```
1919+$ glot pull place.atwork.
2020+ 🟢 place.atwork.searchListings
2121+ 🟢 place.atwork.profile
2222+ 🟢 place.atwork.listing
2323+ 🟢 place.atwork.getListings
2424+ 🟢 place.atwork.getListing
2525+ 🟢 place.atwork.endorsementProof
2626+ 🟢 place.atwork.endorsement
2727+2828+```
2929+3030+Next we'll initialize the Go project, and try to generate types for these Lexicons:
3131+3232+```
3333+$ go mod init tangled.org/bnewbold.net/atwork-cli
3434+go: creating new go.mod: module tangled.org/bnewbold.net/atwork-cli
3535+3636+$ glot codegen lexicons/place/atwork --output-dir .
3737+ 🟠 lexicons/place/atwork/endorsement.json
3838+ [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
3939+ 🟢 lexicons/place/atwork/endorsementProof.json
4040+ 🟢 lexicons/place/atwork/getListing.json
4141+ 🟢 lexicons/place/atwork/getListings.json
4242+ 🟠 lexicons/place/atwork/listing.json
4343+ [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
4444+ 🟠 lexicons/place/atwork/profile.json
4545+ [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
4646+ 🟢 lexicons/place/atwork/searchListings.json
4747+error: some codegen failed
4848+```
4949+5050+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:
5151+5252+```
5353+$ glot pull app.bsky.richtext.facet community.lexicon.location.hthree com.atproto.repo.strongRef
5454+ 🟢 app.bsky.richtext.facet
5555+ 🟢 community.lexicon.location.hthree
5656+ 🟢 com.atproto.repo.strongRef
5757+```
5858+5959+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:
6060+6161+```
6262+$ glot codegen lexicons/place/atwork lexicons/community/lexicon --output-dir .
6363+ 🟢 lexicons/community/lexicon/location/hthree.json
6464+ 🟢 lexicons/place/atwork/endorsement.json
6565+ 🟢 lexicons/place/atwork/endorsementProof.json
6666+ 🟢 lexicons/place/atwork/getListing.json
6767+ 🟢 lexicons/place/atwork/getListings.json
6868+ 🟢 lexicons/place/atwork/listing.json
6969+ 🟢 lexicons/place/atwork/profile.json
7070+ 🟢 lexicons/place/atwork/searchListings.json
7171+```
7272+7373+Great, now we have generated types for all the Lexicons we use.
7474+7575+7676+## Basic API Request: Search
7777+7878+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.
7979+8080+```go
8181+// pseudo-code; see main.go for actual implementation
8282+8383+import (
8484+ "github.com/bluesky-social/indigo/atproto/atclient"
8585+ "tangled.org/bnewbold.net/atwork-cli/placeatwork"
8686+)
8787+8888+func main() {
8989+9090+ searchQuery := "intern"
9191+ apiServer := "https://atwork.place"
9292+9393+ client := atclient.NewAPIClient(apiServer)
9494+9595+ resp, err := placeatwork.SearchListings(ctx, client, searchQuery)
9696+ if err != nil {
9797+ return err
9898+ }
9999+100100+ for _, hit := range resp.Listings {
101101+ fmt.Prinln(hit.Value.Title)
102102+ }
103103+}
104104+```
105105+106106+The API client gets injected in to the generated API endpoint code (`placeatwork.SearchListings`), which converts the response JSON into the expected struct type.