dev vouch dev on at. thats about it atvouch.dev
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

create vouch

Luna ee927eb5 977e05f3

+79 -6
+79 -6
cli/main.go
··· 14 14 "os/exec" 15 15 "runtime" 16 16 "strings" 17 + "time" 17 18 18 19 "github.com/bluesky-social/indigo/atproto/auth/oauth" 20 + "github.com/bluesky-social/indigo/atproto/syntax" 19 21 "github.com/spf13/cobra" 20 22 ) 21 23 ··· 43 45 }, 44 46 } 45 47 46 - rootCmd.AddCommand(loginCmd, meCmd) 48 + createCmd := &cobra.Command{ 49 + Use: "create <handle>", 50 + Short: "Vouch for a user by their handle", 51 + Args: cobra.ExactArgs(1), 52 + RunE: func(cmd *cobra.Command, args []string) error { 53 + return create(cmd.Context(), args[0]) 54 + }, 55 + } 56 + 57 + rootCmd.AddCommand(loginCmd, meCmd, createCmd) 47 58 48 59 if err := rootCmd.ExecuteContext(context.Background()); err != nil { 49 60 os.Exit(1) ··· 55 66 } 56 67 57 68 func newOAuthClient(store *Store, callbackURL string) *oauth.ClientApp { 58 - config := oauth.NewLocalhostConfig(callbackURL, []string{"atproto"}) 69 + config := oauth.NewLocalhostConfig(callbackURL, []string{ 70 + "atproto", 71 + "repo:dev.atvouch.graph.vouch", 72 + }) 59 73 return oauth.NewClientApp(&config, store) 60 74 } 61 75 ··· 111 125 return nil 112 126 } 113 127 114 - func me(ctx context.Context) error { 128 + func resumeSession(ctx context.Context) (*oauth.ClientSession, error) { 115 129 store, err := newStore() 116 130 if err != nil { 117 - return err 131 + return nil, err 118 132 } 119 133 120 134 active, err := store.GetActive() 121 135 if err != nil { 122 - return err 136 + return nil, err 123 137 } 124 138 125 139 // We need a callback URL to construct the client config, but we won't ··· 129 143 130 144 session, err := oauthClient.ResumeSession(ctx, active.DID, active.SessionID) 131 145 if err != nil { 132 - return fmt.Errorf("resuming session: %w", err) 146 + return nil, fmt.Errorf("resuming session: %w", err) 147 + } 148 + 149 + return session, nil 150 + } 151 + 152 + func me(ctx context.Context) error { 153 + session, err := resumeSession(ctx) 154 + if err != nil { 155 + return err 133 156 } 134 157 135 158 client := session.APIClient() ··· 142 165 var pretty bytes.Buffer 143 166 json.Indent(&pretty, resp, "", " ") 144 167 fmt.Println(pretty.String()) 168 + 169 + return nil 170 + } 171 + 172 + func create(ctx context.Context, handle string) error { 173 + session, err := resumeSession(ctx) 174 + if err != nil { 175 + return err 176 + } 177 + 178 + client := session.APIClient() 179 + 180 + // Resolve handle to DID 181 + var resolveResp struct { 182 + DID string `json:"did"` 183 + } 184 + if err := client.Get(ctx, "com.atproto.identity.resolveHandle", map[string]any{ 185 + "handle": handle, 186 + }, &resolveResp); err != nil { 187 + return fmt.Errorf("resolving handle %q: %w", handle, err) 188 + } 189 + 190 + subjectDID, err := syntax.ParseDID(resolveResp.DID) 191 + if err != nil { 192 + return fmt.Errorf("invalid DID from resolution: %w", err) 193 + } 194 + 195 + // Create the vouch record 196 + rkey := syntax.NewTIDNow(0) 197 + record := map[string]any{ 198 + "$type": "dev.atvouch.graph.vouch", 199 + "subject": subjectDID.String(), 200 + "createdAt": time.Now().UTC().Format(time.RFC3339), 201 + } 202 + 203 + var createResp struct { 204 + URI string `json:"uri"` 205 + CID string `json:"cid"` 206 + } 207 + if err := client.Post(ctx, "com.atproto.repo.createRecord", map[string]any{ 208 + "repo": session.Data.AccountDID, 209 + "collection": "dev.atvouch.graph.vouch", 210 + "rkey": rkey.String(), 211 + "record": record, 212 + }, &createResp); err != nil { 213 + return fmt.Errorf("creating vouch record: %w", err) 214 + } 215 + 216 + fmt.Printf("Vouched for %s (%s)\n", handle, subjectDID) 217 + fmt.Printf("Record: %s\n", createResp.URI) 145 218 146 219 return nil 147 220 }