this repo has no description
0
fork

Configure Feed

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

add command for listing invite tree and checking user invites (#131)

Some utilities to help us audit how we are giving invites out

authored by

Whyrusleeping and committed by
GitHub
f1f24808 522cd573

+320
+319
cmd/gosky/admin.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "os" 8 + "sort" 9 + "sync" 10 + "time" 11 + 12 + "github.com/bluesky-social/indigo/api" 13 + comatproto "github.com/bluesky-social/indigo/api/atproto" 14 + cliutil "github.com/bluesky-social/indigo/cmd/gosky/util" 15 + cli "github.com/urfave/cli/v2" 16 + ) 17 + 18 + var adminCmd = &cli.Command{ 19 + Name: "admin", 20 + Subcommands: []*cli.Command{ 21 + buildInviteTreeCmd, 22 + checkUserCmd, 23 + }, 24 + } 25 + 26 + var checkUserCmd = &cli.Command{ 27 + Name: "checkUser", 28 + Flags: []cli.Flag{ 29 + &cli.StringFlag{ 30 + Name: "admin-password", 31 + EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 32 + Required: true, 33 + }, 34 + &cli.StringFlag{ 35 + Name: "plc", 36 + Usage: "method, hostname, and port of PLC registry", 37 + Value: "https://plc.directory", 38 + EnvVars: []string{"ATP_PLC_HOST"}, 39 + }, 40 + &cli.BoolFlag{ 41 + Name: "raw", 42 + }, 43 + }, 44 + Action: func(cctx *cli.Context) error { 45 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 46 + if err != nil { 47 + return err 48 + } 49 + 50 + ctx := context.Background() 51 + 52 + resp, err := comatproto.IdentityResolveHandle(ctx, xrpcc, cctx.Args().First()) 53 + if err != nil { 54 + return fmt.Errorf("resolve handle %q: %w", cctx.Args().First(), err) 55 + } 56 + 57 + adminKey := cctx.String("admin-password") 58 + xrpcc.AdminToken = &adminKey 59 + 60 + rep, err := comatproto.AdminGetRepo(ctx, xrpcc, resp.Did) 61 + if err != nil { 62 + return fmt.Errorf("getRepo %s: %w", resp.Did, err) 63 + } 64 + 65 + b, err := json.MarshalIndent(rep, "", " ") 66 + if err != nil { 67 + return err 68 + } 69 + 70 + plcc := cliutil.GetPLCClient(cctx) 71 + 72 + if cctx.Bool("raw") { 73 + fmt.Println(string(b)) 74 + } else { 75 + var invby string 76 + if fa := rep.InvitedBy.ForAccount; fa != "" { 77 + if fa == "admin" { 78 + invby = fa 79 + } else { 80 + handle, _, err := api.ResolveDidToHandle(ctx, xrpcc, plcc, fa) 81 + if err != nil { 82 + return fmt.Errorf("resolve did %q: %w", fa, err) 83 + } 84 + 85 + invby = handle 86 + } 87 + } 88 + 89 + fmt.Println(rep.Handle) 90 + fmt.Println(rep.Did) 91 + fmt.Println(rep.Email) 92 + fmt.Println("indexed at: ", rep.IndexedAt) 93 + fmt.Printf("Invited by: %s\n", invby) 94 + 95 + var invited []*comatproto.AdminDefs_RepoViewDetail 96 + var lk sync.Mutex 97 + var wg sync.WaitGroup 98 + var used int 99 + for _, inv := range rep.Invites { 100 + used += len(inv.Uses) 101 + 102 + for _, u := range inv.Uses { 103 + wg.Add(1) 104 + go func(did string) { 105 + defer wg.Done() 106 + repo, err := comatproto.AdminGetRepo(ctx, xrpcc, did) 107 + if err != nil { 108 + fmt.Println("ERROR: ", err) 109 + return 110 + } 111 + 112 + lk.Lock() 113 + invited = append(invited, repo) 114 + lk.Unlock() 115 + }(u.UsedBy) 116 + } 117 + } 118 + 119 + wg.Wait() 120 + 121 + fmt.Printf("Invites, used %d of %d\n", used, len(rep.Invites)) 122 + for _, inv := range invited { 123 + 124 + var invited, total int 125 + for _, code := range inv.Invites { 126 + total += len(code.Uses) + int(code.Available) 127 + invited += len(code.Uses) 128 + } 129 + 130 + fmt.Printf(" - %s (%d / %d)\n", inv.Handle, invited, total) 131 + } 132 + } 133 + return nil 134 + }, 135 + } 136 + 137 + var buildInviteTreeCmd = &cli.Command{ 138 + Name: "buildInviteTree", 139 + Flags: []cli.Flag{ 140 + &cli.StringFlag{ 141 + Name: "admin-password", 142 + EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 143 + Required: true, 144 + }, 145 + &cli.StringFlag{ 146 + Name: "invite-list", 147 + }, 148 + &cli.IntFlag{ 149 + Name: "top", 150 + Value: 50, 151 + }, 152 + }, 153 + Action: func(cctx *cli.Context) error { 154 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 155 + if err != nil { 156 + return err 157 + } 158 + 159 + ctx := context.Background() 160 + 161 + adminKey := cctx.String("admin-password") 162 + 163 + xrpcc.AdminToken = &adminKey 164 + 165 + var allcodes []*comatproto.ServerDefs_InviteCode 166 + 167 + if invl := cctx.String("invite-list"); invl != "" { 168 + fi, err := os.Open(invl) 169 + if err != nil { 170 + return err 171 + } 172 + 173 + if err := json.NewDecoder(fi).Decode(&allcodes); err != nil { 174 + return err 175 + } 176 + } else { 177 + var cursor string 178 + for { 179 + invites, err := comatproto.AdminGetInviteCodes(ctx, xrpcc, cursor, 100, "") 180 + if err != nil { 181 + return err 182 + } 183 + 184 + allcodes = append(allcodes, invites.Codes...) 185 + 186 + if invites.Cursor != nil { 187 + cursor = *invites.Cursor 188 + } 189 + if len(invites.Codes) == 0 { 190 + break 191 + } 192 + } 193 + 194 + fi, err := os.Create("output.json") 195 + if err != nil { 196 + return err 197 + } 198 + defer fi.Close() 199 + 200 + if err := json.NewEncoder(fi).Encode(allcodes); err != nil { 201 + return err 202 + } 203 + } 204 + 205 + users := make(map[string]*userInviteInfo) 206 + users["admin"] = &userInviteInfo{ 207 + Handle: "admin", 208 + } 209 + 210 + var getUser func(did string) (*userInviteInfo, error) 211 + getUser = func(did string) (*userInviteInfo, error) { 212 + u, ok := users[did] 213 + if ok { 214 + return u, nil 215 + } 216 + 217 + repo, err := comatproto.AdminGetRepo(ctx, xrpcc, did) 218 + if err != nil { 219 + return nil, err 220 + } 221 + 222 + var invby string 223 + if fa := repo.InvitedBy.ForAccount; fa != "" { 224 + if fa == "admin" { 225 + invby = "admin" 226 + } else { 227 + invu, ok := users[fa] 228 + if ok { 229 + invby = invu.Handle 230 + } else { 231 + invrepo, err := comatproto.AdminGetRepo(ctx, xrpcc, fa) 232 + if err != nil { 233 + return nil, fmt.Errorf("resolving inviter (%q): %w", fa, err) 234 + } 235 + 236 + invby = invrepo.Handle 237 + } 238 + } 239 + } 240 + 241 + u = &userInviteInfo{ 242 + Did: did, 243 + Handle: repo.Handle, 244 + InvitedBy: repo.InvitedBy.ForAccount, 245 + InvitedByHandle: invby, 246 + TotalInvites: len(repo.Invites), 247 + } 248 + if repo.Email != nil { 249 + u.Email = *repo.Email 250 + } 251 + 252 + users[did] = u 253 + 254 + return u, nil 255 + } 256 + _ = getUser 257 + 258 + initmap := make(map[string]*basicInvInfo) 259 + var initlist []*basicInvInfo 260 + for _, inv := range allcodes { 261 + acc, ok := initmap[inv.ForAccount] 262 + if !ok { 263 + acc = &basicInvInfo{ 264 + Did: inv.ForAccount, 265 + } 266 + initmap[inv.ForAccount] = acc 267 + initlist = append(initlist, acc) 268 + } 269 + 270 + acc.TotalInvites += int(inv.Available) + len(inv.Uses) 271 + for _, u := range inv.Uses { 272 + acc.Invited = append(acc.Invited, u.UsedBy) 273 + } 274 + } 275 + 276 + sort.Slice(initlist, func(i, j int) bool { 277 + return len(initlist[i].Invited) > len(initlist[j].Invited) 278 + }) 279 + 280 + for i := 0; i < cctx.Int("top"); i++ { 281 + u, err := getUser(initlist[i].Did) 282 + if err != nil { 283 + fmt.Printf("getuser %q: %s\n", initlist[i].Did, err) 284 + continue 285 + } 286 + 287 + fmt.Printf("%d: %s (%d of %d)\n", i, u.Handle, len(initlist[i].Invited), u.TotalInvites) 288 + } 289 + 290 + /* 291 + fmt.Println("writing output...") 292 + outfi, err := os.Create("userdump.json") 293 + if err != nil { 294 + return err 295 + } 296 + defer outfi.Close() 297 + 298 + return json.NewEncoder(outfi).Encode(users) 299 + */ 300 + return nil 301 + }, 302 + } 303 + 304 + type userInviteInfo struct { 305 + CreatedAt time.Time 306 + Did string 307 + Handle string 308 + InvitedBy string 309 + InvitedByHandle string 310 + TotalInvites int 311 + Invited []string 312 + Email string 313 + } 314 + 315 + type basicInvInfo struct { 316 + Did string 317 + Invited []string 318 + TotalInvites int 319 + }
+1
cmd/gosky/main.go
··· 84 84 handleCmd, 85 85 getRecordCmd, 86 86 createInviteCmd, 87 + adminCmd, 87 88 } 88 89 89 90 app.RunAndExitOnError()