Something went wrong. Try again.
lexicon devex tutorial
Something went wrong. Try again.
atwork-cli main.go
6.0 kB · 233 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234package main
import ( "context" "encoding/json" "fmt" "net/http" "os"
_ "github.com/joho/godotenv/autoload"
"tangled.org/bnewbold.net/atwork-cli/placeatwork"
"github.com/bluesky-social/indigo/api/agnostic" comatproto "github.com/bluesky-social/indigo/api/atproto" appbsky "github.com/bluesky-social/indigo/api/bsky" "github.com/bluesky-social/indigo/atproto/atclient" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/urfave/cli/v3")
func main() { app := cli.Command{ Name: "atwork-cli", Usage: "example CLI project for at://work lexicons", Commands: []*cli.Command{ &cli.Command{ Name: "search", Usage: "queries public API for listings", ArgsUsage: "<query>", Flags: []cli.Flag{ &cli.StringFlag{ Name: "host", Value: "https://atwork.place", }, }, Action: runSearch, }, &cli.Command{ Name: "import-bsky-profile", Usage: "creates a new atwork profile using Bluesky profile data", Flags: []cli.Flag{ &cli.StringFlag{ Name: "username", Sources: cli.EnvVars("ATP_USERNAME"), }, &cli.StringFlag{ Name: "password", Sources: cli.EnvVars("ATP_PASSWORD"), }, }, Action: runImportProfile, }, &cli.Command{ Name: "upload-resume", Usage: "uploads resume PDF and adds to atwork profile", ArgsUsage: "<pdf-path>", Flags: []cli.Flag{ &cli.StringFlag{ Name: "username", Sources: cli.EnvVars("ATP_USERNAME"), }, &cli.StringFlag{ Name: "password", Sources: cli.EnvVars("ATP_PASSWORD"), }, }, Action: runUploadResume, }, }, }
if err := app.Run(context.Background(), os.Args); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) }}
func runSearch(ctx context.Context, cmd *cli.Command) error { if !cmd.Args().Present() { return fmt.Errorf("requires a search query") } searchQuery := cmd.Args().First()
c := atclient.NewAPIClient(cmd.String("host"))
resp, err := placeatwork.SearchListings(ctx, c, searchQuery) if err != nil { return err }
for _, hit := range resp.Listings { if hit.Value == nil { continue } listing := *hit.Value aturi, err := syntax.ParseATURI(hit.Uri) if err != nil { continue } fmt.Printf("%s: %s\n", aturi.Authority(), listing.Title) if listing.ApplyLink != nil { fmt.Printf("\tApply: %s\n", *listing.ApplyLink) } }
return nil}
func runImportProfile(ctx context.Context, cmd *cli.Command) error {
if !cmd.IsSet("username") || !cmd.IsSet("password") { return fmt.Errorf("authentication required (username and password)") }
username, err := syntax.ParseAtIdentifier(cmd.String("username")) if err != nil { return fmt.Errorf("invalid username: %w", err) } dir := identity.DefaultDirectory()
c, err := atclient.LoginWithPassword(ctx, dir, *username, cmd.String("password"), "", nil) if err != nil { return err }
// fetch existing bsky profile resp, err := agnostic.RepoGetRecord(ctx, c, "", "app.bsky.actor.profile", c.AccountDID.String(), "self") if err != nil { return fmt.Errorf("failed fetching bsky profile record: %s", err) }
var bskyProfile appbsky.ActorProfile if err := json.Unmarshal(*resp.Value, &bskyProfile); err != nil { return err }
// create new atwork profile record atworkProfile := placeatwork.Profile{ LexiconTypeID: "place.atwork.profile", Avatar: bskyProfile.Avatar, Banner: bskyProfile.Banner, Description: bskyProfile.Description, DisplayName: bskyProfile.DisplayName, }
// create profile record (fails if one already exists) if err := c.Post(ctx, syntax.NSID("com.atproto.repo.createRecord"), map[string]any{ "repo": c.AccountDID, "collection": atworkProfile.LexiconTypeID, "rkey": "self", "record": atworkProfile, }, nil); err != nil { return err }
return nil}
func runUploadResume(ctx context.Context, cmd *cli.Command) error {
if !cmd.Args().Present() { return fmt.Errorf("requires resume PDF path as argument") } resumePath := cmd.Args().First()
if !cmd.IsSet("username") || !cmd.IsSet("password") { return fmt.Errorf("authentication required (username and password)") }
username, err := syntax.ParseAtIdentifier(cmd.String("username")) if err != nil { return fmt.Errorf("invalid username: %w", err) } dir := identity.DefaultDirectory()
c, err := atclient.LoginWithPassword(ctx, dir, *username, cmd.String("password"), "", nil) if err != nil { return err }
// fetch existing at://work profile getResp, err := agnostic.RepoGetRecord(ctx, c, "", "place.atwork.profile", c.AccountDID.String(), "self") if err != nil { return fmt.Errorf("failed fetching profile record: %s", err) }
var atworkProfile placeatwork.Profile if err := json.Unmarshal(*getResp.Value, &atworkProfile); err != nil { return err }
// upload resume PDF as blob resumeFile, err := os.Open(resumePath) if err != nil { return err } defer resumeFile.Close()
// update record req := atclient.NewAPIRequest(http.MethodPost, syntax.NSID("com.atproto.repo.uploadBlob"), resumeFile) req.Headers.Set("Accept", "application/json") req.Headers.Set("Content-Type", "application/pdf")
resp, err := c.Do(ctx, req) if err != nil { return err } defer resp.Body.Close()
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { return fmt.Errorf("blob upload failed") }
var blobResp comatproto.RepoUploadBlob_Output if err := json.NewDecoder(resp.Body).Decode(&blobResp); err != nil { return fmt.Errorf("failed decoding JSON response body: %w", err) }
// update profile with blob ref atworkProfile.Resume = blobResp.Blob if err := c.Post(ctx, syntax.NSID("com.atproto.repo.putRecord"), map[string]any{ "repo": c.AccountDID, "collection": atworkProfile.LexiconTypeID, "rkey": "self", "record": atworkProfile, }, nil); err != nil { return err }
return nil}