forked from tildetown/town
155 lines
2.5 KiB
Go
155 lines
2.5 KiB
Go
package invites
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"errors"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
const (
|
|
dsn = "/town/var/invites/invites.db?mode=rw"
|
|
codeLen = 32
|
|
)
|
|
|
|
type Invite struct {
|
|
ID int64
|
|
Created time.Time
|
|
Code string
|
|
Email string
|
|
Used bool
|
|
}
|
|
|
|
func (i *Invite) Insert(db *sql.DB) error {
|
|
stmt, err := db.Prepare(`
|
|
INSERT INTO invites (code, email) VALUES (?, ?)
|
|
`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
i.Code = generateCode(i.Email)
|
|
|
|
_, err = stmt.Exec(i.Code, i.Email)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
func ConnectDB() (*sql.DB, error) {
|
|
db, err := sql.Open("sqlite3", dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func generateCode(email string) string {
|
|
|
|
charset := "abcdefghijklmnopqrztuvwxyz"
|
|
charset += strings.ToUpper(charset)
|
|
charset += "0123456789"
|
|
charset += "`~!@#$%^&*()-=_+[]{}|;:,./<>?"
|
|
|
|
code := []byte{}
|
|
|
|
max := big.NewInt(int64(len(charset)))
|
|
for len(code) < codeLen {
|
|
ix, err := rand.Int(rand.Reader, max)
|
|
if err != nil {
|
|
// TODO this is bad but I'm just kind of hoping it doesn't happen...often
|
|
panic(err)
|
|
}
|
|
code = append(code, charset[ix.Int64()])
|
|
}
|
|
|
|
code = append(code, ' ')
|
|
|
|
eb := []byte(email)
|
|
for x := 0; x < len(eb); x++ {
|
|
code = append(code, eb[x])
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(code)
|
|
}
|
|
|
|
func Decode(code string) ([]string, error) {
|
|
decoded, err := base64.StdEncoding.DecodeString(code)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return strings.Split(string(decoded), " "), nil
|
|
}
|
|
|
|
func Get(db *sql.DB, code string) (*Invite, error) {
|
|
inv := &Invite{
|
|
Code: code,
|
|
}
|
|
var created string
|
|
var used int
|
|
stmt, err := db.Prepare(`
|
|
SELECT id, created, email, used
|
|
FROM invites WHERE code = ?`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
row := stmt.QueryRow(code)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
err = row.Scan(
|
|
&inv.ID,
|
|
&created,
|
|
&inv.Email,
|
|
&used,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inv.Created, err = time.Parse("2006-01-02T15:04", created)
|
|
if err != nil {
|
|
return inv, err
|
|
}
|
|
|
|
inv.Used = used > 0
|
|
|
|
return inv, nil
|
|
}
|
|
|
|
func (i *Invite) MarkUsed(db *sql.DB) (err error) {
|
|
var stmt *sql.Stmt
|
|
var result sql.Result
|
|
var rowsAffected int64
|
|
if stmt, err = db.Prepare(`UPDATE invites SET used = 1 WHERE id = ?`); err != nil {
|
|
return
|
|
}
|
|
|
|
if result, err = stmt.Exec(i.ID); err != nil {
|
|
return
|
|
}
|
|
|
|
if rowsAffected, err = result.RowsAffected(); err != nil {
|
|
return
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
err = errors.New("no rows affected")
|
|
}
|
|
|
|
return
|
|
}
|