town/cmd/review/main.go

356 lines
8.5 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-26 23:02:51 +00:00
"git.tilde.town/tildetown/town/invites"
2023-02-23 00:04:26 +00:00
"git.tilde.town/tildetown/town/models"
"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
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 {
2023-02-26 23:02:51 +00:00
inviteDB, err := invites.ConnectDB()
if err != nil {
return fmt.Errorf("could not connect to invites database: %w", err)
}
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-26 23:02:51 +00:00
count.SetText(fmt.Sprintf("[-:-:b]%d of %d[-:-:-]", signupIx+1, len(signups)))
if len(signups) == 0 {
count.SetText("")
2023-02-23 07:30:03 +00:00
}
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 {
2023-03-09 06:33:31 +00:00
invite := &invites.Invite{
Email: currSignup.CleanEmail,
}
if err = invite.Insert(inviteDB); err != nil {
2023-02-26 23:02:51 +00:00
errorModal.SetText(fmt.Sprintf("error! failed to create invite: %s", err.Error()))
pages.SwitchToPage("error")
}
2023-03-09 06:33:31 +00:00
if err = sendInviteEmail(*invite); err != nil {
2023-03-07 01:06:46 +00:00
errorModal.SetText(fmt.Sprintf("error! failed to send welcome email: %s", err.Error()))
pages.SwitchToPage("error")
}
2023-02-23 22:20:39 +00:00
}
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)
}
}