forked from tildetown/town
retooling for sql
parent
0b940cdf1c
commit
f579c811f3
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
@ -8,29 +9,25 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"git.tilde.town/tildetown/town/signup"
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// TODO remove sidebar
|
||||
// TODO add /help
|
||||
|
||||
const (
|
||||
maxInputLength = 10000
|
||||
signupDirectory = "/town/signups"
|
||||
logDir = "/town/var/signup"
|
||||
)
|
||||
|
||||
type scene struct {
|
||||
Name string
|
||||
Description string
|
||||
Key string
|
||||
Host *character
|
||||
}
|
||||
|
||||
type townApplication struct {
|
||||
When time.Time
|
||||
Answers map[string]string
|
||||
Write func([]byte)
|
||||
}
|
||||
|
||||
type character struct {
|
||||
|
@ -65,14 +62,21 @@ func main() {
|
|||
}
|
||||
|
||||
logger := log.New(logF, "", log.Ldate|log.Ltime)
|
||||
err = _main(logger)
|
||||
|
||||
db, err := signup.NewDB()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
err = _main(logger, db)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(3)
|
||||
}
|
||||
}
|
||||
|
||||
func _main(l *log.Logger) error {
|
||||
func _main(l *log.Logger, db *signup.DB) error {
|
||||
l.Println("starting a session")
|
||||
pages := tview.NewPages()
|
||||
mainFlex := tview.NewFlex()
|
||||
|
@ -126,31 +130,15 @@ func _main(l *log.Logger) error {
|
|||
|
||||
player := newCharacter("you", "TODO")
|
||||
|
||||
townApp := &townApplication{
|
||||
Answers: map[string]string{},
|
||||
}
|
||||
su := &signup.TownSignup{ID: -1}
|
||||
|
||||
save := func() {
|
||||
townApp.When = time.Now()
|
||||
output, err := json.Marshal(townApp)
|
||||
if err != nil {
|
||||
l.Printf("could not serialize stuff: %s", err.Error())
|
||||
l.Printf("dumping values: %v", townApp)
|
||||
return
|
||||
}
|
||||
su.Created = time.Now()
|
||||
err := db.InsertSignup(su)
|
||||
|
||||
f, err := os.Create(path.Join(signupDirectory, fmt.Sprintf("%d.json", townApp.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))
|
||||
l.Printf("failed to write to db: %s", err.Error())
|
||||
l.Printf("dumping values: %#v", su)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +164,9 @@ func _main(l *log.Logger) error {
|
|||
when you're ready to move on, [-:-:b]/nod[-:-:-]
|
||||
`),
|
||||
Host: newCharacter("wire guy", "a lil homonculus made of discarded computer cables"),
|
||||
Key: "email",
|
||||
Write: func(b []byte) {
|
||||
su.Email = string(b)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nodded",
|
||||
|
@ -198,7 +188,9 @@ func _main(l *log.Logger) error {
|
|||
just say your answer out loud. when you've said what you want, [-:-:b]/lean[-:-:-]
|
||||
against a tree.
|
||||
`),
|
||||
Key: "how",
|
||||
Write: func(b []byte) {
|
||||
su.How = string(b)
|
||||
},
|
||||
Host: newCharacter("the shrike", "a little grey bird. it has a pretty song."),
|
||||
},
|
||||
{
|
||||
|
@ -217,7 +209,9 @@ func _main(l *log.Logger) error {
|
|||
as usual, just say your answer. when you're satisfied, please [-:-:b]/spin[-:-:-]
|
||||
around in this void.
|
||||
`),
|
||||
Key: "why",
|
||||
Write: func(b []byte) {
|
||||
su.Why = string(b)
|
||||
},
|
||||
Host: newCharacter("the vcr", "a black and grey VCR from 1991"),
|
||||
},
|
||||
{
|
||||
|
@ -241,7 +235,9 @@ func _main(l *log.Logger) error {
|
|||
when you're happy you can submit this whole experience by leaving the
|
||||
store. just [-:-:b]/open[-:-:-] the door.
|
||||
`),
|
||||
Key: "where",
|
||||
Write: func(b []byte) {
|
||||
su.Links = string(b)
|
||||
},
|
||||
Host: newCharacter("the mop", "a greying mop with a wooden handle."),
|
||||
},
|
||||
{
|
||||
|
@ -255,21 +251,29 @@ func _main(l *log.Logger) error {
|
|||
|
||||
ok bye have a good one~
|
||||
`),
|
||||
Key: "extra",
|
||||
Write: func(b []byte) {
|
||||
su.Extra = string(b)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sceneIx := 0
|
||||
currentScene := scenes[sceneIx]
|
||||
|
||||
inputBuff := []byte{}
|
||||
inputWriter := bytes.NewBuffer(inputBuff)
|
||||
|
||||
advanceScene := func(fromScene, sorryMsg string) {
|
||||
if currentScene.Name != fromScene {
|
||||
return
|
||||
}
|
||||
if len(townApp.Answers[currentScene.Key]) == 0 {
|
||||
if inputWriter.Len() == 0 {
|
||||
fmt.Fprintln(msgScroll, currentScene.Host.Say(sorryMsg))
|
||||
return
|
||||
}
|
||||
currentScene.Write(inputBuff)
|
||||
inputBuff = []byte{}
|
||||
inputWriter = bytes.NewBuffer(inputBuff)
|
||||
sceneIx++
|
||||
currentScene = scenes[sceneIx]
|
||||
fmt.Fprintln(msgScroll, heredoc.Doc(`
|
||||
|
@ -310,15 +314,22 @@ func _main(l *log.Logger) error {
|
|||
}
|
||||
return
|
||||
}
|
||||
if len(townApp.Answers[currentScene.Key]) > maxInputLength {
|
||||
if inputWriter.Len() > 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))
|
||||
townApp.Answers[currentScene.Key] += ("\n" + msg)
|
||||
fmt.Fprintln(inputWriter, msg)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if currentScene.Name == "done" {
|
||||
currentScene.Write(inputBuff)
|
||||
db.UpdateSignup(su)
|
||||
}
|
||||
}()
|
||||
|
||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
switch event.Key() {
|
||||
case tcell.KeyCtrlD:
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package signup
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AdminNote struct {
|
||||
ID int64
|
||||
Admin string
|
||||
Note string
|
||||
When time.Time
|
||||
}
|
||||
|
||||
type SignupDecision string
|
||||
type UserState string
|
||||
|
||||
const (
|
||||
SignupAccepted = "accepted"
|
||||
SignupRejected = "rejected"
|
||||
StateActive = "active"
|
||||
StateTempBan = "temp_banned"
|
||||
StateBan = "banned"
|
||||
)
|
||||
|
||||
type TownSignup struct {
|
||||
ID int64
|
||||
Created time.Time
|
||||
Email string
|
||||
How string
|
||||
Why string
|
||||
Links string
|
||||
Extra string
|
||||
Notes []AdminNote
|
||||
Decision SignupDecision
|
||||
}
|
||||
|
||||
type TownAccount struct {
|
||||
ID int
|
||||
Emails []string
|
||||
Username string
|
||||
Signup int
|
||||
Notes []AdminNote
|
||||
State UserState
|
||||
Admin bool
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDB() (*DB, error) {
|
||||
db, err := sql.Open("sqlite3", "/town/var/signups.db")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DB{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DB) InsertSignup(su *TownSignup) error {
|
||||
stmt, err := d.db.Prepare(`
|
||||
INSERT INTO signups (created, email, how, why, links, extra) VALUES(
|
||||
?, ?, ?, ?, ?, ?
|
||||
) RETURNING id
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := stmt.Exec(su.Created.Unix(), su.Email, su.How, su.Why, su.Links, su.Extra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
liid, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
su.ID = liid
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) UpdateSignup(su *TownSignup) error {
|
||||
if su.ID < 0 {
|
||||
return nil
|
||||
}
|
||||
stmt, err := d.db.Prepare(`
|
||||
UPDATE signups (email, how, why, links, extra) VALUES(
|
||||
?, ?, ?, ?, ?
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(su.Email, su.How, su.Why, su.Links, su.Extra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE IF NOT EXISTS signups (
|
||||
id INTEGER PRIMARY KEY,
|
||||
created TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
||||
email TEXT,
|
||||
how TEXT,
|
||||
why TEXT,
|
||||
links TEXT,
|
||||
extra TEXT,
|
||||
decision TEXT
|
||||
);
|
|
@ -0,0 +1,45 @@
|
|||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
created TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
||||
username TEXT UNIQUE,
|
||||
signupid INTEGER,
|
||||
state TEXT,
|
||||
admin INTEGER DEFAULT FALSE,
|
||||
|
||||
FOREIGN KEY (signupid) REFERENCES signups(signupid)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS emails (
|
||||
id INTEGER PRIMARY KEY,
|
||||
address TEXT UNIQUE,
|
||||
userid INTEGER,
|
||||
|
||||
FOREIGN KEY (userid) REFERENCES users(userid)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_notes (
|
||||
noteid INTEGER,
|
||||
userid INTEGER,
|
||||
|
||||
PRIMARY KEY (noteid, userid),
|
||||
FOREIGN KEY (noteid) REFERENCES notes(noteid),
|
||||
FOREIGN KEY (userid) REFERENCES users(userid)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
adminid INTEGER,
|
||||
text TEXT,
|
||||
created TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
||||
|
||||
FOREIGN KEY (adminid) REFERENCES users(adminid)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS signup_notes (
|
||||
noteid INTEGER,
|
||||
signupid INTEGER,
|
||||
|
||||
PRIMARY KEY (noteid, signupid),
|
||||
FOREIGN KEY (noteid) REFERENCES notes(noteid),
|
||||
FOREIGN KEY (signupid) REFERENCES signups(signupid)
|
||||
);
|
Loading…
Reference in New Issue