forked from tildetown/town
Compare commits
18 Commits
d5e6960573
...
1e7a016dca
Author | SHA1 | Date |
---|---|---|
|
1e7a016dca | |
|
0472c24199 | |
|
a124b27021 | |
|
edf4f68932 | |
|
2fbedb75c2 | |
|
1ca0893c2e | |
|
ccc357591e | |
|
852a104300 | |
|
bf2f2e3790 | |
|
c5590cac95 | |
|
f624483614 | |
|
1bec2349cb | |
|
e12d92735d | |
|
3638685d24 | |
|
cb0d574a76 | |
|
7291a43e68 | |
|
869eaa5f3b | |
|
5876b0ebe3 |
10
README.md
10
README.md
|
@ -8,11 +8,15 @@ commands:
|
|||
- `request` (invokved on town as `town request-gitea` or `town request-gemini`), a helper command for requesting certain featuers be enabled for your town account
|
||||
- `stats` (invoked as `town stats`), a command that prints out information about the town and its users in JSON.
|
||||
- `visit` an experimental command for "visiting" a user.
|
||||
- `signup` command that powers `ssh join@tilde.town`
|
||||
- `welcome` command that powers `ssh welcome@tilde.town`
|
||||
- `review` a TUI for town admins to review signups
|
||||
|
||||
There are also sundry helpers and scripts under `cmd/`.
|
||||
|
||||
A lot of this behavior (for example, `stats`) is exposed as a library. if you want to make some stuff for town and want to work in Go feel free to import this and use it.
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] expose `stats` as a library
|
||||
- [ ] add a command for contributing scripts to the launcher's index
|
||||
- [ ] consider bringing the launcher's index of commands into this git repo so it can be tracked
|
||||
- [ ] add a Makefile
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,204 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.tilde.town/tildetown/town/models"
|
||||
"git.tilde.town/tildetown/town/signup"
|
||||
"git.tilde.town/tildetown/town/stats"
|
||||
"git.tilde.town/tildetown/town/towndb"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
// this is basically a pile of scripts. no hope is to be found here. this is
|
||||
// dirty, one off code stored in case any useful patterns are worth extracting
|
||||
// or for copypasta fodder.
|
||||
|
||||
func confirmContinue(msg string) {
|
||||
var serr error
|
||||
var conf bool
|
||||
if serr = survey.AskOne(&survey.Confirm{
|
||||
Message: msg,
|
||||
Default: false,
|
||||
}, &conf); serr != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
if !conf {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type jsonSignup struct {
|
||||
Created float64
|
||||
Email string
|
||||
Username string
|
||||
Reasons string
|
||||
Plans string
|
||||
Referral string
|
||||
Socials string
|
||||
Notes string
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, err := towndb.ConnectDB()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lol, err := os.ReadFile("/town/var/users.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
td, err := stats.Stats()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(lol), "\n")
|
||||
errs := []error{}
|
||||
signups := make([]jsonSignup, len(lines))
|
||||
for i, l := range lines {
|
||||
l = strings.TrimSpace(l)
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
s := jsonSignup{}
|
||||
err := json.Unmarshal([]byte(l), &s)
|
||||
if err != nil {
|
||||
fmt.Printf("%s %s", l, err.Error())
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
signups[i] = s
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
confirmContinue(fmt.Sprintf("%d errors found deserializing; continue?", len(errs)))
|
||||
}
|
||||
|
||||
ttbirth, err := time.Parse("2006-01-02 3:04pm", "2014-10-11 11:49pm")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
me := towndb.TownUser{
|
||||
Created: ttbirth,
|
||||
Username: "vilmibm",
|
||||
Emails: []string{"vilmibm@protonmail.com"},
|
||||
State: towndb.StateActive,
|
||||
IsAdmin: true,
|
||||
}
|
||||
err = me.Insert(db)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
notFound := []jsonSignup{}
|
||||
found := []jsonSignup{}
|
||||
for _, su := range signups {
|
||||
fl := len(found)
|
||||
for _, u := range td.Users {
|
||||
if su.Username == u.Username {
|
||||
found = append(found, su)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(found) == fl {
|
||||
notFound = append(notFound, su)
|
||||
}
|
||||
}
|
||||
if len(notFound) > 0 {
|
||||
confirmContinue(fmt.Sprintf("%d of those were not found. continue?", len(notFound)))
|
||||
}
|
||||
|
||||
for _, su := range found {
|
||||
var emails []string
|
||||
if su.Email != "" {
|
||||
emails = []string{su.Email}
|
||||
}
|
||||
|
||||
u := towndb.TownUser{
|
||||
Created: time.Unix(int64(su.Created), 0),
|
||||
Emails: emails,
|
||||
Username: su.Username,
|
||||
State: towndb.StateActive,
|
||||
}
|
||||
if err = u.Insert(db); err != nil {
|
||||
confirmContinue(fmt.Sprintf("%#v led to error %s; continue?", u, err.Error()))
|
||||
}
|
||||
|
||||
if su.Notes != "" {
|
||||
note := towndb.AdminNote{
|
||||
Created: time.Time{},
|
||||
AuthorID: me.ID,
|
||||
Content: su.Notes,
|
||||
UserID: u.ID,
|
||||
}
|
||||
if err = note.Insert(db); err != nil {
|
||||
confirmContinue(fmt.Sprintf("%#v led to error %s; continue?", note, err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importSignups() {
|
||||
lol, err := os.ReadFile("/town/var/signups.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db, err := signup.ConnectDB()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(lol), "\n")
|
||||
errs := []error{}
|
||||
signups := make([]jsonSignup, len(lines))
|
||||
for i, l := range lines {
|
||||
l = strings.TrimSpace(l)
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
s := jsonSignup{}
|
||||
err := json.Unmarshal([]byte(l), &s)
|
||||
if err != nil {
|
||||
fmt.Printf("%s %s", l, err.Error())
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
signups[i] = s
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
confirmContinue(fmt.Sprintf("%d errors found deserializing; continue?", len(errs)))
|
||||
}
|
||||
|
||||
for _, s := range signups {
|
||||
ts := models.TownSignup{
|
||||
Created: time.Unix(int64(s.Created), 0),
|
||||
Email: s.Email,
|
||||
How: s.Referral,
|
||||
Why: s.Reasons + "\n" + s.Plans,
|
||||
Links: s.Socials,
|
||||
}
|
||||
if err = ts.Insert(db); err != nil {
|
||||
confirmContinue(fmt.Sprintf("%#v led to error %s; continue?", ts, err.Error()))
|
||||
}
|
||||
|
||||
if s.Notes != "" {
|
||||
note := models.SignupNote{
|
||||
Created: time.Now(),
|
||||
Author: "IMPORT",
|
||||
Content: s.Notes,
|
||||
SignupID: ts.ID,
|
||||
}
|
||||
if err = note.Insert(db); err != nil {
|
||||
confirmContinue(fmt.Sprintf("%#v led to error %s; continue?", ts, err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# help
|
||||
|
||||
another ssh command:
|
||||
|
||||
```
|
||||
ssh help@tilde.town
|
||||
|
||||
hey what's up?
|
||||
|
||||
> i'm a user and i need to reset my key
|
||||
i tried to sign up and it just didn't work
|
||||
i have a concern or complaint about the town
|
||||
```
|
||||
|
||||
the goal with this is to formalize the "i forgot my key" process and get us off of using an external email inbox which i find irritating.
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("TODO")
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -88,6 +91,42 @@ func renderNotes(s models.TownSignup) string {
|
|||
return out
|
||||
}
|
||||
|
||||
func searchSignups(signups []*models.TownSignup) (int, error) {
|
||||
escapeNuls := func(str string) string {
|
||||
return strings.ReplaceAll(str, "\000", " ")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for ix, signup := range signups {
|
||||
fmt.Fprintf(buf, "%d\t%s\000", ix, escapeNuls(signup.Email))
|
||||
fmt.Fprintf(buf, "%d\t%s\000", ix, escapeNuls(signup.How))
|
||||
fmt.Fprintf(buf, "%d\t%s\000", ix, escapeNuls(signup.Links))
|
||||
fmt.Fprintf(buf, "%d\t%s\000", ix, escapeNuls(signup.Why))
|
||||
}
|
||||
|
||||
cmd := exec.Command("fzf", "--read0", "--delimiter=\t", "--tac", "--with-nth=2..")
|
||||
cmd.Stdin = buf
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
s := strings.Split(string(out[:]), "\t")[0]
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func _main() error {
|
||||
inviteDB, err := invites.ConnectDB()
|
||||
if err != nil {
|
||||
|
@ -132,10 +171,11 @@ func _main() error {
|
|||
title.SetBackgroundColor(tcell.ColorBlack)
|
||||
|
||||
appView := tview.NewTextView()
|
||||
appView.SetScrollable(true)
|
||||
appView.SetDynamicColors(true)
|
||||
|
||||
legend := tview.NewTextView()
|
||||
legend.SetText("s: skip r: random A: approve R: reject N: notate Q: quit")
|
||||
legend.SetText("s/S: next/prev r: random F: find A: approve R: reject N: notate Q: quit")
|
||||
legend.SetTextColor(tcell.ColorPurple)
|
||||
legend.SetTextAlign(tview.AlignCenter)
|
||||
legend.SetBackgroundColor(tcell.ColorBlack)
|
||||
|
@ -169,6 +209,11 @@ func _main() error {
|
|||
mainFlex.AddItem(title, 1, -1, false)
|
||||
mainFlex.AddItem(innerFlex, 0, 1, false)
|
||||
mainFlex.AddItem(bottomFlex, 1, -1, false)
|
||||
// set scrollable
|
||||
mainFlex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
appView.InputHandler()(event, func(p tview.Primitive) {})
|
||||
return nil
|
||||
})
|
||||
|
||||
pages := tview.NewPages()
|
||||
|
||||
|
@ -225,18 +270,18 @@ func _main() error {
|
|||
decisionFI.SetOptions([]string{"accepted", "rejected"}, func(_ string, _ int) {})
|
||||
|
||||
cleanEmailInput := tview.NewInputField()
|
||||
cleanEmailInput.SetLabel("clean email")
|
||||
cleanEmailInput.SetLabel("clean email ")
|
||||
cleanEmailInput.SetAcceptanceFunc(func(tx string, _ rune) bool { return len(tx) > 0 })
|
||||
|
||||
reviewForm.AddFormItem(decisionFI)
|
||||
reviewForm.AddFormItem(cleanEmailInput)
|
||||
reviewForm.AddButton("submit", func() {
|
||||
currSignup := signups[signupIx]
|
||||
cleanEmail := reviewForm.GetFormItemByLabel("clean email").(*tview.InputField).GetText()
|
||||
cleanEmail := cleanEmailInput.GetText()
|
||||
currSignup.CleanEmail = cleanEmail
|
||||
|
||||
decision := models.SignupRejected
|
||||
_, d := reviewForm.GetFormItemByLabel("decision").(*tview.DropDown).GetCurrentOption()
|
||||
_, d := decisionFI.GetCurrentOption()
|
||||
if d == "accepted" {
|
||||
decision = models.SignupAccepted
|
||||
}
|
||||
|
@ -306,20 +351,48 @@ func _main() error {
|
|||
if signupIx == len(signups) {
|
||||
signupIx = 0
|
||||
}
|
||||
updateCount()
|
||||
render()
|
||||
return nil
|
||||
case 'S':
|
||||
signupIx--
|
||||
if signupIx < 0 {
|
||||
signupIx = len(signups) - 1
|
||||
}
|
||||
updateCount()
|
||||
render()
|
||||
return nil
|
||||
case 'r':
|
||||
if len(signups) > 0 {
|
||||
signupIx = rand.Intn(len(signups))
|
||||
updateCount()
|
||||
render()
|
||||
}
|
||||
// TODO: there's a bunch of messy state management.
|
||||
// should we generate this pane functionally?
|
||||
case 'A':
|
||||
if len(signups) == 0 {
|
||||
return nil
|
||||
}
|
||||
emailVal := signups[signupIx].Email
|
||||
providedEmailView.SetText(emailVal)
|
||||
cleanEmailInput.SetPlaceholder(
|
||||
strings.TrimSpace(strings.ReplaceAll(emailVal, "\n", " ")))
|
||||
cleanEmailInput.SetLabel("clean email ")
|
||||
cleanEmailInput.SetText("")
|
||||
/*
|
||||
TODO the placeholder doesn't appear to become the default text which is
|
||||
what I wanted it to do. Just taking this out so the blank box beckons
|
||||
input. Also, it seems like the AcceptanceFunc didn't work since a blank
|
||||
value got through.
|
||||
cleanEmailInput.SetPlaceholder(
|
||||
strings.TrimSpace(strings.ReplaceAll(emailVal, "\n", " ")))
|
||||
*/
|
||||
cleanEmailInput.SetChangedFunc(func(text string) {
|
||||
if strings.Contains(emailVal, text) {
|
||||
cleanEmailInput.SetLabel("clean email ")
|
||||
} else {
|
||||
cleanEmailInput.SetLabel("[red]clean email :(")
|
||||
}
|
||||
})
|
||||
decisionFI.SetCurrentOption(0)
|
||||
pages.SwitchToPage("review")
|
||||
app.SetFocus(cleanEmailInput)
|
||||
|
@ -330,8 +403,23 @@ func _main() error {
|
|||
}
|
||||
emailVal := signups[signupIx].Email
|
||||
providedEmailView.SetText(emailVal)
|
||||
cleanEmailInput.SetPlaceholder(
|
||||
strings.TrimSpace(strings.ReplaceAll(emailVal, "\n", " ")))
|
||||
cleanEmailInput.SetLabel("clean email ")
|
||||
cleanEmailInput.SetText("")
|
||||
/*
|
||||
TODO the placeholder doesn't appear to become the default text which is
|
||||
what I wanted it to do. Just taking this out so the blank box beckons
|
||||
input. Also, it seems like the AcceptanceFunc didn't work since a blank
|
||||
value got through.
|
||||
cleanEmailInput.SetPlaceholder(
|
||||
strings.TrimSpace(strings.ReplaceAll(emailVal, "\n", " ")))
|
||||
*/
|
||||
cleanEmailInput.SetChangedFunc(func(text string) {
|
||||
if strings.Contains(emailVal, text) {
|
||||
cleanEmailInput.SetLabel("clean email ")
|
||||
} else {
|
||||
cleanEmailInput.SetLabel("[red]clean email :(")
|
||||
}
|
||||
})
|
||||
decisionFI.SetCurrentOption(1)
|
||||
pages.SwitchToPage("review")
|
||||
app.SetFocus(cleanEmailInput)
|
||||
|
@ -342,6 +430,28 @@ func _main() error {
|
|||
}
|
||||
pages.SwitchToPage("notate")
|
||||
return nil
|
||||
case 'F':
|
||||
app.Suspend(func() {
|
||||
ix, err := searchSignups(signups)
|
||||
if err != nil {
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// no match or interrupt. who cares
|
||||
switch exiterr.ExitCode() {
|
||||
case 1: case 130:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errorModal.SetText(fmt.Sprintf("error! failed to search: %s", err.Error()))
|
||||
pages.SwitchToPage("error")
|
||||
} else if ix >= 0 {
|
||||
signupIx = ix
|
||||
}
|
||||
})
|
||||
|
||||
updateCount()
|
||||
render()
|
||||
return nil
|
||||
case 'Q':
|
||||
app.Stop()
|
||||
}
|
||||
|
|
|
@ -206,7 +206,7 @@ func _main(l *log.Logger, db *sql.DB) error {
|
|||
func(s *scene, tv *tview.TextView, msg string) {
|
||||
// TODO could check and see if it's email shaped and admonish if not
|
||||
trimmed := strings.TrimSpace(msg)
|
||||
fmt.Fprintln(tv, s.Host.Say(fmt.Sprintf("I heard '%s'. Is that right?", trimmed)))
|
||||
fmt.Fprintln(tv, s.Host.Say(fmt.Sprintf("I heard '%s'. Is that right? if so, /nod", trimmed)))
|
||||
}),
|
||||
newScene("how", heredoc.Doc(`
|
||||
The workshop fades away. You hear the sound of a dial up modem
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# townstats
|
||||
|
||||
This program dumps information about tilde.town in the [Tilde Data Protocol](http://protocol.club/~datagrok/beta-wiki/tdp.html).
|
||||
Code for dumping information about tilde.town in the [Tilde Data Protocol](http://protocol.club/~datagrok/beta-wiki/tdp.html).
|
||||
|
||||
# author
|
||||
|
||||
|
@ -8,4 +8,4 @@ vilmibm, based on python work by [datagrok](https://datagrok.org)
|
|||
|
||||
# license
|
||||
|
||||
gplv3+
|
||||
gplv3+
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"git.tilde.town/tildetown/town/invites"
|
||||
"git.tilde.town/tildetown/town/stats"
|
||||
|
@ -182,6 +183,7 @@ func createUser(data newUserData) (err error) {
|
|||
|
||||
tu := towndb.TownUser{
|
||||
Username: data.Username,
|
||||
Created: time.Now(),
|
||||
Emails: []string{
|
||||
data.Email,
|
||||
},
|
||||
|
|
7
go.mod
7
go.mod
|
@ -6,6 +6,9 @@ require (
|
|||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/charmbracelet/glamour v0.5.0
|
||||
github.com/charmbracelet/lipgloss v0.6.0
|
||||
github.com/gdamore/tcell/v2 v2.5.3
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/rivo/tview v0.0.0-20230130130022-4a1b7a76c01c
|
||||
github.com/spf13/cobra v1.5.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -14,10 +17,8 @@ require (
|
|||
require (
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.6.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/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
|
@ -25,7 +26,6 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
|
@ -39,5 +39,4 @@ require (
|
|||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 // indirect
|
||||
)
|
||||
|
|
15
go.sum
15
go.sum
|
@ -1,9 +1,7 @@
|
|||
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
|
@ -28,20 +26,17 @@ github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV
|
|||
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/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
|
@ -59,7 +54,6 @@ github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2
|
|||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
|
@ -79,7 +73,6 @@ github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJ
|
|||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
|
@ -88,15 +81,9 @@ github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
|
|||
github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -114,8 +101,6 @@ 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=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
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/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
|
|
@ -318,7 +318,7 @@ func Stats() (TildeData, error) {
|
|||
return TildeData{
|
||||
Name: "tilde.town",
|
||||
URL: "https://tilde.town",
|
||||
SignupURL: "https://cgi.tilde.town/users/signup",
|
||||
SignupURL: "https://tilde.town/signup",
|
||||
WantUsers: true,
|
||||
AdminEmail: "root@tilde.town",
|
||||
Description: description,
|
||||
|
|
|
@ -27,34 +27,57 @@ type AdminNote struct {
|
|||
}
|
||||
|
||||
func (n *AdminNote) Insert(db *sql.DB) error {
|
||||
n.Created = time.Now()
|
||||
stmt, err := db.Prepare(`
|
||||
INSERT INTO notes (created, authorid, content, userid)
|
||||
VALUES (
|
||||
?, ?, ?, ?
|
||||
)`)
|
||||
var (
|
||||
err error
|
||||
stmt *sql.Stmt
|
||||
result sql.Result
|
||||
liid int64
|
||||
)
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := stmt.Exec(
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
stmt, err = tx.Prepare(`
|
||||
INSERT INTO notes (created, author, content)
|
||||
VALUES (?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err = stmt.Exec(
|
||||
n.Created.Unix(),
|
||||
n.AuthorID,
|
||||
n.Content,
|
||||
n.UserID)
|
||||
n.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
liid, err := result.LastInsertId()
|
||||
liid, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.ID = liid
|
||||
|
||||
return nil
|
||||
stmt, err = tx.Prepare(`
|
||||
INSERT INTO user_notes (noteid, userid) VALUES (?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(n.ID, n.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
type TownUser struct {
|
||||
|
@ -83,21 +106,17 @@ func (u *TownUser) Insert(db *sql.DB) (err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
// TODO this does not set the admin flag intentionally as rn this code is
|
||||
// just meant to be called by the welcome binary; other stuff for now is just
|
||||
// expected to be done via sql
|
||||
|
||||
u.Created = time.Now()
|
||||
if stmt, err = tx.Prepare(`
|
||||
INSERT INTO users (created, username, state)
|
||||
VALUES (?, ?, ?)`); err != nil {
|
||||
INSERT INTO users (created, username, state, admin)
|
||||
VALUES (?, ?, ?, ?)`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result, err = stmt.Exec(
|
||||
u.Created.Unix(),
|
||||
u.Username,
|
||||
u.State); err != nil {
|
||||
u.State,
|
||||
u.IsAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue