forked from tildetown/town
		
	stuff n things
This commit is contained in:
		
							parent
							
								
									65228979e9
								
							
						
					
					
						commit
						cf99807126
					
				@ -246,7 +246,8 @@ func rootCmd() *cobra.Command {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	rc.Flags().StringVarP(&updateName, "update", "u", "", "Name of command to update")
 | 
						rc.Flags().StringVarP(&updateName, "update", "u", "", "Name of command to update")
 | 
				
			||||||
	rc.Flags().StringVarP(&delName, "delete", "d", "", "Name of command to delete")
 | 
						rc.Flags().StringVarP(&delName, "delete", "d", "", "Name of command to delete")
 | 
				
			||||||
	rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
 | 
						// TODO hiding this until i decide i want it or not
 | 
				
			||||||
 | 
						//rc.Flags().BoolVarP(&force, "force", "f", false, "skip request, just install the command")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return rc
 | 
						return rc
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										61
									
								
								cmd/signup/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								cmd/signup/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					The point of this project is to enable signing up for tilde.town via an ssh connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It ought to work something like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ssh signup@tilde.town
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       <ascii art>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       tilde.town
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a creature stands before you. what does it look like?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  > a floating gray cube
 | 
				
			||||||
 | 
					    a calico cat with softly glowing eyes
 | 
				
			||||||
 | 
					    a squid
 | 
				
			||||||
 | 
					    something else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cube: you'd like to sign up for tilde.town, yes?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 > yeah
 | 
				
			||||||
 | 
					   nah
 | 
				
			||||||
 | 
					   why should I?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					you say, "yeah"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cube: by what name should i call you?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ~cowcow________
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cube: unfortunately, i already know someone by that name. what else can i call you?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ~shelf_________
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cube: excellent, hello ~shelf. have you been invited here by anyone i know?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 > yes, i have an invite code
 | 
				
			||||||
 | 
					   no
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					you say "yes, i have an invite code"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cube: great. please paste it and press enter:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _____________
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					...and so on. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] make signup user
 | 
				
			||||||
 | 
					- [ ] get dummy program to run as ssh handler
 | 
				
			||||||
 | 
					- [ ] tcell bootstrapping some kind of interactivity
 | 
				
			||||||
 | 
					- [ ] existing username check
 | 
				
			||||||
 | 
					- [ ] email validation
 | 
				
			||||||
 | 
					- [ ] collect responses
 | 
				
			||||||
 | 
					- [ ] create the signup request (on disk? db?)
 | 
				
			||||||
 | 
					- [ ] invite system support
 | 
				
			||||||
							
								
								
									
										366
									
								
								cmd/signup/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								cmd/signup/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,366 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/rivo/tview"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Assumes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Match User join
 | 
				
			||||||
 | 
					        ForceCommand /town/src/town/cmd/signup/signup
 | 
				
			||||||
 | 
					        PubkeyAuthentication no
 | 
				
			||||||
 | 
					        KbdInteractiveAuthentication no
 | 
				
			||||||
 | 
					        PasswordAuthentication yes
 | 
				
			||||||
 | 
					        PermitEmptyPasswords yes
 | 
				
			||||||
 | 
					        DisableForwarding yes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in sshd_config, and:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auth [success=done default=ignore] pam_succeed_if.so user ingroup join
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					in /etc/pam.d/sshd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					is there a way :qa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type streams struct {
 | 
				
			||||||
 | 
						In  io.Reader
 | 
				
			||||||
 | 
						Out io.Writer
 | 
				
			||||||
 | 
						Err io.Writer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Prompter struct {
 | 
				
			||||||
 | 
						in    io.Reader
 | 
				
			||||||
 | 
						out   io.Writer
 | 
				
			||||||
 | 
						width int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Prompter) Say(m string) {
 | 
				
			||||||
 | 
						for _, line := range wrap(m, p.width) {
 | 
				
			||||||
 | 
							fmt.Fprintln(p.out, line)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Prompter) CharSay(c, m string) {
 | 
				
			||||||
 | 
						sayPrefix := fmt.Sprintf("%s says: ", c)
 | 
				
			||||||
 | 
						prefixWidth := utf8.RuneCountInString(sayPrefix)
 | 
				
			||||||
 | 
						width := p.width - prefixWidth
 | 
				
			||||||
 | 
						indent := ""
 | 
				
			||||||
 | 
						for x := 0; x < utf8.RuneCountInString(sayPrefix); x++ {
 | 
				
			||||||
 | 
							indent += " "
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i, line := range wrap(m, width) {
 | 
				
			||||||
 | 
							if i == 0 {
 | 
				
			||||||
 | 
								fmt.Fprintln(p.out, sayPrefix+line)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								fmt.Fprintln(p.out, indent+line)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type InputAnswer struct {
 | 
				
			||||||
 | 
						Value string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func runeLen(s string) int {
 | 
				
			||||||
 | 
						return utf8.RuneCountInString(s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func wrap(s string, width int) []string {
 | 
				
			||||||
 | 
						fielded := strings.Fields(s)
 | 
				
			||||||
 | 
						out := []string{}
 | 
				
			||||||
 | 
						line := ""
 | 
				
			||||||
 | 
						for i, field := range fielded {
 | 
				
			||||||
 | 
							if runeLen(field)+runeLen(line)+1 < width {
 | 
				
			||||||
 | 
								line += field + " "
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								out = append(out, line)
 | 
				
			||||||
 | 
								line = field + " "
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if i == len(fielded)-1 {
 | 
				
			||||||
 | 
								out = append(out, line)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Prompter) Confirm(m string) (bool, error) {
 | 
				
			||||||
 | 
						result := InputAnswer{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							err := p.Ask(m, &result)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							val := strings.TrimSpace(result.Value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch val {
 | 
				
			||||||
 | 
							case "yes", "y":
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							case "no", "n":
 | 
				
			||||||
 | 
								return false, nil
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								p.Say("sorry, please say y or n")
 | 
				
			||||||
 | 
								p.Pause()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Prompter) Ask(m string, result *InputAnswer) error {
 | 
				
			||||||
 | 
						fmt.Fprintf(p.out, "%s ", m)
 | 
				
			||||||
 | 
						var val string
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reader := bufio.NewReader(p.in)
 | 
				
			||||||
 | 
						for val == "" {
 | 
				
			||||||
 | 
							val, err = reader.ReadString('\n')
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if val == "" {
 | 
				
			||||||
 | 
								p.Say("hmm, what was that?")
 | 
				
			||||||
 | 
								p.Pause()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.Value = val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Prompter) AskLong(m string, result *InputAnswer) error {
 | 
				
			||||||
 | 
						fmt.Fprintf(p.out, "%s\n", m)
 | 
				
			||||||
 | 
						var val []byte
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reader := bufio.NewReader(p.in)
 | 
				
			||||||
 | 
						for len(val) == 0 {
 | 
				
			||||||
 | 
							val, err = ioutil.ReadAll(reader)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(val) == 0 {
 | 
				
			||||||
 | 
								fmt.Fprintf(p.out, "%s\n", m)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.Value = string(val)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Prompter) Pause() {
 | 
				
			||||||
 | 
						fmt.Fprintln(p.out)
 | 
				
			||||||
 | 
						time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewPrompter(width int, s *streams) Prompter {
 | 
				
			||||||
 | 
						return Prompter{
 | 
				
			||||||
 | 
							in:    s.In,
 | 
				
			||||||
 | 
							out:   s.Out,
 | 
				
			||||||
 | 
							width: width,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type answers struct {
 | 
				
			||||||
 | 
						username string
 | 
				
			||||||
 | 
						email    string
 | 
				
			||||||
 | 
						applied  time.Time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						howDay   string
 | 
				
			||||||
 | 
						howHeard string
 | 
				
			||||||
 | 
						reasons  string
 | 
				
			||||||
 | 
						plans    string
 | 
				
			||||||
 | 
						socials  string
 | 
				
			||||||
 | 
						sshKey   string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func _main(args []string, s *streams) error {
 | 
				
			||||||
 | 
						a := answers{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p := NewPrompter(80, s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// disable input buffering
 | 
				
			||||||
 | 
						exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// LOL i don't think this will work
 | 
				
			||||||
 | 
						reader := bufio.NewReader(p.in)
 | 
				
			||||||
 | 
						s, _ := reader.ReadString(4)
 | 
				
			||||||
 | 
						fmt.Printf("DBG %#v\n", s)
 | 
				
			||||||
 | 
						//var b []byte = make([]byte, 100)
 | 
				
			||||||
 | 
						//for {
 | 
				
			||||||
 | 
						//	os.Stdin.Read(b)
 | 
				
			||||||
 | 
						//	fmt.Println("I got the byte", b, "("+string(b)+")")
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var ia InputAnswer
 | 
				
			||||||
 | 
						if err := p.AskLong("lol give me stuff hit ctrl+d", &ia); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fmt.Printf("DBG %#v\n", ia.Value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.Say("you are standing in a field.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
						p.Say("there are flowers around you. you are standing in a slight depression and before you is grass touching a purple sky.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
						p.Say("you are not sure how long it's been when a cube about a meter high appears at the top of the hill before you.")
 | 
				
			||||||
 | 
						p.Say("the cube's surface is murky but iridescently reflective like an oil slick.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
						p.CharSay("cube", "hello. how is your day going?")
 | 
				
			||||||
 | 
						p.Say("(you can type a response and hit enter to talk to the cube)")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						howDay := InputAnswer{}
 | 
				
			||||||
 | 
						err := p.Ask("you say:", &howDay)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.howDay = howDay.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.Say("the cube inclines towards you gently as if nodding.")
 | 
				
			||||||
 | 
						p.CharSay("cube", "i see.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "i am guessing that if you are here, you want to be there.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.Say("you blink and are somewhere else.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.Say("the field of flowers is behind you and now you are up on the hill. the cube is next to you.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
						p.Say("you can see clear across a wide open plain. structures large and small dot the landscape. you catch a whiff of a savory smell and can just barely hear voices on the wind.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "the tilde town lies before us. if you want to continue, i'll ask you some questions about acquiring a home down in the town. you'll be free to edit your responses before i carry them off.")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "first, i'm curious how you found out about the town?")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						answer := InputAnswer{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := p.Ask("you say:", &answer); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.howHeard = answer.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "interesting, thanks.")
 | 
				
			||||||
 | 
						p.CharSay("cube", "what sort of stuff do you want to get up to on the town?")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := p.Ask("you say:", &answer); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.plans = answer.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "thanks.")
 | 
				
			||||||
 | 
						p.CharSay("cube", "what do you like about the town?")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := p.Ask("you say:", &answer); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.reasons = answer.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "i appreciate it.")
 | 
				
			||||||
 | 
						p.CharSay("cube", "can you paste some links to other places you are active online? maybe a homepage or social media profile? if you aren't comfortable sharing or there are none, just say so.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := p.Ask("you say:", &answer); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.socials = answer.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "ok, last thing. in order to break ground in the town, you'll need an SSH key. If you don't know what that is, you can check out this link: https://tilde.town/ssh.html .")
 | 
				
			||||||
 | 
						p.Pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := p.AskLong("paste SSH public key; press control+d to submit:", &answer); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						a.sshKey = answer.Value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.CharSay("cube", "i know that was a lot so i appreciate it. i've got everything written down here. before i carry it off, do you want to review and edit what you wrote?")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						confirmed, err := p.Confirm("type y or n: ")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if confirmed {
 | 
				
			||||||
 | 
							err := edit(s, &a)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Printf("DBG %#v\n", confirmed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Printf("DBG %#v\n", a)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO allow for editing
 | 
				
			||||||
 | 
						// TODO write answers to disk
 | 
				
			||||||
 | 
						// TODO add a log
 | 
				
			||||||
 | 
						// TODO pretty colors
 | 
				
			||||||
 | 
						// TODO ascii art
 | 
				
			||||||
 | 
						// TODO IP throttling?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func edit(s *streams, a *answers) error {
 | 
				
			||||||
 | 
						// TODO make more real
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
							username string
 | 
				
			||||||
 | 
							email    string
 | 
				
			||||||
 | 
							applied  time.Time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							howDay   string
 | 
				
			||||||
 | 
							howHeard string
 | 
				
			||||||
 | 
							reasons  string
 | 
				
			||||||
 | 
							plans    string
 | 
				
			||||||
 | 
							socials  string
 | 
				
			||||||
 | 
							sshKey   string
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						// TODO add note about tabbing around
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app := tview.NewApplication()
 | 
				
			||||||
 | 
						form := tview.NewForm().
 | 
				
			||||||
 | 
							AddInputField("how did you hear about the town?", a.howHeard, 0, nil, nil).
 | 
				
			||||||
 | 
							AddButton("cool i'm good", nil).
 | 
				
			||||||
 | 
							AddButton("cancel and discard all this please", func() { app.Stop() })
 | 
				
			||||||
 | 
						form.SetBorder(true).SetTitle("edit your stuff").SetTitleAlign(tview.AlignCenter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return app.SetRoot(form, true).EnableMouse(true).Run()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						retcode := 0
 | 
				
			||||||
 | 
						s := &streams{
 | 
				
			||||||
 | 
							In:  os.Stdin,
 | 
				
			||||||
 | 
							Out: os.Stdout,
 | 
				
			||||||
 | 
							Err: os.Stderr,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := _main(os.Args, s)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							retcode = 1
 | 
				
			||||||
 | 
							fmt.Fprintln(os.Stderr, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						os.Exit(retcode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										46
									
								
								cmd/signup/thoughts.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								cmd/signup/thoughts.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					# New commands for signups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## town-signup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					this binary is run by OpenSSH when a user anywhere in the world runs `ssh join@tilde.town`. It should:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- collect information about an application from a user
 | 
				
			||||||
 | 
					- allow them to edit their responses before submitting
 | 
				
			||||||
 | 
					- write a yaml file of their responses to disk 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## review-signups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					this binary is run by town admins in order to review, approve, notate, and reject applications. It should:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- iterate over signups
 | 
				
			||||||
 | 
					- allow fuzzy finding a particular signup
 | 
				
			||||||
 | 
					- allow approval using create-user
 | 
				
			||||||
 | 
					- allow rejection
 | 
				
			||||||
 | 
					  - just move signup to archived rejected signup directory
 | 
				
			||||||
 | 
					- allow notating an application, ie:
 | 
				
			||||||
 | 
					  - lock the yaml file for writing
 | 
				
			||||||
 | 
					  - add notes to the yaml file that can be seen by other admins
 | 
				
			||||||
 | 
					- print info about historical signups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## create-user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					this binary is called by `review-signups` to take a yaml application and create a user on disk. It should:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- create the user idempotently
 | 
				
			||||||
 | 
					  - `adduser`
 | 
				
			||||||
 | 
					  - `usermod` to set group
 | 
				
			||||||
 | 
					  - calling `add-key` for user
 | 
				
			||||||
 | 
					- move the yaml file to an archive directory of approved signups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## user-key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					this binary helps manage keys for users; basically automating the listing, adding, and removing of public keys for a user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `user-key add <username> <keyfile`
 | 
				
			||||||
 | 
					- `user-key list <username>`
 | 
				
			||||||
 | 
					- `user-key remove <username>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								go.mod
									
									
									
									
									
								
							@ -5,6 +5,7 @@ go 1.18
 | 
				
			|||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/AlecAivazis/survey/v2 v2.3.5
 | 
						github.com/AlecAivazis/survey/v2 v2.3.5
 | 
				
			||||||
	github.com/charmbracelet/glamour v0.5.0
 | 
						github.com/charmbracelet/glamour v0.5.0
 | 
				
			||||||
 | 
						github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
 | 
				
			||||||
	github.com/spf13/cobra v1.5.0
 | 
						github.com/spf13/cobra v1.5.0
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1
 | 
						gopkg.in/yaml.v3 v3.0.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -13,6 +14,8 @@ require (
 | 
				
			|||||||
	github.com/alecthomas/chroma v0.10.0 // indirect
 | 
						github.com/alecthomas/chroma v0.10.0 // indirect
 | 
				
			||||||
	github.com/aymerick/douceur v0.2.0 // indirect
 | 
						github.com/aymerick/douceur v0.2.0 // indirect
 | 
				
			||||||
	github.com/dlclark/regexp2 v1.4.0 // indirect
 | 
						github.com/dlclark/regexp2 v1.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/gdamore/encoding v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/gdamore/tcell/v2 v2.5.3 // indirect
 | 
				
			||||||
	github.com/gorilla/css v1.0.0 // indirect
 | 
						github.com/gorilla/css v1.0.0 // indirect
 | 
				
			||||||
	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 | 
						github.com/inconshreveable/mousetrap v1.0.0 // indirect
 | 
				
			||||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
						github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
				
			||||||
@ -25,12 +28,12 @@ require (
 | 
				
			|||||||
	github.com/muesli/reflow v0.3.0 // indirect
 | 
						github.com/muesli/reflow v0.3.0 // indirect
 | 
				
			||||||
	github.com/muesli/termenv v0.9.0 // indirect
 | 
						github.com/muesli/termenv v0.9.0 // indirect
 | 
				
			||||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
						github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
				
			||||||
	github.com/rivo/uniseg v0.2.0 // indirect
 | 
						github.com/rivo/uniseg v0.4.2 // indirect
 | 
				
			||||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
						github.com/spf13/pflag v1.0.5 // indirect
 | 
				
			||||||
	github.com/yuin/goldmark v1.4.4 // indirect
 | 
						github.com/yuin/goldmark v1.4.4 // indirect
 | 
				
			||||||
	github.com/yuin/goldmark-emoji v1.0.1 // indirect
 | 
						github.com/yuin/goldmark-emoji v1.0.1 // indirect
 | 
				
			||||||
	golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
 | 
						golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
 | 
				
			||||||
	golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
 | 
						golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
 | 
				
			||||||
	golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
 | 
						golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
 | 
				
			||||||
	golang.org/x/text v0.3.6 // indirect
 | 
						golang.org/x/text v0.3.7 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								go.sum
									
									
									
									
									
								
							@ -16,6 +16,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 | 
				
			|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
 | 
					github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
 | 
				
			||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 | 
					github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 | 
				
			||||||
 | 
					github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
 | 
				
			||||||
 | 
					github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
 | 
				
			||||||
 | 
					github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
 | 
				
			||||||
 | 
					github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
 | 
				
			||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 | 
					github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 | 
				
			||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 | 
					github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 | 
				
			||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
 | 
					github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
 | 
				
			||||||
@ -47,9 +51,12 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
 | 
				
			|||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 | 
					github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c h1:zIYU4PjQJ4BnYryMmpyizt1Un13V0ToCMXvC05DK8xc=
 | 
				
			||||||
 | 
					github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c/go.mod h1:lBUy/T5kyMudFzWUH/C2moN+NlU5qF505vzOyINXuUQ=
 | 
				
			||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
					github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
				
			||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 | 
					 | 
				
			||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
					github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
				
			||||||
 | 
					github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
 | 
				
			||||||
 | 
					github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 | 
				
			||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
					github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
				
			||||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
 | 
					github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
 | 
				
			||||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 | 
					github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 | 
				
			||||||
@ -70,14 +77,18 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
 | 
				
			|||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
 | 
					golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
 | 
					golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
 | 
				
			||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
 | 
					golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 | 
					 | 
				
			||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user