diff --git a/cmd/signup/README.md b/cmd/signup/README.md index 690c5f2..3fb9afa 100644 --- a/cmd/signup/README.md +++ b/cmd/signup/README.md @@ -1,61 +1,79 @@ -The point of this project is to enable signing up for tilde.town via an ssh connection. +# town signup +The point of this command is to enable signing up for tilde.town via an ssh connection. It is designed to be run when `join@tilde.town` is SSH'd to. -It ought to work something like this: +## to-dos -```bash - -ssh signup@tilde.town - - - - tilde.town +- [ ] finish this command + - [x] interactive guts + - [x] logging + - [ ] write answers to disk + - [ ] splash screen - put off + - [ ] easter egg commands - put off + - [ ] inactivity timer(?) - put off +- [ ] review tool + - [ ] iterate over answers + - [ ] accept + - [ ] notate + - [ ] reject + - [ ] send email with directions on key upload +- [ ] actual account creation + - [ ] accept key + - [ ] accept username + - [ ] create user +- [ ] backlog + - [ ] get a manual dump from psql of json + - [ ] convert into files in the review directory -a creature stands before you. what does it look like? +## configuration - > 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: - - _____________ +It assumes, in `sshd_config`: ``` +Match User join + ForceCommand /town/src/town/cmd/signup/signup + PubkeyAuthentication no + KbdInteractiveAuthentication no + PasswordAuthentication yes + PermitEmptyPasswords yes + DisableForwarding yes +``` -...and so on. +and in `/etc/pam.d/sshd`: -TODO +``` +auth [success=done default=ignore] pam_succeed_if.so user ingroup join +``` -- [ ] 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 +## initial thoughts + +I need a script for how this interaction should go. It should feel more organic and fluid than just filling out a form. It shouldn't, however, take more than 10 minutes to get through stuff. The end result is to collect: + +- an email address +- how a user found out about the town +- if they have a referral from an existing member +- what interests them about the town +- any links to personal websites or social media + +A given is that the applicant is interacting with a "host" that guides them through the process. Should there be more than one host? + +How many rooms should there be? 1? more? + +in terms of data collection, I intend to just save the transcript of their interaction instead of more structured data. The only real structured data are email and referral. + +another idea/thought/dream: + +the whole point of this is to spatialize a form. it's a form! a set of questions and corresponding textarea elements. but in order to trigger self reflection -- as well as a feeling of being seen by another (in a good way) -- i want to turn the form into a shape. i'm thinking about this like the room in Eclipse Penumbra: + +(For the Hollow Head was drug paraphenalia you could walk into. The building itself was the syringe, or the hookah, or the sniff-tube) + +so, spatialized, every room is a question. the rooms take shape as a linear script the user moves through by: + +1. answering a question +2. executing a verb + +and finally at the end, a verb to confirm submission. + +## author + +vilmibm diff --git a/cmd/signup/main.go b/cmd/signup/main.go index 1145ae9..ab453b8 100644 --- a/cmd/signup/main.go +++ b/cmd/signup/main.go @@ -4,48 +4,80 @@ import ( "bytes" "fmt" "io" + "log" "os" + "path" "strings" + "time" "github.com/MakeNowJust/heredoc/v2" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" + "gopkg.in/yaml.v3" ) -/* -Assumes: +const ( + maxInputLength int = 10000 + signupDirectory string = "/town/signups" + logDir string = "/town/var/signup" +) -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 -*/ - -const maxInputLength int = 4000 - -type TownApplication struct { - Email string - HowFound string - Why string - Where string +type scene struct { + Name string + Description string + InBuff io.ReadWriter + InLength int + Host *character } -type streams struct { - In io.Reader - Out io.Writer - Err io.Writer +type townApplication struct { + Email io.ReadWriter + How io.ReadWriter + Why io.ReadWriter + Where io.ReadWriter + Extra io.ReadWriter } -func cli(s *streams) error { +type character struct { + Name string + Description string +} + +func newCharacter(name, description string) *character { + return &character{ + Name: name, + Description: description, + } +} + +func (c *character) Say(msg string) string { + verb := "says" + if c.Name == "you" { + verb = "say" + } + return fmt.Sprintf("[-:-:b]%s[-:-:-] %s: '%s'", + c.Name, + verb, + strings.TrimSpace(msg)) +} + +func main() { + logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix())) + logF, err := os.Create(logFile) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + logger := log.New(logF, "", log.Ldate|log.Ltime) + err = cli(logger) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } +} + +func cli(l *log.Logger) error { pages := tview.NewPages() mainFlex := tview.NewFlex() innerFlex := tview.NewFlex() @@ -98,12 +130,56 @@ func cli(s *streams) error { player := newCharacter("you", "TODO") - type scene struct { - Name string - Description string - InBuff io.ReadWriter - InLength int - Host *character + answers := &townApplication{ + Email: bytes.NewBuffer([]byte{}), + How: bytes.NewBuffer([]byte{}), + Why: bytes.NewBuffer([]byte{}), + Where: bytes.NewBuffer([]byte{}), + Extra: bytes.NewBuffer([]byte{}), + } + + save := func() { + when := time.Now() + // TODO this code sucks + email := []byte{} + how := []byte{} + why := []byte{} + where := []byte{} + extra := []byte{} + _, _ = answers.Email.Read(email) + _, _ = answers.Why.Read(why) + _, _ = answers.How.Read(how) + _, _ = answers.Where.Read(where) + _, _ = answers.Extra.Read(extra) + d := map[string]interface{}{ + "when": when, + "email": email, + "how": how, + "why": why, + "where": where, + "extra": extra, + } + output, err := yaml.Marshal(d) + if err != nil { + l.Printf("could not serialize stuff: %s", err.Error()) + l.Printf("dumping values: %v", d) + return + } + + f, err := os.Create(path.Join(signupDirectory, fmt.Sprintf("%d", when.Unix()))) + if err != nil { + l.Printf("could not open signup file: %s", err.Error()) + l.Printf("dumping values: %s", string(output)) + return + } + defer f.Close() + + _, err = f.Write(output) + if err != nil { + l.Printf("failed to write to file: %s", err.Error()) + l.Printf("dumping values: %s", string(output)) + return + } } scenes := []*scene{ @@ -126,7 +202,7 @@ func cli(s *streams) error { when you're ready to move on, [-:-:b]/nod[-:-:-] `), Host: newCharacter("wire guy", "a lil homonculus made of discarded computer cables"), - InBuff: bytes.NewBuffer([]byte{}), + InBuff: answers.Email, }, { Name: "nodded", @@ -148,7 +224,7 @@ func cli(s *streams) error { just say your answer out loud. when you've said what you want, [-:-:b]/lean[-:-:-] against a tree. `), - InBuff: bytes.NewBuffer([]byte{}), + InBuff: answers.How, Host: newCharacter("the shrike", "a little grey bird. it has a pretty song."), }, { @@ -167,7 +243,7 @@ func cli(s *streams) error { as usual, just say your answer. when you're satisfied, please [-:-:b]/spin[-:-:-] around in this void. `), - InBuff: bytes.NewBuffer([]byte{}), + InBuff: answers.Why, Host: newCharacter("the vcr", "a black and grey VCR from 1991"), }, { @@ -191,11 +267,11 @@ func cli(s *streams) error { when you're happy you can submit this whole experience by leaving the store. just [-:-:b]/open[-:-:-] the door. `), - InBuff: bytes.NewBuffer([]byte{}), + InBuff: answers.Where, Host: newCharacter("the mop", "a greying mop with a wooden handle."), }, { - Name: "spun", + Name: "done", Description: heredoc.Doc(` thank you for applying to tilde.town! @@ -205,7 +281,7 @@ func cli(s *streams) error { ok bye have a good one~ `), - InBuff: bytes.NewBuffer([]byte{}), + InBuff: answers.Extra, }, } @@ -220,6 +296,7 @@ func cli(s *streams) error { fmt.Fprintln(msgScroll, currentScene.Host.Say(sorryMsg)) return } + // TODO put contents of InBuff into answers struct sceneIx++ currentScene = scenes[sceneIx] fmt.Fprintln(msgScroll, heredoc.Doc(` @@ -256,9 +333,15 @@ func cli(s *streams) error { advanceScene("leaned", "hmm did you say something?") case "open": advanceScene("spun", "just the one last thing please") + save() } return } + if currentScene.InLength+len(msg) > maxInputLength { + fmt.Fprintln(msgScroll, + currentScene.Host.Say("sorry I've heard more than I can remember :( maybe it's time to move on")) + return + } fmt.Fprintln(msgScroll, player.Say(msg)) fmt.Fprintln(currentScene.InBuff, msg) currentScene.InLength += len(msg) @@ -282,70 +365,3 @@ func cli(s *streams) error { return app.Run() } - -/* - -I need a script for how this interaction should go. It should feel more organic and fluid than just filling out a form. It shouldn't, however, take more than 10 minutes to get through stuff. The end result is to collect: - -- an email address -- how a user found out about the town -- if they have a referral from an existing member -- what interests them about the town -- any links to personal websites or social media - -A given is that the applicant is interacting with a "host" that guides them through the process. Should there be more than one host? - -How many rooms should there be? 1? more? - -in terms of data collection, I intend to just save the transcript of their interaction instead of more structured data. The only real structured data are email and referral. - -another idea/thought/dream: - -the whole point of this is to spatialize a form. it's a form! a set of questions and corresponding textarea elements. but in order to trigger self reflection -- as well as a feeling of being seen by another (in a good way) -- i want to turn the form into a shape. i'm thinking about this like the room in Eclipse Penumbra: - -(For the Hollow Head was drug paraphenalia you could walk into. The building itself was the syringe, or the hookah, or the sniff-tube) - -so, spatialized, every room is a question. the rooms take shape as a linear script the user moves through by: - -1. answering a question -2. executing a verb - -and finally at the end, a verb to confirm submission. - -*/ - -type character struct { - Name string - Description string -} - -func newCharacter(name, description string) *character { - return &character{ - Name: name, - Description: description, - } -} - -func (c *character) Say(msg string) string { - verb := "says" - if c.Name == "you" { - verb = "say" - } - return fmt.Sprintf("[-:-:b]%s[-:-:-] %s: '%s'", - c.Name, - verb, - strings.TrimSpace(msg)) -} - -func main() { - s := &streams{ - In: os.Stdin, - Out: os.Stdout, - Err: os.Stderr, - } - err := cli(s) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} diff --git a/cmd/signup/thoughts.md b/cmd/signup/thoughts.md index b839984..744f76f 100644 --- a/cmd/signup/thoughts.md +++ b/cmd/signup/thoughts.md @@ -1,4 +1,4 @@ -# New commands for signups +# New commands for signups, rough draft ## town-signup @@ -39,8 +39,3 @@ this binary helps manage keys for users; basically automating the listing, addin - `user-key add ` - `user-key remove ` - - - - -