town/cmd/review/main.go

351 lines
8.2 KiB
Go
Raw Normal View History

2023-02-14 04:48:12 +00:00
package main
import (
2023-02-23 00:04:26 +00:00
"database/sql"
2023-02-17 05:53:05 +00:00
"errors"
2023-02-14 04:48:12 +00:00
"fmt"
2023-02-14 07:33:53 +00:00
"math/rand"
2023-02-14 04:48:12 +00:00
"os"
2023-02-17 05:53:05 +00:00
"os/user"
2023-02-14 07:33:53 +00:00
"strings"
"time"
2023-02-23 00:04:26 +00:00
"git.tilde.town/tildetown/town/models"
//"git.tilde.town/tildetown/town/review"
"git.tilde.town/tildetown/town/signup"
2023-02-17 05:53:05 +00:00
tuser "git.tilde.town/tildetown/town/user"
2023-02-14 07:33:53 +00:00
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
func getTitle() string {
titles := []string{
"yo bum rush the show",
"can i kick it?",
"super nintendo sega genesis",
"birthdays was the worst days",
"where were you when we were getting high?",
"it's real time, real time, time to get real",
}
return titles[rand.Intn(len(titles))]
}
2023-02-23 00:04:26 +00:00
// TODO affordance for cleaning up email response
2023-02-16 05:48:26 +00:00
2023-02-23 00:04:26 +00:00
type reviewer struct {
db *sql.DB
adminName string
2023-02-14 07:33:53 +00:00
}
2023-02-23 00:04:26 +00:00
func newReviewer(db *sql.DB, adminName string) *reviewer {
return &reviewer{db: db, adminName: adminName}
2023-02-16 05:48:26 +00:00
}
2023-02-16 05:08:31 +00:00
2023-02-23 00:04:26 +00:00
func (r *reviewer) Review(s *models.TownSignup, decision models.SignupDecision) error {
2023-02-16 05:48:26 +00:00
s.DecisionTime = time.Now()
2023-02-23 00:04:26 +00:00
s.Decision = decision
s.DecidedBy = r.adminName
2023-02-23 22:20:39 +00:00
return s.Review(r.db)
2023-02-16 05:48:26 +00:00
}
2023-02-23 00:14:38 +00:00
func (r *reviewer) AddNote(s *models.TownSignup, content string) error {
note := &models.SignupNote{
2023-02-23 06:20:00 +00:00
Author: r.adminName,
Content: content,
SignupID: s.ID,
2023-02-23 00:14:38 +00:00
}
2023-02-23 06:20:00 +00:00
return note.Insert(r.db)
2023-02-23 00:14:38 +00:00
}
2023-02-23 00:04:26 +00:00
func renderSignup(s models.TownSignup) string {
2023-02-23 07:30:03 +00:00
out := ""
2023-02-23 00:04:26 +00:00
2023-02-23 07:30:03 +00:00
pairs := [][]string{
{"submitted", s.Created.Format("2006-01-02 15:04")},
{"e-mail", s.Email},
{"how found / referral", s.How},
{"why like town / plans", s.Why},
{"links", s.Links},
2023-02-23 00:04:26 +00:00
}
2023-02-16 05:48:26 +00:00
2023-02-23 07:30:03 +00:00
for _, v := range pairs {
out += fmt.Sprintf("[-:-:b]%s[-:-:-]\n", v[0])
out += strings.TrimSpace(v[1])
2023-02-16 05:08:31 +00:00
out += "\n\n"
}
return out
2023-02-14 07:33:53 +00:00
}
2023-02-23 07:19:30 +00:00
func renderNotes(s models.TownSignup) string {
out := ""
for _, note := range s.Notes {
out += fmt.Sprintf(`%s said on %s:
%s`, note.Author, note.Created.Format("2006-01-02 15:04"), note.Content)
out += "\n\n"
}
return out
}
2023-02-23 00:04:26 +00:00
func _main() error {
/*
TODO will use this for invites
userDB, err := review.ConnectDB()
2023-02-16 05:08:31 +00:00
if err != nil {
2023-02-23 00:04:26 +00:00
return fmt.Errorf("could not connect to user database: %w", err)
2023-02-16 05:08:31 +00:00
}
2023-02-23 00:04:26 +00:00
*/
2023-02-16 05:08:31 +00:00
2023-02-23 00:04:26 +00:00
signupDB, err := signup.ConnectDB()
if err != nil {
return fmt.Errorf("could not connect to signups database: %w", err)
2023-02-14 07:33:53 +00:00
}
2023-02-17 05:53:05 +00:00
u, err := user.Current()
if err != nil {
return fmt.Errorf("that's my purse. I don't know you! %w", err)
}
isAdmin, err := tuser.IsAdmin(u)
if err != nil {
return fmt.Errorf("that's my purse. I don't know you! %w", err)
}
if !isAdmin {
return errors.New("this command can only be run by a town admin")
}
2023-02-23 00:04:26 +00:00
r := newReviewer(signupDB, u.Username)
2023-02-14 07:33:53 +00:00
rand.Seed(time.Now().Unix())
2023-02-23 00:04:26 +00:00
su := models.TownSignup{}
signups, err := su.All(signupDB)
2023-02-14 07:33:53 +00:00
if err != nil {
2023-02-23 00:04:26 +00:00
return fmt.Errorf("could not fetch signups: %w", err)
2023-02-14 07:33:53 +00:00
}
2023-02-16 05:08:31 +00:00
signupIx := 0
2023-02-14 07:33:53 +00:00
title := tview.NewTextView()
title.SetText(getTitle())
2023-02-16 05:08:31 +00:00
title.SetTextAlign(tview.AlignCenter)
2023-02-16 05:48:26 +00:00
title.SetTextColor(tcell.ColorPurple)
2023-02-16 05:08:31 +00:00
title.SetBackgroundColor(tcell.ColorBlack)
2023-02-14 07:33:53 +00:00
appView := tview.NewTextView()
2023-02-16 05:08:31 +00:00
appView.SetDynamicColors(true)
2023-02-14 07:33:53 +00:00
legend := tview.NewTextView()
2023-02-16 05:48:26 +00:00
legend.SetText("s: skip r: random A: approve R: reject N: notate Q: quit")
legend.SetTextColor(tcell.ColorPurple)
legend.SetTextAlign(tview.AlignCenter)
legend.SetBackgroundColor(tcell.ColorBlack)
2023-02-14 07:33:53 +00:00
2023-02-17 05:53:05 +00:00
count := tview.NewTextView()
count.SetDynamicColors(true)
updateCount := func() {
2023-02-23 07:30:03 +00:00
plural := "s"
if len(signups) == 1 {
plural = ""
}
2023-02-23 22:25:15 +00:00
count.SetText(fmt.Sprintf("[-:-:b]%d of %d %s[-:-:-]", signupIx+1, len(signups), plural))
2023-02-17 05:53:05 +00:00
}
updateCount()
2023-02-23 07:19:30 +00:00
notesView := tview.NewTextView()
notesView.SetDynamicColors(true)
2023-02-23 07:30:03 +00:00
notesView.SetBorder(true).SetBorderColor(tcell.ColorPurple)
2023-02-23 07:19:30 +00:00
2023-02-17 05:53:05 +00:00
bottomFlex := tview.NewFlex()
bottomFlex.SetDirection(tview.FlexColumn)
bottomFlex.AddItem(count, 0, 1, false)
bottomFlex.AddItem(legend, 0, 10, false)
2023-02-23 07:19:30 +00:00
innerFlex := tview.NewFlex()
innerFlex.SetDirection(tview.FlexColumn)
2023-02-23 07:30:03 +00:00
innerFlex.AddItem(appView, 0, 2, true)
2023-02-23 07:19:30 +00:00
innerFlex.AddItem(notesView, 0, 1, true)
2023-02-17 05:53:05 +00:00
mainFlex := tview.NewFlex()
2023-02-14 07:33:53 +00:00
mainFlex.SetDirection(tview.FlexRow)
mainFlex.AddItem(title, 1, -1, false)
2023-02-23 07:19:30 +00:00
mainFlex.AddItem(innerFlex, 0, 1, false)
2023-02-17 05:53:05 +00:00
mainFlex.AddItem(bottomFlex, 1, -1, false)
pages := tview.NewPages()
errorModal := tview.NewModal()
errorModal.AddButtons([]string{"damn"})
errorModal.SetDoneFunc(func(ix int, _ string) {
pages.SwitchToPage("main")
})
2023-02-14 07:33:53 +00:00
2023-02-23 07:19:30 +00:00
render := func() {
if len(signups) == 0 {
appView.SetText("no signups")
return
}
currSignup := signups[signupIx]
err := currSignup.RefreshNotes(signupDB)
if err != nil {
errorModal.SetText(fmt.Sprintf("error! failed to add note: %s", err.Error()))
pages.SwitchToPage("error")
}
appView.SetText(renderSignup(*currSignup))
notesView.SetText(renderNotes(*currSignup))
}
render()
2023-02-17 06:06:37 +00:00
notate := tview.NewForm()
notate.AddTextArea("note", "", 80, 10, 1000, func(string) {})
notate.AddButton("submit", func() {
2023-02-23 00:14:38 +00:00
fi := notate.GetFormItemByLabel("note").(*tview.TextArea)
err = r.AddNote(signups[signupIx], fi.GetText())
if err != nil {
errorModal.SetText(fmt.Sprintf("error! failed to add note: %s", err.Error()))
pages.SwitchToPage("error")
return
}
2023-02-23 07:19:30 +00:00
render()
2023-02-23 00:14:38 +00:00
2023-02-17 06:06:37 +00:00
pages.SwitchToPage("main")
})
notate.AddButton("cancel", func() {
pages.SwitchToPage("main")
})
2023-02-23 22:20:39 +00:00
reviewModal := tview.NewFlex().SetDirection(tview.FlexRow)
2023-02-23 07:40:31 +00:00
2023-02-23 22:20:39 +00:00
providedEmailView := tview.NewTextView()
providedEmailView.SetTitle("provided email input")
2023-02-23 07:40:31 +00:00
2023-02-23 22:20:39 +00:00
reviewForm := tview.NewForm()
decisionFI := tview.NewDropDown().SetLabel("decision")
decisionFI.SetOptions([]string{"accepted", "rejected"}, func(_ string, _ int) {})
2023-02-14 07:33:53 +00:00
2023-02-23 22:20:39 +00:00
cleanEmailInput := tview.NewInputField()
cleanEmailInput.SetLabel("clean email")
cleanEmailInput.SetAcceptanceFunc(func(tx string, _ rune) bool { return len(tx) > 0 })
2023-02-14 07:33:53 +00:00
2023-02-23 22:20:39 +00:00
reviewForm.AddFormItem(decisionFI)
reviewForm.AddFormItem(cleanEmailInput)
reviewForm.AddButton("submit", func() {
currSignup := signups[signupIx]
cleanEmail := reviewForm.GetFormItemByLabel("clean email").(*tview.InputField).GetText()
currSignup.CleanEmail = cleanEmail
decision := models.SignupRejected
_, d := reviewForm.GetFormItemByLabel("decision").(*tview.DropDown).GetCurrentOption()
if d == "accepted" {
decision = models.SignupAccepted
}
err := r.Review(currSignup, decision)
if err != nil {
errorModal.SetText(fmt.Sprintf("error! failed to submit review: %s", err.Error()))
pages.SwitchToPage("error")
return
2023-02-17 05:53:05 +00:00
}
2023-02-23 00:04:26 +00:00
newSignups := []*models.TownSignup{}
2023-02-17 05:53:05 +00:00
for ix, s := range signups {
if ix != signupIx {
newSignups = append(newSignups, s)
}
}
signups = newSignups
if len(signups) > 0 {
if signupIx >= len(signups) {
signupIx = 0
}
}
2023-02-23 22:20:39 +00:00
updateCount()
2023-02-23 07:19:30 +00:00
render()
2023-02-23 22:20:39 +00:00
if decision == models.SignupAccepted {
// TODO generate invite token
// TODO send invite email
}
pages.SwitchToPage("main")
})
reviewForm.AddButton("cancel", func() {
pages.SwitchToPage("main")
})
reviewModal.AddItem(tview.NewTextView().SetText("provided email input"), 1, 1, false)
reviewModal.AddItem(providedEmailView, 0, 1, false)
reviewModal.AddItem(reviewForm, 0, 1, true)
pages.AddPage("main", mainFlex, true, true)
pages.AddPage("error", errorModal, false, false)
pages.AddPage("notate", notate, true, false)
pages.AddPage("review", reviewModal, true, false)
app := tview.NewApplication()
app.SetRoot(pages, true)
2023-02-17 05:53:05 +00:00
2023-02-14 07:33:53 +00:00
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
2023-02-23 06:20:00 +00:00
currPage, _ := pages.GetFrontPage()
2023-02-23 22:25:15 +00:00
if currPage == "notate" || currPage == "review" {
2023-02-23 06:20:00 +00:00
return event
}
2023-02-14 07:33:53 +00:00
switch event.Rune() {
case 's':
2023-02-23 22:20:39 +00:00
signupIx++
if signupIx == len(signups) {
signupIx = 0
}
render()
2023-02-14 07:33:53 +00:00
case 'r':
2023-02-17 05:53:05 +00:00
if len(signups) > 0 {
signupIx = rand.Intn(len(signups))
2023-02-23 07:19:30 +00:00
render()
2023-02-17 05:53:05 +00:00
}
2023-02-14 07:33:53 +00:00
case 'A':
2023-02-17 05:53:05 +00:00
if len(signups) == 0 {
return nil
}
2023-02-23 22:20:39 +00:00
providedEmailView.SetText(signups[signupIx].Email)
decisionFI.SetCurrentOption(0)
pages.SwitchToPage("review")
app.SetFocus(cleanEmailInput)
2023-02-23 22:25:15 +00:00
return nil
2023-02-14 07:33:53 +00:00
case 'R':
2023-02-17 05:53:05 +00:00
if len(signups) == 0 {
return nil
}
2023-02-23 22:20:39 +00:00
providedEmailView.SetText(signups[signupIx].Email)
decisionFI.SetCurrentOption(1)
pages.SwitchToPage("review")
2023-02-23 22:25:15 +00:00
app.SetFocus(cleanEmailInput)
return nil
2023-02-17 05:53:05 +00:00
case 'N':
if len(signups) == 0 {
return nil
2023-02-16 05:48:26 +00:00
}
2023-02-17 06:06:37 +00:00
pages.SwitchToPage("notate")
return nil
2023-02-14 07:33:53 +00:00
case 'Q':
app.Stop()
}
return event
})
return app.Run()
2023-02-14 04:48:12 +00:00
}
func main() {
err := _main()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}